From f2b0a5e7c731d993fc7822dcdd95bc8367b84a75 Mon Sep 17 00:00:00 2001 From: Danny Bessems Date: Wed, 22 Feb 2023 21:24:42 +0100 Subject: [PATCH] Test dependencies --- .drone.yml | 31 + .github/ISSUE_TEMPLATE/bug_report.md | 33 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/PULL_REQUEST_TEMPLATE.md | 6 + Dockerfile | 46 + Makefile | 938 ++++++++++++++++++ OWNERS | 4 + README.md | 5 + ansible.cfg | 20 + ansible/.gitignore | 1 + ansible/firstboot.yml | 43 + ansible/node.yml | 56 ++ ansible/python.yml | 32 + ansible/roles/containerd/defaults/main.yml | 15 + ansible/roles/containerd/tasks/debian.yml | 18 + ansible/roles/containerd/tasks/main.yml | 169 ++++ ansible/roles/containerd/tasks/photon.yml | 16 + ansible/roles/containerd/tasks/redhat.yml | 19 + .../templates/etc/containerd/config.toml | 33 + .../containerd/templates/etc/crictl.yaml | 1 + .../systemd/system/containerd-flatcar.conf | 6 + .../containerd.service.d/http-proxy.conf | 10 + .../containerd.service.d/max-tasks.conf | 3 + .../containerd.service.d/memory-pressure.conf | 8 + ansible/roles/firstboot/README.md | 2 + ansible/roles/firstboot/defaults | 1 + ansible/roles/firstboot/meta/main.yml | 26 + ansible/roles/firstboot/tasks/main.yaml | 19 + ansible/roles/firstboot/tasks/photon.yml | 24 + ansible/roles/firstboot/tasks/qemu.yml | 17 + ansible/roles/kubernetes/defaults/main.yml | 41 + ansible/roles/kubernetes/tasks/crictl-url.yml | 54 + ansible/roles/kubernetes/tasks/debian.yml | 36 + ansible/roles/kubernetes/tasks/ecrpull.yml | 29 + .../roles/kubernetes/tasks/kubeadmpull.yml | 14 + ansible/roles/kubernetes/tasks/main.yml | 75 ++ ansible/roles/kubernetes/tasks/photon.yml | 24 + ansible/roles/kubernetes/tasks/redhat.yml | 34 + ansible/roles/kubernetes/tasks/url.yml | 115 +++ .../kubernetes/templates/etc/kubeadm.yml | 11 + .../templates/etc/kubernetes-version | 1 + .../templates/etc/sysconfig/kubelet | 1 + .../templates/etc/yum.repos.d/kubernetes.repo | 7 + .../usr/lib/systemd/system/kubelet.service | 14 + .../system/kubelet.service.d/10-kubeadm.conf | 11 + .../defaults/main.yml | 22 + .../tasks/executables.yml | 22 + .../load_additional_components/tasks/main.yml | 23 + .../tasks/registry.yml | 19 + .../load_additional_components/tasks/url.yml | 37 + ansible/roles/node/defaults/main.yml | 118 +++ .../files/etc/audit/rules.d/containerd.rules | 10 + .../audit/rules.d/containerd.rules-flatcar | 10 + .../usr/local/bin/etcd-network-tuning.sh | 38 + ansible/roles/node/meta/main.yml | 38 + ansible/roles/node/tasks/amazonLinux2.yml | 28 + ansible/roles/node/tasks/main.yml | 133 +++ ansible/roles/node/tasks/photon.yml | 53 + .../etc/udev/rules.d/90-etcd-tuning.rules | 15 + ansible/roles/providers/defaults/main.yml | 17 + .../roles/providers/files/etc/azure/iptables | 8 + .../etc/cloud/cloud.cfg.d/05_logging.cfg | 67 ++ .../etc/cloud/cloud.cfg.d/99_metadata.cfg | 2 + .../no-carrier.d/20-chrony.j2 | 26 + .../networkd-dispatcher/off.d/20-chrony.j2 | 26 + .../routable.d/20-chrony.j2 | 27 + .../cloud-config.service.d/boot-order.conf | 3 + .../cloud-final.service.d/boot-order.conf | 3 + .../system/modify-cloud-init-cfg.service | 12 + .../files/etc/vmware-tools/tools.conf | 3 + .../files/tmp/cloud-init_22.2-outscale.deb | Bin 0 -> 482784 bytes .../cloudinit/feature_overrides.py | 1 + .../providers/files/usr/libexec/chrony-helper | 251 +++++ .../usr/local/bin/modify-cloud-init-cfg.sh | 3 + ansible/roles/providers/tasks/aws.yml | 74 ++ ansible/roles/providers/tasks/azure.yml | 67 ++ ansible/roles/providers/tasks/cloudstack.yml | 39 + ansible/roles/providers/tasks/debian.yml | 34 + .../roles/providers/tasks/googlecompute.yml | 47 + ansible/roles/providers/tasks/main.yml | 120 +++ ansible/roles/providers/tasks/nutanix.yml | 76 ++ ansible/roles/providers/tasks/oci.yml | 34 + ansible/roles/providers/tasks/outscale.yml | 24 + ansible/roles/providers/tasks/qemu.yml | 49 + ansible/roles/providers/tasks/raw.yml | 57 ++ ansible/roles/providers/tasks/redhat.yml | 30 + .../roles/providers/tasks/vmware-photon.yml | 79 ++ .../roles/providers/tasks/vmware-redhat.yml | 51 + .../roles/providers/tasks/vmware-ubuntu.yml | 45 + ansible/roles/providers/tasks/vmware.yml | 57 ++ ansible/roles/python/defaults/main.yml | 19 + ansible/roles/python/tasks/flatcar.yml | 30 + ansible/roles/python/tasks/main.yml | 22 + ansible/roles/setup/defaults/main.yml | 27 + .../10-flatcar-path | 3 + .../etc/systemd/system/usr-libexec.mount | 11 + .../roles/setup/tasks/bootstrap-flatcar.yml | 28 + ansible/roles/setup/tasks/debian.yml | 105 ++ ansible/roles/setup/tasks/flatcar.yml | 55 + ansible/roles/setup/tasks/main.yml | 36 + ansible/roles/setup/tasks/photon.yml | 61 ++ ansible/roles/setup/tasks/redhat.yml | 54 + ansible/roles/setup/tasks/rpm_repos.yml | 34 + .../templates/etc/apt/apt.conf.d/90proxy | 8 + .../setup/templates/etc/apt/sources.list.j2 | 4 + .../roles/setup/templates/photon_bash_profile | 2 + ansible/roles/sysprep/defaults/main.yml | 17 + ansible/roles/sysprep/files/etc/hosts | 2 + .../etc/netplan/51-kubevirt-netplan.yaml | 7 + ansible/roles/sysprep/tasks/debian.yml | 95 ++ ansible/roles/sysprep/tasks/flatcar.yml | 57 ++ ansible/roles/sysprep/tasks/main.yml | 234 +++++ ansible/roles/sysprep/tasks/photon.yml | 50 + ansible/roles/sysprep/tasks/redhat.yml | 79 ++ ansible/roles/sysprep/tasks/rpm_repos.yml | 33 + ansible/windows/OWNERS | 4 + ansible/windows/ansible_winrm.ps1 | 48 + ansible/windows/example.vars.yml | 38 + ansible/windows/node_windows.yml | 105 ++ .../roles/cloudbase-init/tasks/main.yml | 53 + .../templates/cloudbase-init-unattend.conf | 28 + .../templates/cloudbase-init.conf | 36 + ansible/windows/roles/debug/defaults/main.yml | 27 + ansible/windows/roles/debug/tasks/main.yml | 33 + ansible/windows/roles/gmsa/defaults/main.yml | 16 + .../files/install-gmsa-keyvault-plugin.ps1 | 134 +++ .../roles/gmsa/tasks/gmsa_keyvault.yml | 64 ++ ansible/windows/roles/gmsa/tasks/main.yml | 17 + .../roles/kubernetes/defaults/main.yml | 18 + .../roles/kubernetes/tasks/kubelet.yml | 58 ++ .../windows/roles/kubernetes/tasks/main.yml | 31 + .../windows/roles/kubernetes/tasks/nssm.yml | 39 + ansible/windows/roles/kubernetes/tasks/sc.yml | 27 + .../windows/roles/kubernetes/tasks/url.yml | 24 + .../windows/roles/kubernetes/tasks/wins.yml | 29 + .../kubernetes/templates/StartKubelet.ps1 | 43 + .../defaults/main.yml | 22 + .../tasks/executables.yml | 31 + .../load_additional_components/tasks/main.yml | 22 + .../tasks/registry.yml | 39 + .../load_additional_components/tasks/url.yml | 83 ++ .../windows/roles/providers/defaults/main.yml | 15 + .../windows/roles/providers/tasks/azure.yml | 48 + .../windows/roles/providers/tasks/main.yml | 14 + .../windows/roles/runtimes/defaults/main.yml | 28 + .../roles/runtimes/tasks/containerd.yml | 111 +++ .../roles/runtimes/tasks/docker_ee.yml | 45 + ansible/windows/roles/runtimes/tasks/main.yml | 19 + .../roles/runtimes/templates/config.toml | 37 + .../roles/systemprep/defaults/main.yml | 17 + .../windows/roles/systemprep/tasks/main.yml | 179 ++++ .../roles/systemprep/tasks/ssh-archive.yml | 73 ++ .../roles/systemprep/tasks/ssh-feature.yml | 21 + azure_targets.sh | 6 + cloudinit/.gitignore | 3 + cloudinit/Makefile | 31 + cloudinit/README.md | 7 + cloudinit/ca.crt | 17 + cloudinit/ca.key | 27 + cloudinit/id_rsa.capi | 27 + cloudinit/id_rsa.capi.pub | 1 + cloudinit/meta-data | 12 + cloudinit/user-data | 263 +++++ hack/boxes-flatcar.sh | 49 + hack/convert-cloudstack-image.sh | 136 +++ hack/ensure-ansible-windows.sh | 45 + hack/ensure-ansible.sh | 45 + hack/ensure-azure-cli.sh | 37 + hack/ensure-boskosctl.sh | 30 + hack/ensure-ct.sh | 43 + hack/ensure-go.sh | 59 ++ hack/ensure-goss.sh | 73 ++ hack/ensure-jq.sh | 43 + hack/ensure-ovftool.sh | 32 + hack/ensure-packer.sh | 54 + hack/ensure-powervs.sh | 52 + hack/ensure-vhdutil.sh | 33 + hack/generate-goss-specs.py | 161 +++ hack/image-build-flatcar.sh | 145 +++ hack/image-build-ova.py | 272 +++++ hack/image-govc-cloudinit.sh | 68 ++ hack/image-grok-latest-flatcar-version.sh | 12 + hack/image-new-kube.py | 192 ++++ hack/image-post-create-config.sh | 80 ++ hack/image-ssh.sh | 61 ++ hack/image-upload.py | 137 +++ hack/ovf_eula.txt | 273 +++++ hack/ovf_template.xml | 170 ++++ hack/utils.sh | 95 ++ hack/windows-ova-unattend.py | 80 ++ kubevirt-Dockerfile | 5 + packer/.gitignore | 3 + packer/ami/OWNERS | 4 + packer/ami/amazon-2.json | 11 + packer/ami/centos-7.json | 11 + packer/ami/flatcar.json | 17 + packer/ami/packer-windows.json | 208 ++++ packer/ami/packer.json | 210 ++++ packer/ami/rhel-8.json | 15 + packer/ami/rockylinux-8.json | 14 + packer/ami/scripts/sysprep_prerequisites.ps1 | 29 + packer/ami/scripts/winrm_bootstrap.txt | 47 + packer/ami/ubuntu-1804.json | 11 + packer/ami/ubuntu-2004.json | 11 + packer/ami/ubuntu-2204.json | 11 + packer/ami/windows-2004.json | 10 + packer/ami/windows-2019.json | 10 + packer/azure/.pipelines/build-vhd.yaml | 50 + .../azure/.pipelines/create-disk-version.yaml | 38 + packer/azure/.pipelines/create-sku.yaml | 34 + .../.pipelines/delete-storage-account.yaml | 13 + packer/azure/.pipelines/generate-sas.yaml | 21 + packer/azure/.pipelines/k8s-config.yaml | 15 + packer/azure/.pipelines/smoke-test.yaml | 59 ++ packer/azure/.pipelines/stages.yaml | 57 ++ packer/azure/.pipelines/test-vhd.yaml | 143 +++ .../azure/.pipelines/vhd-publishing-info.yaml | 19 + packer/azure/OWNERS | 8 + packer/azure/azure-config.json | 8 + packer/azure/azure-sig-gen2.json | 7 + packer/azure/azure-sig.json | 7 + packer/azure/azure-vhd.json | 5 + packer/azure/centos-7-gen2.json | 9 + packer/azure/centos-7.json | 9 + packer/azure/flatcar-gen2.json | 23 + packer/azure/flatcar.json | 23 + packer/azure/packer-windows.json | 244 +++++ packer/azure/packer.json | 276 ++++++ packer/azure/rhel-8.json | 11 + packer/azure/scripts/delete-unused-storage.sh | 156 +++ .../scripts/disable-windows-prepull.json | 3 + packer/azure/scripts/ensure-kustomize.sh | 42 + packer/azure/scripts/init-sig.sh | 100 ++ packer/azure/scripts/init-vhd.sh | 32 + packer/azure/scripts/new-disk-version.sh | 107 ++ packer/azure/scripts/new-sku.sh | 80 ++ packer/azure/scripts/parse-prow-creds.sh | 33 + packer/azure/scripts/sysprep.ps1 | 46 + .../test-templates/linux/kustomization.yaml | 7 + .../azuremachinetemplate-controlplane.yaml | 11 + .../patches/azuremachinetemplate-windows.yaml | 11 + .../azuremachinetemplate-workload.yaml | 11 + .../patches/kubeadmcontrolplane-windows.yaml | 8 + .../patches/machinedeployment-windows.yaml | 8 + .../test-templates/windows/kustomization.yaml | 8 + packer/azure/sku-template.json | 41 + packer/azure/ubuntu-1804-gen2.json | 9 + packer/azure/ubuntu-1804.json | 9 + packer/azure/ubuntu-2004-gen2.json | 9 + packer/azure/ubuntu-2004.json | 9 + packer/azure/ubuntu-2204-gen2.json | 9 + packer/azure/ubuntu-2204.json | 9 + packer/azure/windows-2004.json | 10 + packer/azure/windows-2019-containerd.json | 16 + packer/azure/windows-2019.json | 14 + packer/azure/windows-2022-containerd.json | 16 + packer/config/additional_components.json | 10 + packer/config/ansible-args.json | 5 + packer/config/cni.json | 9 + packer/config/common.json | 19 + packer/config/containerd.json | 7 + packer/config/goss-args.json | 15 + packer/config/kubernetes.json | 25 + packer/config/ppc64le/cni.json | 3 + packer/config/ppc64le/common.json | 3 + packer/config/ppc64le/containerd.json | 5 + packer/config/ppc64le/kubernetes.json | 4 + packer/config/wasm-shims.json | 6 + packer/config/windows/OWNERS | 4 + .../config/windows/ansible-args-windows.json | 3 + packer/config/windows/cloudbase-init.json | 3 + packer/config/windows/common.json | 17 + packer/config/windows/containerd.json | 4 + packer/config/windows/docker.json | 3 + packer/config/windows/kubernetes.json | 4 + packer/digitalocean/OWNERS | 6 + packer/digitalocean/centos-7.json | 5 + packer/digitalocean/packer.json | 95 ++ packer/digitalocean/ubuntu-1804.json | 5 + packer/digitalocean/ubuntu-2004.json | 5 + packer/files/flatcar/README.md | 42 + packer/files/flatcar/clc/bootstrap.yaml | 26 + packer/files/flatcar/ignition/bootstrap.json | 44 + .../flatcar/scripts/bootstrap-flatcar.sh | 45 + packer/gce/OWNERS | 4 + packer/gce/ci/nightly/README.md | 5 + packer/gce/ci/nightly/overwrite-1-23.json | 8 + packer/gce/ci/nightly/overwrite-1-24.json | 8 + packer/gce/ci/nightly/overwrite-1-25.json | 8 + packer/gce/ci/nightly/overwrite-1-26.json | 8 + packer/gce/packer.json | 126 +++ packer/gce/ubuntu-1804.json | 6 + packer/gce/ubuntu-2004.json | 6 + packer/gce/ubuntu-2204.json | 6 + packer/goss/goss-command.yaml | 261 +++++ packer/goss/goss-files.yaml | 17 + packer/goss/goss-kernel-params.yaml | 31 + packer/goss/goss-package.yaml | 86 ++ packer/goss/goss-service.yaml | 77 ++ packer/goss/goss-vars.yaml | 546 ++++++++++ packer/goss/goss.yaml | 6 + packer/nutanix/OWNERS | 7 + packer/nutanix/config.pkr.hcl | 8 + packer/nutanix/flatcar.json | 20 + packer/nutanix/nutanix.json | 11 + packer/nutanix/packer-windows.json | 149 +++ packer/nutanix/packer.json | 142 +++ packer/nutanix/rockylinux-8.json | 15 + packer/nutanix/rockylinux-9.json | 14 + packer/nutanix/ubuntu-2004.json | 8 + packer/nutanix/ubuntu-2204.json | 8 + packer/nutanix/windows-2022.json | 11 + .../windows/disable-network-discovery.cmd | 2 + packer/nutanix/windows/sysprep.ps1 | 31 + .../windows/windows-2022/autounattend.xml | 265 +++++ packer/oci/oracle-linux-8.json | 8 + packer/oci/oracle-linux-9.json | 8 + packer/oci/packer-windows.json | 148 +++ packer/oci/packer.json | 150 +++ packer/oci/scripts/attach_secondary_vnic.ps1 | 39 + packer/oci/scripts/enable_second_nic.ps1 | 47 + packer/oci/scripts/set_bootstrap.sh | 27 + packer/oci/scripts/sysprep.ps1 | 37 + packer/oci/scripts/unset_bootstrap.sh | 25 + .../oci/scripts/winrm_bootstrap_template.txt | 50 + packer/oci/ubuntu-1804.json | 7 + packer/oci/ubuntu-2004.json | 7 + packer/oci/ubuntu-2204.json | 7 + packer/oci/windows-2019.json | 7 + packer/oci/windows-2022.json | 7 + .../outscale/ci/nightly/overwrite-1-21.json | 7 + .../outscale/ci/nightly/overwrite-1-22.json | 7 + .../outscale/ci/nightly/overwrite-1-23.json | 7 + .../outscale/ci/nightly/overwrite-1-24.json | 7 + .../outscale/ci/nightly/overwrite-1-25.json | 7 + packer/outscale/packer.json | 124 +++ packer/outscale/ubuntu-2004.json | 7 + packer/ova/OWNERS | 4 + packer/ova/centos-7.json | 17 + packer/ova/flatcar.json | 25 + packer/ova/linux/centos/http/7/ks.cfg | 95 ++ packer/ova/linux/centos/http/8/ks.cfg | 75 ++ packer/ova/linux/photon/http/3/ks.json | 60 ++ packer/ova/linux/photon/http/4 | 1 + packer/ova/linux/rhel | 1 + packer/ova/linux/rockylinux/http/8/ks.cfg | 96 ++ .../ova/linux/ubuntu/http/18.04/preseed.cfg | 15 + .../linux/ubuntu/http/20.04/preseed-efi.cfg | 15 + .../ova/linux/ubuntu/http/20.04/preseed.cfg | 15 + packer/ova/linux/ubuntu/http/22.04/meta-data | 0 packer/ova/linux/ubuntu/http/22.04/user-data | 87 ++ .../linux/ubuntu/http/base/preseed-efi.cfg | 128 +++ packer/ova/linux/ubuntu/http/base/preseed.cfg | 128 +++ packer/ova/packer-common.json | 33 + packer/ova/packer-node.json | 485 +++++++++ packer/ova/packer-windows.json | 267 +++++ packer/ova/photon-3.json | 16 + packer/ova/photon-4.json | 16 + packer/ova/rhel-7.json | 18 + packer/ova/rhel-8.json | 19 + packer/ova/rockylinux-8.json | 18 + packer/ova/ubuntu-1804.json | 18 + packer/ova/ubuntu-2004-efi.json | 20 + packer/ova/ubuntu-2004.json | 18 + packer/ova/ubuntu-2204.json | 17 + packer/ova/vmx.json | 3 + packer/ova/vsphere.json | 16 + packer/ova/windows-2004.json | 15 + packer/ova/windows-2019.json | 16 + packer/ova/windows-2022.json | 16 + .../ova/windows/disable-network-discovery.cmd | 2 + packer/ova/windows/disable-winrm.ps1 | 8 + packer/ova/windows/enable-winrm.ps1 | 18 + packer/ova/windows/pvscsi/amd64/pvscsi.cat | Bin 0 -> 10536 bytes packer/ova/windows/pvscsi/amd64/pvscsi.inf | 221 +++++ packer/ova/windows/pvscsi/amd64/pvscsi.sys | Bin 0 -> 60224 bytes packer/ova/windows/pvscsi/amd64/txtsetup.oem | 35 + packer/ova/windows/pvscsi/i386/pvscsi.cat | Bin 0 -> 10163 bytes packer/ova/windows/pvscsi/i386/pvscsi.inf | 221 +++++ packer/ova/windows/pvscsi/i386/pvscsi.sys | Bin 0 -> 55312 bytes packer/ova/windows/pvscsi/i386/txtsetup.oem | 35 + packer/ova/windows/sysprep.ps1 | 13 + .../ova/windows/windows-2004/autounattend.xml | 231 +++++ .../ova/windows/windows-2019/autounattend.xml | 255 +++++ .../ova/windows/windows-2022/autounattend.xml | 256 +++++ packer/powervs/centos-8.json | 9 + packer/powervs/packer.json | 107 ++ packer/qemu/OWNERS | 11 + packer/qemu/README.md | 6 + packer/qemu/linux | 1 + packer/qemu/packer.json | 194 ++++ packer/qemu/qemu-centos-7.json | 15 + packer/qemu/qemu-flatcar.json | 25 + packer/qemu/qemu-rhel-8.json | 15 + packer/qemu/qemu-rockylinux-8.json | 16 + packer/qemu/qemu-ubuntu-1804.json | 12 + packer/qemu/qemu-ubuntu-2004-efi.json | 13 + packer/qemu/qemu-ubuntu-2004.json | 12 + packer/qemu/qemu-ubuntu-2204.json | 12 + packer/qemu/scripts/build_kubevirt_image.sh | 15 + packer/raw/OWNERS | 8 + .../raw/linux/ubuntu/http/18.04/preseed.cfg | 15 + .../linux/ubuntu/http/20.04/preseed-efi.cfg | 15 + .../raw/linux/ubuntu/http/20.04/preseed.cfg | 15 + .../linux/ubuntu/http/base/preseed-efi.cfg | 128 +++ packer/raw/linux/ubuntu/http/base/preseed.cfg | 126 +++ packer/raw/packer.json | 179 ++++ packer/raw/raw-flatcar.json | 24 + packer/raw/raw-ubuntu-1804.json | 13 + packer/raw/raw-ubuntu-2004-efi.json | 14 + packer/raw/raw-ubuntu-2004.json | 13 + packer/vbox/OWNERS | 5 + packer/vbox/packer-common.json | 8 + packer/vbox/packer-windows.json | 125 +++ packer/vbox/vagrantfile-windows_2019.template | 24 + packer/vbox/windows-2019.json | 10 + packer/vbox/windows/enable-winrm.ps1 | 18 + packer/vbox/windows/sysprep.ps1 | 13 + .../windows/windows-2019/autounattend.xml | 250 +++++ scripts/ci-azure-e2e.sh | 122 +++ scripts/ci-container-image.sh | 28 + scripts/ci-disable-goss-inspect.json | 3 + scripts/ci-gce-nightly.sh | 66 ++ scripts/ci-gce.sh | 94 ++ scripts/ci-goss-populate.sh | 45 + scripts/ci-json-sort.sh | 43 + scripts/ci-outscale-nightly.sh | 11 + scripts/ci-ova.sh | 157 +++ scripts/ci-packer-validate.sh | 38 + 429 files changed, 20330 insertions(+) create mode 100644 .drone.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 OWNERS create mode 100644 README.md create mode 100644 ansible.cfg create mode 100644 ansible/.gitignore create mode 100644 ansible/firstboot.yml create mode 100644 ansible/node.yml create mode 100644 ansible/python.yml create mode 100644 ansible/roles/containerd/defaults/main.yml create mode 100644 ansible/roles/containerd/tasks/debian.yml create mode 100644 ansible/roles/containerd/tasks/main.yml create mode 100644 ansible/roles/containerd/tasks/photon.yml create mode 100644 ansible/roles/containerd/tasks/redhat.yml create mode 100644 ansible/roles/containerd/templates/etc/containerd/config.toml create mode 100644 ansible/roles/containerd/templates/etc/crictl.yaml create mode 100644 ansible/roles/containerd/templates/etc/systemd/system/containerd-flatcar.conf create mode 100644 ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/http-proxy.conf create mode 100644 ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/max-tasks.conf create mode 100644 ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/memory-pressure.conf create mode 100644 ansible/roles/firstboot/README.md create mode 120000 ansible/roles/firstboot/defaults create mode 100644 ansible/roles/firstboot/meta/main.yml create mode 100644 ansible/roles/firstboot/tasks/main.yaml create mode 100644 ansible/roles/firstboot/tasks/photon.yml create mode 100644 ansible/roles/firstboot/tasks/qemu.yml create mode 100644 ansible/roles/kubernetes/defaults/main.yml create mode 100644 ansible/roles/kubernetes/tasks/crictl-url.yml create mode 100644 ansible/roles/kubernetes/tasks/debian.yml create mode 100644 ansible/roles/kubernetes/tasks/ecrpull.yml create mode 100644 ansible/roles/kubernetes/tasks/kubeadmpull.yml create mode 100644 ansible/roles/kubernetes/tasks/main.yml create mode 100644 ansible/roles/kubernetes/tasks/photon.yml create mode 100644 ansible/roles/kubernetes/tasks/redhat.yml create mode 100644 ansible/roles/kubernetes/tasks/url.yml create mode 100644 ansible/roles/kubernetes/templates/etc/kubeadm.yml create mode 100644 ansible/roles/kubernetes/templates/etc/kubernetes-version create mode 100644 ansible/roles/kubernetes/templates/etc/sysconfig/kubelet create mode 100644 ansible/roles/kubernetes/templates/etc/yum.repos.d/kubernetes.repo create mode 100644 ansible/roles/kubernetes/templates/usr/lib/systemd/system/kubelet.service create mode 100644 ansible/roles/kubernetes/templates/usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf create mode 100644 ansible/roles/load_additional_components/defaults/main.yml create mode 100644 ansible/roles/load_additional_components/tasks/executables.yml create mode 100644 ansible/roles/load_additional_components/tasks/main.yml create mode 100644 ansible/roles/load_additional_components/tasks/registry.yml create mode 100644 ansible/roles/load_additional_components/tasks/url.yml create mode 100644 ansible/roles/node/defaults/main.yml create mode 100644 ansible/roles/node/files/etc/audit/rules.d/containerd.rules create mode 100644 ansible/roles/node/files/etc/audit/rules.d/containerd.rules-flatcar create mode 100755 ansible/roles/node/files/usr/local/bin/etcd-network-tuning.sh create mode 100644 ansible/roles/node/meta/main.yml create mode 100644 ansible/roles/node/tasks/amazonLinux2.yml create mode 100644 ansible/roles/node/tasks/main.yml create mode 100644 ansible/roles/node/tasks/photon.yml create mode 100644 ansible/roles/node/templates/etc/udev/rules.d/90-etcd-tuning.rules create mode 100644 ansible/roles/providers/defaults/main.yml create mode 100644 ansible/roles/providers/files/etc/azure/iptables create mode 100644 ansible/roles/providers/files/etc/cloud/cloud.cfg.d/05_logging.cfg create mode 100644 ansible/roles/providers/files/etc/cloud/cloud.cfg.d/99_metadata.cfg create mode 100644 ansible/roles/providers/files/etc/networkd-dispatcher/no-carrier.d/20-chrony.j2 create mode 100644 ansible/roles/providers/files/etc/networkd-dispatcher/off.d/20-chrony.j2 create mode 100644 ansible/roles/providers/files/etc/networkd-dispatcher/routable.d/20-chrony.j2 create mode 100644 ansible/roles/providers/files/etc/systemd/system/cloud-config.service.d/boot-order.conf create mode 100644 ansible/roles/providers/files/etc/systemd/system/cloud-final.service.d/boot-order.conf create mode 100644 ansible/roles/providers/files/etc/systemd/system/modify-cloud-init-cfg.service create mode 100644 ansible/roles/providers/files/etc/vmware-tools/tools.conf create mode 100644 ansible/roles/providers/files/tmp/cloud-init_22.2-outscale.deb create mode 100644 ansible/roles/providers/files/usr/lib/python3/dist-packages/cloudinit/feature_overrides.py create mode 100644 ansible/roles/providers/files/usr/libexec/chrony-helper create mode 100644 ansible/roles/providers/files/usr/local/bin/modify-cloud-init-cfg.sh create mode 100644 ansible/roles/providers/tasks/aws.yml create mode 100644 ansible/roles/providers/tasks/azure.yml create mode 100644 ansible/roles/providers/tasks/cloudstack.yml create mode 100644 ansible/roles/providers/tasks/debian.yml create mode 100644 ansible/roles/providers/tasks/googlecompute.yml create mode 100644 ansible/roles/providers/tasks/main.yml create mode 100644 ansible/roles/providers/tasks/nutanix.yml create mode 100644 ansible/roles/providers/tasks/oci.yml create mode 100644 ansible/roles/providers/tasks/outscale.yml create mode 100644 ansible/roles/providers/tasks/qemu.yml create mode 100644 ansible/roles/providers/tasks/raw.yml create mode 100644 ansible/roles/providers/tasks/redhat.yml create mode 100644 ansible/roles/providers/tasks/vmware-photon.yml create mode 100644 ansible/roles/providers/tasks/vmware-redhat.yml create mode 100644 ansible/roles/providers/tasks/vmware-ubuntu.yml create mode 100644 ansible/roles/providers/tasks/vmware.yml create mode 100644 ansible/roles/python/defaults/main.yml create mode 100644 ansible/roles/python/tasks/flatcar.yml create mode 100644 ansible/roles/python/tasks/main.yml create mode 100644 ansible/roles/setup/defaults/main.yml create mode 100755 ansible/roles/setup/files/etc/systemd/system-environment-generators/10-flatcar-path create mode 100644 ansible/roles/setup/files/etc/systemd/system/usr-libexec.mount create mode 100644 ansible/roles/setup/tasks/bootstrap-flatcar.yml create mode 100644 ansible/roles/setup/tasks/debian.yml create mode 100644 ansible/roles/setup/tasks/flatcar.yml create mode 100644 ansible/roles/setup/tasks/main.yml create mode 100644 ansible/roles/setup/tasks/photon.yml create mode 100644 ansible/roles/setup/tasks/redhat.yml create mode 100644 ansible/roles/setup/tasks/rpm_repos.yml create mode 100644 ansible/roles/setup/templates/etc/apt/apt.conf.d/90proxy create mode 100644 ansible/roles/setup/templates/etc/apt/sources.list.j2 create mode 100644 ansible/roles/setup/templates/photon_bash_profile create mode 100644 ansible/roles/sysprep/defaults/main.yml create mode 100644 ansible/roles/sysprep/files/etc/hosts create mode 100644 ansible/roles/sysprep/files/etc/netplan/51-kubevirt-netplan.yaml create mode 100644 ansible/roles/sysprep/tasks/debian.yml create mode 100644 ansible/roles/sysprep/tasks/flatcar.yml create mode 100644 ansible/roles/sysprep/tasks/main.yml create mode 100644 ansible/roles/sysprep/tasks/photon.yml create mode 100644 ansible/roles/sysprep/tasks/redhat.yml create mode 100644 ansible/roles/sysprep/tasks/rpm_repos.yml create mode 100644 ansible/windows/OWNERS create mode 100644 ansible/windows/ansible_winrm.ps1 create mode 100644 ansible/windows/example.vars.yml create mode 100644 ansible/windows/node_windows.yml create mode 100644 ansible/windows/roles/cloudbase-init/tasks/main.yml create mode 100644 ansible/windows/roles/cloudbase-init/templates/cloudbase-init-unattend.conf create mode 100644 ansible/windows/roles/cloudbase-init/templates/cloudbase-init.conf create mode 100644 ansible/windows/roles/debug/defaults/main.yml create mode 100644 ansible/windows/roles/debug/tasks/main.yml create mode 100644 ansible/windows/roles/gmsa/defaults/main.yml create mode 100644 ansible/windows/roles/gmsa/files/install-gmsa-keyvault-plugin.ps1 create mode 100644 ansible/windows/roles/gmsa/tasks/gmsa_keyvault.yml create mode 100644 ansible/windows/roles/gmsa/tasks/main.yml create mode 100644 ansible/windows/roles/kubernetes/defaults/main.yml create mode 100644 ansible/windows/roles/kubernetes/tasks/kubelet.yml create mode 100644 ansible/windows/roles/kubernetes/tasks/main.yml create mode 100644 ansible/windows/roles/kubernetes/tasks/nssm.yml create mode 100644 ansible/windows/roles/kubernetes/tasks/sc.yml create mode 100644 ansible/windows/roles/kubernetes/tasks/url.yml create mode 100644 ansible/windows/roles/kubernetes/tasks/wins.yml create mode 100644 ansible/windows/roles/kubernetes/templates/StartKubelet.ps1 create mode 100644 ansible/windows/roles/load_additional_components/defaults/main.yml create mode 100644 ansible/windows/roles/load_additional_components/tasks/executables.yml create mode 100644 ansible/windows/roles/load_additional_components/tasks/main.yml create mode 100644 ansible/windows/roles/load_additional_components/tasks/registry.yml create mode 100644 ansible/windows/roles/load_additional_components/tasks/url.yml create mode 100644 ansible/windows/roles/providers/defaults/main.yml create mode 100644 ansible/windows/roles/providers/tasks/azure.yml create mode 100644 ansible/windows/roles/providers/tasks/main.yml create mode 100644 ansible/windows/roles/runtimes/defaults/main.yml create mode 100644 ansible/windows/roles/runtimes/tasks/containerd.yml create mode 100644 ansible/windows/roles/runtimes/tasks/docker_ee.yml create mode 100644 ansible/windows/roles/runtimes/tasks/main.yml create mode 100644 ansible/windows/roles/runtimes/templates/config.toml create mode 100644 ansible/windows/roles/systemprep/defaults/main.yml create mode 100644 ansible/windows/roles/systemprep/tasks/main.yml create mode 100644 ansible/windows/roles/systemprep/tasks/ssh-archive.yml create mode 100644 ansible/windows/roles/systemprep/tasks/ssh-feature.yml create mode 100644 azure_targets.sh create mode 100644 cloudinit/.gitignore create mode 100644 cloudinit/Makefile create mode 100644 cloudinit/README.md create mode 100644 cloudinit/ca.crt create mode 100644 cloudinit/ca.key create mode 100644 cloudinit/id_rsa.capi create mode 100644 cloudinit/id_rsa.capi.pub create mode 100644 cloudinit/meta-data create mode 100644 cloudinit/user-data create mode 100755 hack/boxes-flatcar.sh create mode 100755 hack/convert-cloudstack-image.sh create mode 100755 hack/ensure-ansible-windows.sh create mode 100755 hack/ensure-ansible.sh create mode 100755 hack/ensure-azure-cli.sh create mode 100755 hack/ensure-boskosctl.sh create mode 100755 hack/ensure-ct.sh create mode 100755 hack/ensure-go.sh create mode 100755 hack/ensure-goss.sh create mode 100755 hack/ensure-jq.sh create mode 100755 hack/ensure-ovftool.sh create mode 100755 hack/ensure-packer.sh create mode 100755 hack/ensure-powervs.sh create mode 100755 hack/ensure-vhdutil.sh create mode 100755 hack/generate-goss-specs.py create mode 100755 hack/image-build-flatcar.sh create mode 100755 hack/image-build-ova.py create mode 100755 hack/image-govc-cloudinit.sh create mode 100755 hack/image-grok-latest-flatcar-version.sh create mode 100755 hack/image-new-kube.py create mode 100755 hack/image-post-create-config.sh create mode 100755 hack/image-ssh.sh create mode 100755 hack/image-upload.py create mode 100644 hack/ovf_eula.txt create mode 100644 hack/ovf_template.xml create mode 100755 hack/utils.sh create mode 100755 hack/windows-ova-unattend.py create mode 100644 kubevirt-Dockerfile create mode 100644 packer/.gitignore create mode 100644 packer/ami/OWNERS create mode 100644 packer/ami/amazon-2.json create mode 100644 packer/ami/centos-7.json create mode 100644 packer/ami/flatcar.json create mode 100644 packer/ami/packer-windows.json create mode 100644 packer/ami/packer.json create mode 100644 packer/ami/rhel-8.json create mode 100644 packer/ami/rockylinux-8.json create mode 100644 packer/ami/scripts/sysprep_prerequisites.ps1 create mode 100644 packer/ami/scripts/winrm_bootstrap.txt create mode 100644 packer/ami/ubuntu-1804.json create mode 100644 packer/ami/ubuntu-2004.json create mode 100644 packer/ami/ubuntu-2204.json create mode 100644 packer/ami/windows-2004.json create mode 100644 packer/ami/windows-2019.json create mode 100644 packer/azure/.pipelines/build-vhd.yaml create mode 100644 packer/azure/.pipelines/create-disk-version.yaml create mode 100644 packer/azure/.pipelines/create-sku.yaml create mode 100644 packer/azure/.pipelines/delete-storage-account.yaml create mode 100644 packer/azure/.pipelines/generate-sas.yaml create mode 100644 packer/azure/.pipelines/k8s-config.yaml create mode 100644 packer/azure/.pipelines/smoke-test.yaml create mode 100644 packer/azure/.pipelines/stages.yaml create mode 100644 packer/azure/.pipelines/test-vhd.yaml create mode 100644 packer/azure/.pipelines/vhd-publishing-info.yaml create mode 100644 packer/azure/OWNERS create mode 100644 packer/azure/azure-config.json create mode 100644 packer/azure/azure-sig-gen2.json create mode 100644 packer/azure/azure-sig.json create mode 100644 packer/azure/azure-vhd.json create mode 100644 packer/azure/centos-7-gen2.json create mode 100644 packer/azure/centos-7.json create mode 100644 packer/azure/flatcar-gen2.json create mode 100644 packer/azure/flatcar.json create mode 100644 packer/azure/packer-windows.json create mode 100644 packer/azure/packer.json create mode 100644 packer/azure/rhel-8.json create mode 100755 packer/azure/scripts/delete-unused-storage.sh create mode 100644 packer/azure/scripts/disable-windows-prepull.json create mode 100755 packer/azure/scripts/ensure-kustomize.sh create mode 100755 packer/azure/scripts/init-sig.sh create mode 100755 packer/azure/scripts/init-vhd.sh create mode 100755 packer/azure/scripts/new-disk-version.sh create mode 100755 packer/azure/scripts/new-sku.sh create mode 100755 packer/azure/scripts/parse-prow-creds.sh create mode 100644 packer/azure/scripts/sysprep.ps1 create mode 100644 packer/azure/scripts/test-templates/linux/kustomization.yaml create mode 100644 packer/azure/scripts/test-templates/patches/azuremachinetemplate-controlplane.yaml create mode 100644 packer/azure/scripts/test-templates/patches/azuremachinetemplate-windows.yaml create mode 100644 packer/azure/scripts/test-templates/patches/azuremachinetemplate-workload.yaml create mode 100644 packer/azure/scripts/test-templates/patches/kubeadmcontrolplane-windows.yaml create mode 100644 packer/azure/scripts/test-templates/patches/machinedeployment-windows.yaml create mode 100644 packer/azure/scripts/test-templates/windows/kustomization.yaml create mode 100644 packer/azure/sku-template.json create mode 100644 packer/azure/ubuntu-1804-gen2.json create mode 100644 packer/azure/ubuntu-1804.json create mode 100644 packer/azure/ubuntu-2004-gen2.json create mode 100644 packer/azure/ubuntu-2004.json create mode 100644 packer/azure/ubuntu-2204-gen2.json create mode 100644 packer/azure/ubuntu-2204.json create mode 100644 packer/azure/windows-2004.json create mode 100644 packer/azure/windows-2019-containerd.json create mode 100644 packer/azure/windows-2019.json create mode 100644 packer/azure/windows-2022-containerd.json create mode 100644 packer/config/additional_components.json create mode 100644 packer/config/ansible-args.json create mode 100644 packer/config/cni.json create mode 100644 packer/config/common.json create mode 100644 packer/config/containerd.json create mode 100644 packer/config/goss-args.json create mode 100644 packer/config/kubernetes.json create mode 100644 packer/config/ppc64le/cni.json create mode 100644 packer/config/ppc64le/common.json create mode 100644 packer/config/ppc64le/containerd.json create mode 100644 packer/config/ppc64le/kubernetes.json create mode 100644 packer/config/wasm-shims.json create mode 100644 packer/config/windows/OWNERS create mode 100644 packer/config/windows/ansible-args-windows.json create mode 100644 packer/config/windows/cloudbase-init.json create mode 100644 packer/config/windows/common.json create mode 100644 packer/config/windows/containerd.json create mode 100644 packer/config/windows/docker.json create mode 100644 packer/config/windows/kubernetes.json create mode 100644 packer/digitalocean/OWNERS create mode 100644 packer/digitalocean/centos-7.json create mode 100644 packer/digitalocean/packer.json create mode 100644 packer/digitalocean/ubuntu-1804.json create mode 100644 packer/digitalocean/ubuntu-2004.json create mode 100644 packer/files/flatcar/README.md create mode 100644 packer/files/flatcar/clc/bootstrap.yaml create mode 100644 packer/files/flatcar/ignition/bootstrap.json create mode 100644 packer/files/flatcar/scripts/bootstrap-flatcar.sh create mode 100644 packer/gce/OWNERS create mode 100644 packer/gce/ci/nightly/README.md create mode 100644 packer/gce/ci/nightly/overwrite-1-23.json create mode 100644 packer/gce/ci/nightly/overwrite-1-24.json create mode 100644 packer/gce/ci/nightly/overwrite-1-25.json create mode 100644 packer/gce/ci/nightly/overwrite-1-26.json create mode 100644 packer/gce/packer.json create mode 100644 packer/gce/ubuntu-1804.json create mode 100644 packer/gce/ubuntu-2004.json create mode 100644 packer/gce/ubuntu-2204.json create mode 100644 packer/goss/goss-command.yaml create mode 100644 packer/goss/goss-files.yaml create mode 100644 packer/goss/goss-kernel-params.yaml create mode 100644 packer/goss/goss-package.yaml create mode 100644 packer/goss/goss-service.yaml create mode 100644 packer/goss/goss-vars.yaml create mode 100644 packer/goss/goss.yaml create mode 100644 packer/nutanix/OWNERS create mode 100644 packer/nutanix/config.pkr.hcl create mode 100644 packer/nutanix/flatcar.json create mode 100644 packer/nutanix/nutanix.json create mode 100644 packer/nutanix/packer-windows.json create mode 100644 packer/nutanix/packer.json create mode 100644 packer/nutanix/rockylinux-8.json create mode 100644 packer/nutanix/rockylinux-9.json create mode 100644 packer/nutanix/ubuntu-2004.json create mode 100644 packer/nutanix/ubuntu-2204.json create mode 100644 packer/nutanix/windows-2022.json create mode 100644 packer/nutanix/windows/disable-network-discovery.cmd create mode 100644 packer/nutanix/windows/sysprep.ps1 create mode 100644 packer/nutanix/windows/windows-2022/autounattend.xml create mode 100644 packer/oci/oracle-linux-8.json create mode 100644 packer/oci/oracle-linux-9.json create mode 100644 packer/oci/packer-windows.json create mode 100644 packer/oci/packer.json create mode 100644 packer/oci/scripts/attach_secondary_vnic.ps1 create mode 100644 packer/oci/scripts/enable_second_nic.ps1 create mode 100755 packer/oci/scripts/set_bootstrap.sh create mode 100644 packer/oci/scripts/sysprep.ps1 create mode 100755 packer/oci/scripts/unset_bootstrap.sh create mode 100644 packer/oci/scripts/winrm_bootstrap_template.txt create mode 100644 packer/oci/ubuntu-1804.json create mode 100644 packer/oci/ubuntu-2004.json create mode 100644 packer/oci/ubuntu-2204.json create mode 100644 packer/oci/windows-2019.json create mode 100644 packer/oci/windows-2022.json create mode 100644 packer/outscale/ci/nightly/overwrite-1-21.json create mode 100644 packer/outscale/ci/nightly/overwrite-1-22.json create mode 100644 packer/outscale/ci/nightly/overwrite-1-23.json create mode 100644 packer/outscale/ci/nightly/overwrite-1-24.json create mode 100644 packer/outscale/ci/nightly/overwrite-1-25.json create mode 100644 packer/outscale/packer.json create mode 100644 packer/outscale/ubuntu-2004.json create mode 100644 packer/ova/OWNERS create mode 100644 packer/ova/centos-7.json create mode 100644 packer/ova/flatcar.json create mode 100644 packer/ova/linux/centos/http/7/ks.cfg create mode 100644 packer/ova/linux/centos/http/8/ks.cfg create mode 100644 packer/ova/linux/photon/http/3/ks.json create mode 120000 packer/ova/linux/photon/http/4 create mode 120000 packer/ova/linux/rhel create mode 100644 packer/ova/linux/rockylinux/http/8/ks.cfg create mode 100644 packer/ova/linux/ubuntu/http/18.04/preseed.cfg create mode 100644 packer/ova/linux/ubuntu/http/20.04/preseed-efi.cfg create mode 100644 packer/ova/linux/ubuntu/http/20.04/preseed.cfg create mode 100644 packer/ova/linux/ubuntu/http/22.04/meta-data create mode 100644 packer/ova/linux/ubuntu/http/22.04/user-data create mode 100644 packer/ova/linux/ubuntu/http/base/preseed-efi.cfg create mode 100644 packer/ova/linux/ubuntu/http/base/preseed.cfg create mode 100644 packer/ova/packer-common.json create mode 100644 packer/ova/packer-node.json create mode 100644 packer/ova/packer-windows.json create mode 100644 packer/ova/photon-3.json create mode 100644 packer/ova/photon-4.json create mode 100644 packer/ova/rhel-7.json create mode 100644 packer/ova/rhel-8.json create mode 100644 packer/ova/rockylinux-8.json create mode 100644 packer/ova/ubuntu-1804.json create mode 100644 packer/ova/ubuntu-2004-efi.json create mode 100644 packer/ova/ubuntu-2004.json create mode 100644 packer/ova/ubuntu-2204.json create mode 100644 packer/ova/vmx.json create mode 100644 packer/ova/vsphere.json create mode 100644 packer/ova/windows-2004.json create mode 100644 packer/ova/windows-2019.json create mode 100644 packer/ova/windows-2022.json create mode 100644 packer/ova/windows/disable-network-discovery.cmd create mode 100644 packer/ova/windows/disable-winrm.ps1 create mode 100644 packer/ova/windows/enable-winrm.ps1 create mode 100644 packer/ova/windows/pvscsi/amd64/pvscsi.cat create mode 100644 packer/ova/windows/pvscsi/amd64/pvscsi.inf create mode 100644 packer/ova/windows/pvscsi/amd64/pvscsi.sys create mode 100644 packer/ova/windows/pvscsi/amd64/txtsetup.oem create mode 100644 packer/ova/windows/pvscsi/i386/pvscsi.cat create mode 100644 packer/ova/windows/pvscsi/i386/pvscsi.inf create mode 100644 packer/ova/windows/pvscsi/i386/pvscsi.sys create mode 100644 packer/ova/windows/pvscsi/i386/txtsetup.oem create mode 100644 packer/ova/windows/sysprep.ps1 create mode 100644 packer/ova/windows/windows-2004/autounattend.xml create mode 100644 packer/ova/windows/windows-2019/autounattend.xml create mode 100644 packer/ova/windows/windows-2022/autounattend.xml create mode 100644 packer/powervs/centos-8.json create mode 100644 packer/powervs/packer.json create mode 100644 packer/qemu/OWNERS create mode 100644 packer/qemu/README.md create mode 120000 packer/qemu/linux create mode 100644 packer/qemu/packer.json create mode 100644 packer/qemu/qemu-centos-7.json create mode 100644 packer/qemu/qemu-flatcar.json create mode 100644 packer/qemu/qemu-rhel-8.json create mode 100644 packer/qemu/qemu-rockylinux-8.json create mode 100644 packer/qemu/qemu-ubuntu-1804.json create mode 100644 packer/qemu/qemu-ubuntu-2004-efi.json create mode 100644 packer/qemu/qemu-ubuntu-2004.json create mode 100644 packer/qemu/qemu-ubuntu-2204.json create mode 100644 packer/qemu/scripts/build_kubevirt_image.sh create mode 100644 packer/raw/OWNERS create mode 100644 packer/raw/linux/ubuntu/http/18.04/preseed.cfg create mode 100644 packer/raw/linux/ubuntu/http/20.04/preseed-efi.cfg create mode 100644 packer/raw/linux/ubuntu/http/20.04/preseed.cfg create mode 100644 packer/raw/linux/ubuntu/http/base/preseed-efi.cfg create mode 100644 packer/raw/linux/ubuntu/http/base/preseed.cfg create mode 100644 packer/raw/packer.json create mode 100644 packer/raw/raw-flatcar.json create mode 100644 packer/raw/raw-ubuntu-1804.json create mode 100644 packer/raw/raw-ubuntu-2004-efi.json create mode 100644 packer/raw/raw-ubuntu-2004.json create mode 100644 packer/vbox/OWNERS create mode 100644 packer/vbox/packer-common.json create mode 100644 packer/vbox/packer-windows.json create mode 100644 packer/vbox/vagrantfile-windows_2019.template create mode 100644 packer/vbox/windows-2019.json create mode 100644 packer/vbox/windows/enable-winrm.ps1 create mode 100644 packer/vbox/windows/sysprep.ps1 create mode 100644 packer/vbox/windows/windows-2019/autounattend.xml create mode 100755 scripts/ci-azure-e2e.sh create mode 100755 scripts/ci-container-image.sh create mode 100644 scripts/ci-disable-goss-inspect.json create mode 100755 scripts/ci-gce-nightly.sh create mode 100755 scripts/ci-gce.sh create mode 100755 scripts/ci-goss-populate.sh create mode 100755 scripts/ci-json-sort.sh create mode 100755 scripts/ci-outscale-nightly.sh create mode 100755 scripts/ci-ova.sh create mode 100755 scripts/ci-packer-validate.sh diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..898ea09 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,31 @@ +kind: pipeline +type: kubernetes +name: 'CAPI ImageBuilder' + +volumes: +- name: output + claim: + name: flexvolsmb-drone-output +- name: scratch + claim: + name: flexvolsmb-drone-scratch + +steps: +- name: Debugging information + image: bv11-cr01.bessems.eu/library/packer-extended + commands: + - ansible --version + - ovftool --version + - packer --version + - yamllint --version +- name: Build CAPV image(s) + image: bv11-cr01.bessems.eu/library/packer-extended + pull: always + commands: + - | + make deps + + + volumes: + - name: output + path: /output diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..ef5b8bb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Tell us about a problem you are experiencing + +--- + +**What steps did you take and what happened:** +[A clear and concise description on how to REPRODUCE the bug.] + + +**What did you expect to happen:** + + +**Anything else you would like to add:** +[Miscellaneous information that will assist in solving the issue.] + + +**Environment:** + +Project ([Image Builder for Cluster API](https://github.com/kubernetes-sigs/image-builder/tree/master/images/capi), [kube-deploy/imagebuilder](https://github.com/kubernetes-sigs/image-builder/tree/master/images/kube-deploy/imagebuilder), [konfigadm](https://github.com/kubernetes-sigs/image-builder/tree/master/images/konfigadm)): + +Additional info for Image Builder for Cluster API related issues: + +- OS (e.g. from `/etc/os-release`, or `cmd /c ver`): +- Packer Version: +- Packer Provider: +- Ansible Version: +- Cluster-api version (if using): +- Kubernetes version: (use `kubectl version`): + +/kind bug +[One or more /area label. See https://github.com/kubernetes-sigs/cluster-api/labels?q=area for the list of labels] + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..3669dbf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. + +/kind feature + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..cf21d55 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ +What this PR does / why we need it: + +Which issue(s) this PR fixes (optional, in fixes #(, fixes #, ...) format, will close the issue(s) when PR gets merged): Fixes # + +**Additional context** +Add any other context for the reviewers \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..32fa9a3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,46 @@ +# syntax=docker/dockerfile:1.1-experimental + +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ARG BASE_IMAGE=docker.io/library/ubuntu:latest +FROM $BASE_IMAGE + +RUN apt-get update && apt-get install -y apt-transport-https ca-certificates python3-pip curl wget git rsync vim unzip build-essential \ + && useradd -ms /bin/bash imagebuilder \ + && apt-get purge --auto-remove -y \ + && rm -rf /var/lib/apt/lists/* + +ARG ARCH +ARG PASSED_IB_VERSION + +USER imagebuilder +WORKDIR /home/imagebuilder/ + +COPY --chown=imagebuilder:imagebuilder ansible ansible/ +COPY --chown=imagebuilder:imagebuilder ansible.cfg ansible.cfg +COPY --chown=imagebuilder:imagebuilder cloudinit cloudinit/ +COPY --chown=imagebuilder:imagebuilder hack hack/ +COPY --chown=imagebuilder:imagebuilder packer packer/ +COPY --chown=imagebuilder:imagebuilder Makefile Makefile +COPY --chown=imagebuilder:imagebuilder azure_targets.sh azure_targets.sh + +ENV PATH="/home/imagebuilder/.local/bin:${PATH}" +ENV PACKER_ARGS '' +ENV PACKER_VAR_FILES '' +ENV IB_VERSION "${PASSED_IB_VERSION}" + +RUN make deps + +ENTRYPOINT [ "/usr/bin/make" ] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..89db5d3 --- /dev/null +++ b/Makefile @@ -0,0 +1,938 @@ +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# If you update this file, please follow +# https://suva.sh/posts/well-documented-makefiles + +# Ensure Make is run with bash shell as some syntax below is bash-specific +SHELL := /usr/bin/env bash + +.DEFAULT_GOAL := help + +# This option is for running docker manifest command +export DOCKER_CLI_EXPERIMENTAL := enabled +export PATH := $(PATH):$(CURDIR)/.local/bin + +export IB_VERSION ?= $(shell git describe --dirty) + +## -------------------------------------- +## Help +## -------------------------------------- +##@ Helpers +help: ## Display this help + @echo NOTE + @echo ' The "build-node-ova" targets have analogue "clean-node-ova" targets for' + @echo ' cleaning artifacts created from building OVAs using a local' + @echo ' hypervisor.' + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-35s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +.PHONY: version +version: ## Display version of image-builder + @echo $(IB_VERSION) + +## -------------------------------------- +## Dependencies +## -------------------------------------- +##@ Dependencies + +.PHONY: deps +deps: ## Installs/checks all dependencies +deps: deps-ami deps-azure deps-do deps-gce deps-ova deps-qemu deps-raw deps-oci deps-osc deps-vbox deps-powervs deps-nutanix + +.PHONY: deps-ami +deps-ami: ## Installs/checks dependencies for AMI builds +deps-ami: + hack/ensure-ansible.sh + hack/ensure-ansible-windows.sh + hack/ensure-packer.sh + hack/ensure-goss.sh + +.PHONY: deps-azure +deps-azure: ## Installs/checks dependencies for Azure builds +deps-azure: + hack/ensure-ansible.sh + hack/ensure-ansible-windows.sh + hack/ensure-packer.sh + hack/ensure-jq.sh + hack/ensure-azure-cli.sh + hack/ensure-goss.sh + +.PHONY: deps-do +deps-do: ## Installs/checks dependencies for DigitalOcean builds +deps-do: + hack/ensure-ansible.sh + hack/ensure-packer.sh + +.PHONY: deps-osc +deps-osc: ## Installs/checks dependencies for Outscale builds +deps-osc: + hack/ensure-ansible.sh + hack/ensure-packer.sh + hack/ensure-goss.sh + packer plugins install github.com/outscale/outscale + +.PHONY: deps-gce +deps-gce: ## Installs/checks dependencies for GCE builds +deps-gce: + hack/ensure-ansible.sh + hack/ensure-packer.sh + hack/ensure-goss.sh + +.PHONY: deps-ova +deps-ova: ## Installs/checks dependencies for OVA builds +deps-ova: + hack/ensure-ansible.sh + hack/ensure-ansible-windows.sh + hack/ensure-packer.sh + hack/ensure-goss.sh + hack/ensure-ovftool.sh + +.PHONY: deps-qemu +deps-qemu: ## Installs/checks dependencies for QEMU builds +deps-qemu: + hack/ensure-ansible.sh + hack/ensure-packer.sh + hack/ensure-goss.sh + +.PHONY: deps-raw +deps-raw: ## Installs/checks dependencies for RAW builds +deps-raw: + hack/ensure-ansible.sh + hack/ensure-packer.sh + hack/ensure-goss.sh + +.PHONY: deps-oci +deps-oci: ## Installs/checks dependencies for OCI builds +deps-oci: + hack/ensure-ansible.sh + hack/ensure-packer.sh + packer plugins install github.com/hashicorp/oracle + +.PHONY: deps-vbox +deps-vbox: ## Installs/checks dependencies for VirtualBox builds +deps-vbox: + hack/ensure-ansible.sh + hack/ensure-ansible-windows.sh + hack/ensure-packer.sh + hack/ensure-goss.sh + +.PHONY: deps-powervs +deps-powervs: +deps-powervs: + hack/ensure-ansible.sh + hack/ensure-packer.sh + hack/ensure-goss.sh + hack/ensure-powervs.sh + +.PHONY: deps-ignition +deps-ignition: ## Installs/checks dependencies for generating Ignition files +deps-ignition: + hack/ensure-jq.sh + hack/ensure-ct.sh + +.PHONY: deps-nutanix +deps-nutanix: ## Installs/checks dependencies for Nutanix builds +deps-nutanix: + hack/ensure-ansible.sh + hack/ensure-packer.sh + hack/ensure-goss.sh + +## -------------------------------------- +## Container variables +## -------------------------------------- +REGISTRY ?= gcr.io/$(shell gcloud config get-value project) +STAGING_REGISTRY := gcr.io/k8s-staging-scl-image-builder +IMAGE_NAME ?= cluster-node-image-builder +CONTROLLER_IMG ?= $(REGISTRY)/$(IMAGE_NAME) +TAG ?= dev +ARCH ?= amd64 +BASE_IMAGE ?= docker.io/library/ubuntu:focal + +## -------------------------------------- +## Packer flags +## -------------------------------------- + +# Set Packer color to true if not already set in env variables +# Only valid for builds +ifneq (,$(findstring build-, $(MAKECMDGOALS))) + # A build target + PACKER_COLOR ?= true + PACKER_FLAGS += -color=$(PACKER_COLOR) +endif + +# If FOREGROUND=1 then Packer will set headless to false, causing local builds +# to build in the foreground, with a UI. This is very useful when debugging new +# platforms or issues with existing ones. +ifeq (1,$(strip $(FOREGROUND))) +PACKER_FLAGS += -var="headless=false" +endif + +# If ON_ERROR_ASK=1 then Packer will set -on-error to ask, causing the Packer +# build to pause when any error happens, instead of simply exiting. This is +# useful when debugging unknown issues logging into the remote machine via ssh. +ifeq (1,$(strip $(ON_ERROR_ASK))) +PACKER_FLAGS += -on-error=ask +endif + +# ssh_private_key_file and ssh_public_key are needed to pass ssh keypair +# from its host to the packer guest machine, so boot managers like ignition +# could make use of the key in its config. +# SSH_PRIVATE_KEY_FILE is name of the file that contains the private key. +# SSH_PUBLIC_KEY_FILE is name of the file that contains the public key. +ifneq (,$(strip $(SSH_PRIVATE_KEY_FILE))) +PACKER_FLAGS += -var ssh_private_key_file="$(SSH_PRIVATE_KEY_FILE)" +endif + +ifneq (,$(strip $(SSH_PUBLIC_KEY_FILE))) +PACKER_FLAGS += -var ssh_public_key="$(shell cat ${SSH_PUBLIC_KEY_FILE})" +endif + +# Since OpenSSH 9.0+ 'scp' uses SFTP protocol instead of legacy SCP protocol, which causes building errors like: +# +# bash: line 1: /usr/lib/sftp-server: No such file or directory\nscp: Connection closed\r\n"" +# +# However, -O option is not available in older OpenSSH version, so we cannot always set it as an option to use. +# To provide better out-of-the-box experience for users with newer versions of OpenSSH, we conditionally ensure +# -O is used when used OpenSSH version requires it. +# +# See https://github.com/kubernetes-sigs/image-builder/issues/859 and +# https://github.com/hashicorp/packer-plugin-ansible/issues/100 for more details. +ifeq ($(shell test $$(ssh -V 2>&1 | cut -d _ -f2 | cut -d . -f1) -ge 9; echo $$?),0) + # Use ?= to retain possible existing value of environment variable. If it is already declared, we assume user to be + # aware of OpenSSH version they use and it is up to the user to specify "-O" option as well if needed. + export ANSIBLE_SCP_EXTRA_ARGS ?= "-O" +endif + +# If DEBUG=1 then Packer will set -debug, enabling debug mode for builds, providing +# more verbose logging +ifeq (1,$(strip $(DEBUG))) +PACKER_FLAGS += -debug +endif + +# We want the var files passed to Packer to have a specific order, because the +# precenence of the variables they contain depends on the order. Files listed +# later on the CLI have higher precedence. We want the common var files found in +# packer/config to be listed first, then the var files that specific to the +# provider, then any user-supplied var files so that a user can override what +# they need to. + +# A list of variable files given to Packer to configure things like the versions +# of Kubernetes, CNI, and ContainerD to install. Any additional files from the +# environment are appended. +COMMON_NODE_VAR_FILES := packer/config/kubernetes.json \ + packer/config/cni.json \ + packer/config/containerd.json \ + packer/config/wasm-shims.json \ + packer/config/ansible-args.json \ + packer/config/goss-args.json \ + packer/config/common.json \ + packer/config/additional_components.json + +COMMON_WINDOWS_VAR_FILES := packer/config/kubernetes.json \ + packer/config/windows/kubernetes.json \ + packer/config/containerd.json \ + packer/config/windows/containerd.json \ + packer/config/windows/docker.json \ + packer/config/windows/ansible-args-windows.json \ + packer/config/common.json \ + packer/config/windows/common.json \ + packer/config/windows/cloudbase-init.json \ + packer/config/goss-args.json \ + packer/config/additional_components.json + +COMMON_POWERVS_VAR_FILES := packer/config/kubernetes.json \ + packer/config/ppc64le/kubernetes.json \ + packer/config/cni.json \ + packer/config/ppc64le/cni.json \ + packer/config/containerd.json \ + packer/config/ppc64le/containerd.json \ + packer/config/ansible-args.json \ + packer/config/goss-args.json \ + packer/config/common.json \ + packer/config/ppc64le/common.json \ + packer/config/additional_components.json + +# Initialize a list of flags to pass to Packer. This includes any existing flags +# specified by PACKER_FLAGS, as well as prefixing the list with the variable +# files from COMMON_VAR_FILES, with each file prefixed by -var-file=. +# +# Any existing values from PACKER_FLAGS take precendence over variable files. +PACKER_NODE_FLAGS := $(foreach f,$(abspath $(COMMON_NODE_VAR_FILES)),-var-file="$(f)" ) \ + $(PACKER_FLAGS) +ABSOLUTE_PACKER_VAR_FILES := $(foreach f,$(abspath $(PACKER_VAR_FILES)),-var-file="$(f)" ) +PACKER_WINDOWS_NODE_FLAGS := $(foreach f,$(abspath $(COMMON_WINDOWS_VAR_FILES)),-var-file="$(f)" ) \ + $(PACKER_FLAGS) +PACKER_POWERVS_NODE_FLAGS := $(foreach f,$(abspath $(COMMON_POWERVS_VAR_FILES)),-var-file="$(f)" ) \ + $(PACKER_FLAGS) + +## -------------------------------------- +## Platform and version combinations +## -------------------------------------- +CENTOS_VERSIONS := centos-7 +FLATCAR_VERSIONS := flatcar +PHOTON_VERSIONS := photon-3 photon-4 +RHEL_VERSIONS := rhel-7 rhel-8 +ROCKYLINUX_VERSIONS := rockylinux-8 +UBUNTU_VERSIONS := ubuntu-1804 ubuntu-2004 ubuntu-2004-efi ubuntu-2204 +WINDOWS_VERSIONS := windows-2019 windows-2004 windows-2022 + +# Set Flatcar Container Linux channel and version if not supplied +FLATCAR_CHANNEL ?= stable +FLATCAR_VERSION ?= current +ifeq ($(FLATCAR_VERSION),current) +override FLATCAR_VERSION := $(shell hack/image-grok-latest-flatcar-version.sh $(FLATCAR_CHANNEL)) +endif + +export FLATCAR_CHANNEL FLATCAR_VERSION + +PLATFORMS_AND_VERSIONS := $(CENTOS_VERSIONS) \ + $(PHOTON_VERSIONS) \ + $(RHEL_VERSIONS) \ + $(ROCKYLINUX_VERSIONS) \ + $(UBUNTU_VERSIONS) \ + $(FLATCAR_VERSIONS) \ + $(WINDOWS_VERSIONS) + +NODE_OVA_LOCAL_BUILD_NAMES := $(addprefix node-ova-local-,$(PLATFORMS_AND_VERSIONS)) +NODE_OVA_LOCAL_VMX_BUILD_NAMES := $(addprefix node-ova-local-vmx-,$(PLATFORMS_AND_VERSIONS)) +NODE_OVA_LOCAL_BASE_BUILD_NAMES := $(addprefix node-ova-local-base-,$(PLATFORMS_AND_VERSIONS)) +NODE_OVA_VSPHERE_BUILD_NAMES := $(addprefix node-ova-vsphere-,$(PLATFORMS_AND_VERSIONS)) +NODE_OVA_VSPHERE_BASE_BUILD_NAMES := $(addprefix node-ova-vsphere-base-,$(PLATFORMS_AND_VERSIONS)) +NODE_OVA_VSPHERE_CLONE_BUILD_NAMES := $(addprefix node-ova-vsphere-clone-,$(PLATFORMS_AND_VERSIONS)) + +AMI_BUILD_NAMES ?= ami-centos-7 ami-ubuntu-1804 ami-ubuntu-2004 ami-ubuntu-2204 ami-amazon-2 ami-flatcar ami-windows-2019 ami-windows-2004 ami-rockylinux-8 ami-rhel-8 +GCE_BUILD_NAMES ?= gce-ubuntu-1804 gce-ubuntu-2004 gce-ubuntu-2204 + +# Make needs these lists to be space delimited, no quotes +VHD_TARGETS := $(shell grep VHD_TARGETS azure_targets.sh | sed 's/VHD_TARGETS=//' | tr -d \") +SIG_TARGETS := $(shell grep SIG_TARGETS azure_targets.sh | sed 's/SIG_TARGETS=//' | tr -d \") +SIG_GEN2_TARGETS := $(shell grep SIG_GEN2_TARGETS azure_targets.sh | sed 's/SIG_GEN2_TARGETS=//' | tr -d \") +AZURE_BUILD_VHD_NAMES ?= $(addprefix azure-vhd-,$(VHD_TARGETS)) +AZURE_BUILD_SIG_NAMES ?= $(addprefix azure-sig-,$(SIG_TARGETS)) +AZURE_BUILD_SIG_GEN2_NAMES ?= $(addsuffix -gen2,$(addprefix azure-sig-,$(SIG_GEN2_TARGETS))) + +OCI_BUILD_NAMES ?= oci-ubuntu-1804 oci-ubuntu-2004 oci-ubuntu-2204 oci-oracle-linux-8 oci-oracle-linux-9 oci-windows-2019 oci-windows-2022 + +DO_BUILD_NAMES ?= do-centos-7 do-ubuntu-1804 do-ubuntu-2004 + +OSC_BUILD_NAMES ?= osc-ubuntu-2004 + +QEMU_BUILD_NAMES ?= qemu-ubuntu-1804 qemu-ubuntu-2004 qemu-ubuntu-2204 qemu-centos-7 qemu-ubuntu-2004-efi qemu-rhel-8 qemu-rockylinux-8 qemu-flatcar +QEMU_KUBEVIRT_BUILD_NAMES := $(addprefix kubevirt-,$(QEMU_BUILD_NAMES)) + +RAW_BUILD_NAMES ?= raw-ubuntu-1804 raw-ubuntu-2004 raw-ubuntu-2004-efi raw-flatcar +VBOX_BUILD_NAMES ?= vbox-windows-2019 + +POWERVS_BUILD_NAMES ?= powervs-centos-8 + +NUTANIX_BUILD_NAMES ?= nutanix-ubuntu-2004 nutanix-ubuntu-2204 nutanix-rockylinux-8 nutanix-rockylinux-9 nutanix-flatcar nutanix-windows-2022 + +## -------------------------------------- +## Dynamic build targets +## -------------------------------------- +NODE_OVA_LOCAL_BUILD_TARGETS := $(addprefix build-,$(NODE_OVA_LOCAL_BUILD_NAMES)) +NODE_OVA_LOCAL_VMX_BUILD_TARGETS := $(addprefix build-,$(NODE_OVA_LOCAL_VMX_BUILD_NAMES)) +NODE_OVA_LOCAL_BASE_BUILD_TARGETS := $(addprefix build-,$(NODE_OVA_LOCAL_BASE_BUILD_NAMES)) +NODE_OVA_LOCAL_VALIDATE_TARGETS := $(addprefix validate-,$(NODE_OVA_LOCAL_BUILD_NAMES)) +NODE_OVA_VSPHERE_BUILD_TARGETS := $(addprefix build-,$(NODE_OVA_VSPHERE_BUILD_NAMES)) +NODE_OVA_VSPHERE_BASE_BUILD_TARGETS := $(addprefix build-,$(NODE_OVA_VSPHERE_BASE_BUILD_NAMES)) +NODE_OVA_VSPHERE_CLONE_BUILD_TARGETS := $(addprefix build-,$(NODE_OVA_VSPHERE_CLONE_BUILD_NAMES)) +AMI_BUILD_TARGETS := $(addprefix build-,$(AMI_BUILD_NAMES)) +AMI_VALIDATE_TARGETS := $(addprefix validate-,$(AMI_BUILD_NAMES)) +GCE_BUILD_TARGETS := $(addprefix build-,$(GCE_BUILD_NAMES)) +GCE_VALIDATE_TARGETS := $(addprefix validate-,$(GCE_BUILD_NAMES)) +AZURE_BUILD_VHD_TARGETS := $(addprefix build-,$(AZURE_BUILD_VHD_NAMES)) +AZURE_VALIDATE_VHD_TARGETS := $(addprefix validate-,$(AZURE_BUILD_VHD_NAMES)) +AZURE_BUILD_SIG_TARGETS := $(addprefix build-,$(AZURE_BUILD_SIG_NAMES)) +AZURE_BUILD_SIG_GEN2_TARGETS := $(addprefix build-,$(AZURE_BUILD_SIG_GEN2_NAMES)) +AZURE_VALIDATE_SIG_TARGETS := $(addprefix validate-,$(AZURE_BUILD_SIG_NAMES)) +AZURE_VALIDATE_SIG_GEN2_TARGETS := $(addprefix validate-,$(AZURE_BUILD_SIG_GEN2_NAMES)) +DO_BUILD_TARGETS := $(addprefix build-,$(DO_BUILD_NAMES)) +DO_VALIDATE_TARGETS := $(addprefix validate-,$(DO_BUILD_NAMES)) +QEMU_BUILD_TARGETS := $(addprefix build-,$(QEMU_BUILD_NAMES)) +QEMU_VALIDATE_TARGETS := $(addprefix validate-,$(QEMU_BUILD_NAMES)) +QEMU_KUBEVIRT_BUILD_TARGETS := $(addprefix build-,$(QEMU_KUBEVIRT_BUILD_NAMES)) +QEMU_KUBEVIRT_VALIDATE_TARGETS := $(addprefix validate-,$(QEMU_KUBEVIRT_BUILD_NAMES)) +RAW_BUILD_TARGETS := $(addprefix build-,$(RAW_BUILD_NAMES)) +RAW_VALIDATE_TARGETS := $(addprefix validate-,$(RAW_BUILD_NAMES)) +OCI_BUILD_TARGETS := $(addprefix build-,$(OCI_BUILD_NAMES)) +OCI_VALIDATE_TARGETS := $(addprefix validate-,$(OCI_BUILD_NAMES)) +OSC_BUILD_TARGETS := $(addprefix build-,$(OSC_BUILD_NAMES)) +OSC_VALIDATE_TARGETS := $(addprefix validate-,$(OSC_BUILD_NAMES)) +VBOX_BUILD_TARGETS := $(addprefix build-,$(VBOX_BUILD_NAMES)) +VBOX_VALIDATE_TARGETS := $(addprefix validate-,$(VBOX_BUILD_NAMES)) +POWERVS_BUILD_TARGETS := $(addprefix build-,$(POWERVS_BUILD_NAMES)) +POWERVS_VALIDATE_TARGETS := $(addprefix validate-,$(POWERVS_BUILD_NAMES)) +NUTANIX_BUILD_TARGETS := $(addprefix build-,$(NUTANIX_BUILD_NAMES)) +NUTANIX_VALIDATE_TARGETS := $(addprefix validate-,$(NUTANIX_BUILD_NAMES)) + +.PHONY: $(NODE_OVA_LOCAL_BUILD_TARGETS) +$(NODE_OVA_LOCAL_BUILD_TARGETS): deps-ova + # This uses a packer file builder to input unattend variables into a json file to be consumed by the python script before running the vmware-iso provisioner + $(if $(findstring windows,$@),packer build $(PACKER_WINDOWS_NODE_FLAGS) -var-file="packer/ova/packer-common.json" -var-file="$(abspath packer/ova/$(subst build-node-ova-local-,,$@).json)" -only=file $(ABSOLUTE_PACKER_VAR_FILES) packer/ova/packer-windows.json,) + $(if $(findstring windows,$@),hack/windows-ova-unattend.py --unattend-file='./packer/ova/windows/$(subst build-node-ova-local-,,$@)/autounattend.xml',) + packer build $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="packer/ova/packer-common.json" -var-file="$(abspath packer/ova/$(subst build-node-ova-local-,,$@).json)" -except=vsphere -only=vmware-iso $(ABSOLUTE_PACKER_VAR_FILES) packer/ova/packer-$(if $(findstring windows,$@),windows,node).json + +.PHONY: $(NODE_OVA_LOCAL_VALIDATE_TARGETS) +$(NODE_OVA_LOCAL_VALIDATE_TARGETS): deps-ova + packer validate $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="packer/ova/packer-common.json" -var-file="$(abspath packer/ova/$(subst validate-node-ova-local-,,$@).json)" -except=vsphere -only=vmware-iso $(ABSOLUTE_PACKER_VAR_FILES) packer/ova/packer-$(if $(findstring windows,$@),windows,node).json + +.PHONY: $(NODE_OVA_LOCAL_VMX_BUILD_TARGETS) +$(NODE_OVA_LOCAL_VMX_BUILD_TARGETS): deps-ova + packer build $(PACKER_NODE_FLAGS) -var-file="packer/ova/packer-common.json" -var-file="$(abspath packer/ova/$(subst build-node-ova-local-vmx-,,$@).json)" -var-file="packer/ova/vmx.json" -except=vsphere -except=vmware-iso -only=vmware-vmx $(ABSOLUTE_PACKER_VAR_FILES) packer/ova/packer-node.json + +.PHONY: $(NODE_OVA_LOCAL_BASE_BUILD_TARGETS) +$(NODE_OVA_LOCAL_BASE_BUILD_TARGETS): deps-ova + packer build $(PACKER_NODE_FLAGS) -var-file="packer/ova/packer-common.json" -var-file="$(abspath packer/ova/$(subst build-node-ova-local-base-,,$@).json)" -except=vsphere -except=vmware-iso -except=vmware-vmx -only=vmware-iso-base $(ABSOLUTE_PACKER_VAR_FILES) packer/ova/packer-node.json + +.PHONY: $(NODE_OVA_VSPHERE_BUILD_TARGETS) +$(NODE_OVA_VSPHERE_BUILD_TARGETS): deps-ova + # This uses a packer file builder to input unattend variables into a json file to be consumed by the python script before running the vsphere provisioner + $(if $(findstring windows,$@),packer build $(PACKER_WINDOWS_NODE_FLAGS) -var-file="packer/ova/packer-common.json" -var-file="$(abspath packer/ova/$(subst build-node-ova-vsphere-,,$@).json)" -only=file $(ABSOLUTE_PACKER_VAR_FILES) packer/ova/packer-windows.json,) + $(if $(findstring windows,$@),hack/windows-ova-unattend.py --unattend-file='./packer/ova/windows/$(subst build-node-ova-vsphere-,,$@)/autounattend.xml',) + packer build $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="packer/ova/packer-common.json" -var-file="$(abspath packer/ova/$(subst build-node-ova-vsphere-,,$@).json)" -var-file="packer/ova/vsphere.json" -except=local -only=vsphere-iso $(ABSOLUTE_PACKER_VAR_FILES) -only=vsphere packer/ova/packer-$(if $(findstring windows,$@),windows,node).json + +.PHONY: $(NODE_OVA_VSPHERE_BASE_BUILD_TARGETS) +$(NODE_OVA_VSPHERE_BASE_BUILD_TARGETS): deps-ova + packer build $(PACKER_NODE_FLAGS) -var-file="packer/ova/packer-common.json" -var-file="$(abspath packer/ova/$(subst build-node-ova-vsphere-base-,,$@).json)" -var-file="packer/ova/vsphere.json" -except=local -except=manifest -except=vsphere -only=vsphere-iso-base $(ABSOLUTE_PACKER_VAR_FILES) packer/ova/packer-node.json + +.PHONY: $(NODE_OVA_VSPHERE_CLONE_BUILD_TARGETS) +$(NODE_OVA_VSPHERE_CLONE_BUILD_TARGETS): deps-ova + packer build $(PACKER_NODE_FLAGS) -var-file="packer/ova/packer-common.json" -var-file="$(abspath packer/ova/$(subst build-node-ova-vsphere-clone-,,$@).json)" -var-file="packer/ova/vsphere.json" -except=local -only=vsphere-clone $(ABSOLUTE_PACKER_VAR_FILES) packer/ova/packer-node.json + +.PHONY: $(AMI_BUILD_TARGETS) +$(AMI_BUILD_TARGETS): deps-ami + packer build $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="$(abspath packer/ami/$(subst build-ami-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/ami/packer$(if $(findstring windows,$@),-windows,).json + +.PHONY: $(AMI_VALIDATE_TARGETS) +$(AMI_VALIDATE_TARGETS): deps-ami + packer validate $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="$(abspath packer/ami/$(subst validate-ami-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/ami/packer$(if $(findstring windows,$@),-windows,).json + +.PHONY: $(GCE_BUILD_TARGETS) +$(GCE_BUILD_TARGETS): deps-gce + packer build $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/gce/$(subst build-gce-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/gce/packer.json + +.PHONY: $(GCE_VALIDATE_TARGETS) +$(GCE_VALIDATE_TARGETS): deps-gce + packer validate $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/gce/$(subst validate-gce-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/gce/packer.json + +.PHONY: $(AZURE_BUILD_VHD_TARGETS) +$(AZURE_BUILD_VHD_TARGETS): deps-azure + . $(abspath packer/azure/scripts/init-vhd.sh) && packer build $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="$(abspath packer/azure/azure-config.json)" -var-file="$(abspath packer/azure/azure-vhd.json)" -var-file="$(abspath packer/azure/$(subst build-azure-vhd-,,$@).json)" -only="$(subst build-azure-,,$@)" $(ABSOLUTE_PACKER_VAR_FILES) packer/azure/packer$(findstring -windows,$@).json + +.PHONY: $(AZURE_VALIDATE_VHD_TARGETS) +$(AZURE_VALIDATE_VHD_TARGETS): deps-azure + packer validate $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="$(abspath packer/azure/azure-config.json)" -var-file="$(abspath packer/azure/azure-vhd.json)" -var-file="$(abspath packer/azure/$(subst validate-azure-vhd-,,$@).json)" -only="$(subst validate-azure-,,$@)" $(ABSOLUTE_PACKER_VAR_FILES) packer/azure/packer$(findstring -windows,$@).json + +.PHONY: $(AZURE_BUILD_SIG_TARGETS) +$(AZURE_BUILD_SIG_TARGETS): deps-azure + . $(abspath packer/azure/scripts/init-sig.sh) $(subst build-azure-sig-,,$@) && packer build $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="$(abspath packer/azure/azure-config.json)" -var-file="$(abspath packer/azure/azure-sig.json)" -var-file="$(abspath packer/azure/$(subst build-azure-sig-,,$@).json)" -only="$(subst build-azure-,,$@)" $(ABSOLUTE_PACKER_VAR_FILES) packer/azure/packer$(findstring -windows,$@).json + +.PHONY: $(AZURE_BUILD_SIG_GEN2_TARGETS) +$(AZURE_BUILD_SIG_GEN2_TARGETS): deps-azure + . $(abspath packer/azure/scripts/init-sig.sh) $(subst build-azure-sig-,,$@) && packer build $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="$(abspath packer/azure/azure-config.json)" -var-file="$(abspath packer/azure/azure-sig-gen2.json)" -var-file="$(abspath packer/azure/$(subst build-azure-sig-,,$@).json)" -only="$(subst build-azure-,,$@)" $(ABSOLUTE_PACKER_VAR_FILES) packer/azure/packer$(findstring -windows,$@).json + +.PHONY: $(AZURE_VALIDATE_SIG_TARGETS) +$(AZURE_VALIDATE_SIG_TARGETS): deps-azure + packer validate $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="$(abspath packer/azure/azure-config.json)" -var-file="$(abspath packer/azure/azure-sig.json)" -var-file="$(abspath packer/azure/$(subst validate-azure-sig-,,$@).json)" -only="$(subst validate-azure-,,$@)" $(ABSOLUTE_PACKER_VAR_FILES) packer/azure/packer$(findstring -windows,$@).json + +.PHONY: $(AZURE_VALIDATE_SIG_GEN2_TARGETS) +$(AZURE_VALIDATE_SIG_GEN2_TARGETS): deps-azure + packer validate $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="$(abspath packer/azure/azure-config.json)" -var-file="$(abspath packer/azure/azure-sig-gen2.json)" -var-file="$(abspath packer/azure/$(subst validate-azure-sig-,,$@).json)" -only="$(subst validate-azure-,,$@)" $(ABSOLUTE_PACKER_VAR_FILES) packer/azure/packer$(findstring windows,$@).json + +.PHONY: $(DO_BUILD_TARGETS) +$(DO_BUILD_TARGETS): deps-do + packer build $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/digitalocean/$(subst build-do-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/digitalocean/packer.json + +.PHONY: $(DO_VALIDATE_TARGETS) +$(DO_VALIDATE_TARGETS): deps-do + packer validate $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/digitalocean/$(subst validate-do-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/digitalocean/packer.json + +.PHONY: $(QEMU_BUILD_TARGETS) +$(QEMU_BUILD_TARGETS): deps-qemu + packer build $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/qemu/$(subst build-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/qemu/packer.json + +.PHONY: $(QEMU_VALIDATE_TARGETS) +$(QEMU_VALIDATE_TARGETS): deps-qemu + packer validate $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/qemu/$(subst validate-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/qemu/packer.json + +.PHONY: $(QEMU_KUBEVIRT_BUILD_TARGETS) +$(QEMU_KUBEVIRT_BUILD_TARGETS): deps-qemu + packer build $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/qemu/$(subst build-kubevirt-,,$@).json)" --var 'kubevirt=true' $(ABSOLUTE_PACKER_VAR_FILES) packer/qemu/packer.json + +.PHONY: $(QEMU_KUBEVIRT_VALIDATE_TARGETS) +$(QEMU_KUBEVIRT_VALIDATE_TARGETS): deps-qemu + packer validate $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/qemu/$(subst validate-kubevirt-,,$@).json)" --var 'kubevirt=true' $(ABSOLUTE_PACKER_VAR_FILES) packer/qemu/packer.json + +.PHONY: $(RAW_BUILD_TARGETS) +$(RAW_BUILD_TARGETS): deps-raw + packer build $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/raw/$(subst build-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/raw/packer.json + +.PHONY: $(RAW_VALIDATE_TARGETS) +$(RAW_VALIDATE_TARGETS): deps-raw + packer validate $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/raw/$(subst validate-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/raw/packer.json + +.PHONY: $(OCI_BUILD_TARGETS) +$(OCI_BUILD_TARGETS): deps-oci + $(if $(findstring windows,$@),./packer/oci/scripts/set_bootstrap.sh,) + packer build $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="$(abspath packer/oci/$(subst build-oci-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/oci/packer$(findstring -windows,$@).json + $(if $(findstring windows,$@),./packer/oci/scripts/unset_bootstrap.sh,) + +.PHONY: $(OCI_VALIDATE_TARGETS) +$(OCI_VALIDATE_TARGETS): deps-oci + packer validate $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/oci/$(subst validate-oci-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/oci/packer.json + +.PHONY: $(OSC_BUILD_TARGETS) +$(OSC_BUILD_TARGETS): deps-osc + packer build $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/outscale/$(subst build-osc-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/outscale/packer.json + +.PHONY: $(OSC_VALIDATE_TARGETS) +$(OSC_VALIDATE_TARGETS): deps-osc + packer validate $(PACKER_NODE_FLAGS) -var-file="$(abspath packer/outscale/$(subst validate-osc-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/outscale/packer.json + +.PHONY: $(VBOX_BUILD_TARGETS) +$(VBOX_BUILD_TARGETS): deps-vbox + packer build $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="packer/vbox/packer-common.json" -var-file="$(abspath packer/vbox/$(subst build-vbox-,,$@).json)" -only=virtualbox-iso $(ABSOLUTE_PACKER_VAR_FILES) packer/vbox/packer-$(if $(findstring windows,$@),windows).json + +.PHONY: $(VBOX_VALIDATE_TARGETS) +$(VBOX_VALIDATE_TARGETS): deps-vbox + packer validate $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="packer/vbox/packer-common.json" -var-file="$(abspath packer/vbox/$(subst validate-vbox-,,$@).json)" -only=virtualbox-iso $(ABSOLUTE_PACKER_VAR_FILES) packer/vbox/packer-$(if $(findstring windows,$@),windows).json + +.PHONY: $(POWERVS_BUILD_TARGETS) +$(POWERVS_BUILD_TARGETS): deps-powervs + packer build $(PACKER_POWERVS_NODE_FLAGS) -var-file="$(abspath packer/powervs/$(subst build-powervs-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) -except=flatcar packer/powervs/packer.json + +.PHONY: $(POWERVS_VALIDATE_TARGETS) +$(POWERVS_VALIDATE_TARGETS): deps-powervs + packer validate $(PACKER_POWERVS_NODE_FLAGS) -var-file="$(abspath packer/powervs/$(subst validate-powervs-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) -except=flatcar packer/powervs/packer.json + +.PHONY: $(NUTANIX_BUILD_TARGETS) +$(NUTANIX_BUILD_TARGETS): deps-nutanix + packer init packer/nutanix/config.pkr.hcl + packer build $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="packer/nutanix/nutanix.json" -var-file="$(abspath packer/nutanix/$(subst build-nutanix-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/nutanix/packer$(if $(findstring windows,$@),-windows,).json + +.PHONY: $(NUTANIX_VALIDATE_TARGETS) +$(NUTANIX_VALIDATE_TARGETS): deps-nutanix + packer init packer/nutanix/config.pkr.hcl + packer validate $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="packer/nutanix/nutanix.json" -var-file="$(abspath packer/nutanix/$(subst validate-nutanix-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/nutanix/packer$(if $(findstring windows,$@),-windows,).json + +## -------------------------------------- +## Dynamic clean targets +## -------------------------------------- +NODE_OVA_LOCAL_CLEAN_TARGETS := $(subst build-,clean-,$(NODE_OVA_LOCAL_BUILD_TARGETS)) +.PHONY: $(NODE_OVA_LOCAL_CLEAN_TARGETS) +$(NODE_OVA_LOCAL_CLEAN_TARGETS): + rm -fr output/$(subst clean-node-ova-local-,,$@)-kube* + +QEMU_CLEAN_TARGETS := $(subst build-,clean-,$(QEMU_BUILD_TARGETS)) +.PHONY: $(QEMU_CLEAN_TARGETS) +$(QEMU_CLEAN_TARGETS): + rm -fr output/$(subst clean-qemu-,,$@)-kube* + +RAW_CLEAN_TARGETS := $(subst build-,clean-,$(RAW_BUILD_TARGETS)) +.PHONY: $(RAW_CLEAN_TARGETS) +$(RAW_CLEAN_TARGETS): + rm -fr output/$(subst clean-raw-,,$@)-kube* + +VBOX_CLEAN_TARGETS := $(subst build-,clean-,$(VBOX_BUILD_TARGETS)) +.PHONY: $(VBOX_CLEAN_TARGETS) +$(VBOX_CLEAN_TARGETS): + rm -fr output/$(subst clean-vbox-,,$@)-kube* + +## -------------------------------------- +## Document dynamic build targets +## -------------------------------------- +##@ Builds +build-ami-amazon-2: ## Builds Amazon-2 Linux AMI +build-ami-centos-7: ## Builds CentOS 7 AMI +build-ami-ubuntu-1804: ## Builds Ubuntu 18.04 AMI +build-ami-ubuntu-2004: ## Builds Ubuntu 20.04 AMI +build-ami-ubuntu-2204: ## Builds Ubuntu 22.04 AMI +build-ami-rockylinux-8: ## Builds RockyLinux 8 AMI +build-ami-rhel-8: ## Builds RHEL-8 AMI +build-ami-flatcar: ## Builds Flatcar +build-ami-windows-2019: ## Build Windows Server 2019 AMI Packer config +build-ami-windows-2004: ## Build Windows Server 2004 SAC AMI Packer config +build-ami-all: $(AMI_BUILD_TARGETS) ## Builds all AMIs + +build-azure-sig-ubuntu-1804: ## Builds Ubuntu 18.04 Azure managed image in Shared Image Gallery +build-azure-sig-ubuntu-2004: ## Builds Ubuntu 20.04 Azure managed image in Shared Image Gallery +build-azure-sig-ubuntu-2204: ## Builds Ubuntu 22.04 Azure managed image in Shared Image Gallery +build-azure-sig-centos-7: ## Builds CentOS 7 Azure managed image in Shared Image Gallery +build-azure-sig-rhel-8: ## Builds RHEL 8 Azure managed image in Shared Image Gallery +build-azure-sig-windows-2019: ## Builds Windows Server 2019 Azure managed image in Shared Image Gallery +build-azure-sig-windows-2019-containerd: ## Builds Windows Server 2019 with containerd Azure managed image in Shared Image Gallery +build-azure-sig-windows-2022-containerd: ## Builds Windows Server 2022 with containerd Azure managed image in Shared Image Gallery +build-azure-sig-windows-2004: ## Builds Windows Server 2004 SAC Azure managed image in Shared Image Gallery +build-azure-vhd-ubuntu-1804: ## Builds Ubuntu 18.04 VHD image for Azure +build-azure-vhd-ubuntu-2004: ## Builds Ubuntu 20.04 VHD image for Azure +build-azure-vhd-ubuntu-2204: ## Builds Ubuntu 22.04 VHD image for Azure +build-azure-vhd-centos-7: ## Builds CentOS 7 VHD image for Azure +build-azure-vhd-rhel-8: ## Builds RHEL 8 VHD image for Azure +build-azure-vhd-windows-2019: ## Builds for Windows Server 2019 +build-azure-vhd-windows-2019-containerd: ## Builds for Windows Server 2019 with containerd +build-azure-vhd-windows-2022-containerd: ## Builds for Windows Server 2022 with containerd +build-azure-vhd-windows-2004: ## Builds for Windows Server 2004 SAC +build-azure-sig-centos-7-gen2: ## Builds CentOS Gen2 managed image in Shared Image Gallery +build-azure-sig-flatcar: ## Builds Flatcar Azure managed image in Shared Image Gallery +build-azure-sig-flatcar-gen2: ## Builds Flatcar Azure Gen2 managed image in Shared Image Gallery +build-azure-sig-ubuntu-1804-gen2: ## Builds Ubuntu 18.04 Gen2 managed image in Shared Image Gallery +build-azure-sig-ubuntu-2004-gen2: ## Builds Ubuntu 20.04 Gen2 managed image in Shared Image Gallery +build-azure-sig-ubuntu-2204-gen2: ## Builds Ubuntu 22.04 Gen2 managed image in Shared Image Gallery +build-azure-vhds: $(AZURE_BUILD_VHD_TARGETS) ## Builds all Azure VHDs +build-azure-sigs: $(AZURE_BUILD_SIG_TARGETS) $(AZURE_BUILD_SIG_GEN2_TARGETS) ## Builds all Azure Shared Image Gallery images + +build-do-ubuntu-1804: ## Builds Ubuntu 18.04 DigitalOcean Snapshot +build-do-ubuntu-2004: ## Builds Ubuntu 20.04 DigitalOcean Snapshot +build-do-centos-7: ## Builds Centos 7 DigitalOcean Snapshot +build-do-all: $(DO_BUILD_TARGETS) ## Builds all DigitalOcean Snapshot + +build-gce-ubuntu-1804: ## Builds the GCE ubuntu-1804 image +build-gce-ubuntu-2004: ## Builds the GCE ubuntu-2004 image +build-gce-ubuntu-2204: ## Builds the GCE ubuntu-2204 image +build-gce-all: $(GCE_BUILD_TARGETS) ## Builds all GCE image + +build-node-ova-local-centos-7: ## Builds CentOS 7 Node OVA w local hypervisor +build-node-ova-local-flatcar: ## Builds Flatcar stable Node OVA w local hypervisor +build-node-ova-local-photon-3: ## Builds Photon 3 Node OVA w local hypervisor +build-node-ova-local-photon-4: ## Builds Photon 4 Node OVA w local hypervisor +build-node-ova-local-rhel-7: ## Builds RHEL 7 Node OVA w local hypervisor +build-node-ova-local-rhel-8: ## Builds RHEL 8 Node OVA w local hypervisor +build-node-ova-local-rockylinux-8: ## Builds RockyLinux 8 Node OVA w local hypervisor +build-node-ova-local-ubuntu-1804: ## Builds Ubuntu 18.04 Node OVA w local hypervisor +build-node-ova-local-ubuntu-2004: ## Builds Ubuntu 20.04 Node OVA w local hypervisor +build-node-ova-local-windows-2019: ## Builds for Windows Server 2019 Node OVA w local hypervisor +build-node-ova-local-windows-2004: ## Builds for Windows Server 2004 SAC Node OVA w local hypervisor +build-node-ova-local-all: $(NODE_OVA_LOCAL_BUILD_TARGETS) ## Builds all Node OVAs w local hypervisor + +build-node-ova-vsphere-centos-7: ## Builds CentOS 7 Node OVA and template on vSphere +build-node-ova-vsphere-flatcar: ## Builds Flatcar stable Node OVA and template on vSphere +build-node-ova-vsphere-photon-3: ## Builds Photon 3 Node OVA and template on vSphere +build-node-ova-vsphere-photon-4: ## Builds Photon 4 Node OVA and template on vSphere +build-node-ova-vsphere-rhel-7: ## Builds RHEL 7 Node OVA and template on vSphere +build-node-ova-vsphere-rhel-8: ## Builds RHEL 8 Node OVA and template on vSphere +build-node-ova-vsphere-rockylinux-8: ## Builds RockyLinux 8 Node OVA and template on vSphere +build-node-ova-vsphere-ubuntu-1804: ## Builds Ubuntu 18.04 Node OVA and template on vSphere +build-node-ova-vsphere-ubuntu-2004: ## Builds Ubuntu 20.04 Node OVA and template on vSphere +build-node-ova-vsphere-ubuntu-2204: ## Builds Ubuntu 22.04 Node OVA and template on vSphere +build-node-ova-vsphere-windows-2019: ## Builds for Windows Server 2019 and template on vSphere +build-node-ova-vsphere-windows-2004: ## Builds for Windows Server 2004 SAC and template on vSphere +build-node-ova-vsphere-windows-2022: ## Builds for Windows Server 2022 template on vSphere +build-node-ova-vsphere-ubuntu-2004-efi: ## Builds Ubuntu 20.04 Node OVA and template on vSphere that EFI boots +build-node-ova-vsphere-all: $(NODE_OVA_VSPHERE_BUILD_TARGETS) ## Builds all Node OVAs and templates on vSphere + +build-node-ova-vsphere-clone-centos-7: ## Builds CentOS 7 Node OVA and template on vSphere +build-node-ova-vsphere-clone-photon-3: ## Builds Photon 3 Node OVA and template on vSphere +build-node-ova-vsphere-clone-photon-4: ## Builds Photon 4 Node OVA and template on vSphere +build-node-ova-vsphere-clone-rhel-7: ## Builds RHEL 7 Node OVA and template on vSphere +build-node-ova-vsphere-clone-rhel-8: ## Builds RHEL 8 Node OVA and template on vSphere +build-node-ova-vsphere-clone-rockylinux-8: ## Builds RockyLinux 8 Node OVA and template on vSphere +build-node-ova-vsphere-clone-ubuntu-1804: ## Builds Ubuntu 18.04 Node OVA and template on vSphere +build-node-ova-vsphere-clone-ubuntu-2004: ## Builds Ubuntu 20.04 Node OVA and template on vSphere +build-node-ova-vsphere-clone-ubuntu-2204: ## Builds Ubuntu 22.04 Node OVA and template on vSphere +build-node-ova-vsphere-clone-all: $(NODE_OVA_VSPHERE_CLONE_BUILD_TARGETS) ## Builds all Node OVAs and templates on vSphere + +build-node-ova-vsphere-base-centos-7: ## Builds base CentOS 7 Node OVA and template on vSphere +build-node-ova-vsphere-base-photon-3: ## Builds base Photon 3 Node OVA and template on vSphere +build-node-ova-vsphere-base-photon-4: ## Builds base Photon 4 Node OVA and template on vSphere +build-node-ova-vsphere-base-rhel-7: ## Builds base RHEL 7 Node OVA and template on vSphere +build-node-ova-vsphere-base-rhel-8: ## Builds base RHEL 8 Node OVA and template on vSphere +build-node-ova-vsphere-base-rockylinux-8: ## Builds base RockyLinux 8 Node OVA and template on vSphere +build-node-ova-vsphere-base-ubuntu-1804: ## Builds base Ubuntu 18.04 Node OVA and template on vSphere +build-node-ova-vsphere-base-ubuntu-2004: ## Builds base Ubuntu 20.04 Node OVA and template on vSphere +build-node-ova-vsphere-base-ubuntu-2204: ## Builds base Ubuntu 22.04 Node OVA and template on vSphere +build-node-ova-vsphere-base-all: $(NODE_OVA_VSPHERE_BASE_BUILD_TARGETS) ## Builds all base Node OVAs and templates on vSphere + +build-node-ova-local-vmx-photon-3: ## Builds Photon 3 Node OVA from VMX file w local hypervisor +build-node-ova-local-vmx-photon-4: ## Builds Photon 4 Node OVA from VMX file w local hypervisor +build-node-ova-local-vmx-centos-7: ## Builds Centos 7 Node OVA from VMX file w local hypervisor +build-node-ova-local-vmx-rhel-7: ## Builds RHEL 7 Node OVA from VMX file w local hypervisor +build-node-ova-local-vmx-rhel-8: ## Builds RHEL 8 Node OVA from VMX file w local hypervisor +build-node-ova-local-vmx-rockylinux-8: ## Builds RockyLinux 8 Node OVA from VMX file w local hypervisor +build-node-ova-local-vmx-ubuntu-1804: ## Builds Ubuntu 18.04 Node OVA from VMX file w local hypervisor +build-node-ova-local-vmx-ubuntu-2004: ## Builds Ubuntu 20.04 Node OVA from VMX file w local hypervisor + +build-node-ova-local-base-photon-3: ## Builds Photon 3 Base Node OVA w local hypervisor +build-node-ova-local-base-photon-4: ## Builds Photon 4 Base Node OVA w local hypervisor +build-node-ova-local-base-centos-7: ## Builds Centos 7 Base Node OVA w local hypervisor +build-node-ova-local-base-rhel-7: ## Builds RHEL 7 Base Node OVA w local hypervisor +build-node-ova-local-base-rhel-8: ## Builds RHEL 8 Base Node OVA w local hypervisor +build-node-ova-local-base-rockylinux-8: ## Builds RockyLinux 8 Base Node OVA w local hypervisor +build-node-ova-local-base-ubuntu-1804: ## Builds Ubuntu 18.04 Base Node OVA w local hypervisor +build-node-ova-local-base-ubuntu-2004: ## Builds Ubuntu 20.04 Base Node OVA w local hypervisor + +build-qemu-flatcar: ## Builds Flatcar QEMU image +build-qemu-ubuntu-1804: ## Builds Ubuntu 18.04 QEMU image +build-qemu-ubuntu-2004: ## Builds Ubuntu 20.04 QEMU image +build-qemu-ubuntu-2004-efi: ## Builds Ubuntu 20.04 QEMU image that EFI boots +build-qemu-ubuntu-2204: ## Builds Ubuntu 22.04 QEMU image +build-qemu-centos-7: ## Builds CentOS 7 QEMU image +build-qemu-rhel-8: ## Builds RHEL 8 QEMU image +build-qemu-rockylinux-8: ## Builds Rocky 8 QEMU image +build-qemu-all: $(QEMU_BUILD_TARGETS) ## Builds all Qemu images + +build-raw-flatcar: ## Builds Flatcar RAW image +build-raw-ubuntu-1804: ## Builds Ubuntu 18.04 RAW image +build-raw-ubuntu-2004: ## Builds Ubuntu 20.04 RAW image +build-raw-ubuntu-2004-efi: ## Builds Ubuntu 20.04 RAW image that EFI boots +build-raw-all: $(RAW_BUILD_TARGETS) ## Builds all RAW images + +build-oci-ubuntu-1804: ## Builds the OCI ubuntu-1804 image +build-oci-ubuntu-2004: ## Builds the OCI ubuntu-2004 image +build-oci-ubuntu-2204: ## Builds the OCI ubuntu-2204 image +build-oci-oracle-linux-8: ## Builds the OCI Oracle Linux 8.x image +build-oci-oracle-linux-9: ## Builds the OCI Oracle Linux 9.x image +build-oci-windows-2019: ## Builds the OCI Windows Server 2019 image +build-oci-windows-2022: ## Builds the OCI Windows Server 2022 image +build-oci-all: $(OCI_BUILD_TARGETS) ## Builds all OCI image + +build-osc-ubuntu-2004: ## Builds Ubuntu 20.04 Outscale Snapshot +build-osc-all: $(OSC_BUILD_TARGETS) ## Builds all Outscale Snapshot + +build-vbox-windows-2019: ## Builds for Windows Server 2019 Node VirtualBox w local hypervisor +build-vbox-all: $(VBOX_BUILD_TARGETS) ## Builds all Qemu images + +build-nutanix-ubuntu-2004: ## Builds the Nutanix ubuntu-2004 image +build-nutanix-ubuntu-2204: ## Builds the Nutanix ubuntu-2204 image +build-nutanix-rockylinux-8: ## Builds the Nutanix Rocky Linux 8 image +build-nutanix-rockylinux-9: ## Builds the Nutanix Rocky Linux 9 image +build-nutanix-flatcar: ## Builds the Nutanix Flatcar image +build-nutanix-windows-2022: ## Builds the Nutanix Windows 2022 image +build-nutanix-all: $(NUTANIX_BUILD_TARGETS) ## Builds all Nutanix image + +## -------------------------------------- +## Document dynamic validate targets +## -------------------------------------- +##@ Validate packer config +validate-ami-amazon-2: ## Validates Amazon-2 Linux AMI Packer config +validate-ami-centos-7: ## Validates CentOS 7 AMI Packer config +validate-ami-rockylinux-8: ## Validates RockyLinux 8 AMI Packer config +validate-ami-rhel-8: ## Validates RHEL-8 AMI Packer config +validate-ami-flatcar: ## Validates Flatcar AMI Packer config +validate-ami-ubuntu-1804: ## Validates Ubuntu 18.04 AMI Packer config +validate-ami-ubuntu-2004: ## Validates Ubuntu 20.04 AMI Packer config +validate-ami-ubuntu-2204: ## Validates Ubuntu 22.04 AMI Packer config +validate-ami-windows-2019: ## Validates Windows Server 2019 AMI Packer config +validate-ami-windows-2004: ## Validates Windows Server 2004 SAC AMI Packer config +validate-ami-all: $(AMI_VALIDATE_TARGETS) ## Validates all AMIs Packer config + +validate-azure-sig-centos-7: ## Validates CentOS 7 Azure managed image in Shared Image Gallery Packer config +validate-azure-sig-rhel-8: ## Validates RHEL 8 Azure managed image in Shared Image Gallery Packer config +validate-azure-sig-ubuntu-1804: ## Validates Ubuntu 18.04 Azure managed image in Shared Image Gallery Packer config +validate-azure-sig-ubuntu-2004: ## Validates Ubuntu 20.04 Azure managed image in Shared Image Gallery Packer config +validate-azure-sig-ubuntu-2204: ## Validates Ubuntu 22.04 Azure managed image in Shared Image Gallery Packer config +validate-azure-sig-windows-2019: ## Validate Windows Server 2019 Azure managed image in Shared Image Gallery Packer config +validate-azure-sig-windows-2019-containerd: ## Validate Windows Server 2019 with containerd Azure managed image in Shared Image Gallery Packer config +validate-azure-sig-windows-2022-containerd: ## Validate Windows Server 2022 with containerd Azure managed image in Shared Image Gallery Packer config +validate-azure-sig-windows-2004: ## Validate Windows Server 2004 SAC Azure managed image in Shared Image Gallery Packer config +validate-azure-vhd-centos-7: ## Validates CentOS 7 VHD image Azure Packer config +validate-azure-vhd-rhel-8: ## Validates RHEL 8 VHD image Azure Packer config +validate-azure-vhd-ubuntu-1804: ## Validates Ubuntu 18.04 VHD image Azure Packer config +validate-azure-vhd-ubuntu-2004: ## Validates Ubuntu 20.04 VHD image Azure Packer config +validate-azure-vhd-ubuntu-2204: ## Validates Ubuntu 22.04 VHD image Azure Packer config +validate-azure-vhd-windows-2019: ## Validate Windows Server 2019 VHD image Azure Packer config +validate-azure-vhd-windows-2019-containerd: ## Validate Windows Server 2019 VHD with containerd image Azure Packer config +validate-azure-vhd-windows-2022-containerd: ## Validate Windows Server 2022 VHD with containerd image Azure Packer config +validate-azure-vhd-windows-2004: ## Validate Windows Server 2004 SAC VHD image Azure Packer config +validate-azure-sig-centos-7-gen2: ## Validates CentOS 7 Azure managed image in Shared Image Gallery Packer config +validate-azure-sig-ubuntu-1804-gen2: ## Validates Ubuntu 18.04 Azure managed image in Shared Image Gallery Packer config +validate-azure-sig-ubuntu-2004-gen2: ## Validates Ubuntu 20.04 Azure managed image in Shared Image Gallery Packer config +validate-azure-sig-ubuntu-2204-gen2: ## Validates Ubuntu 22.04 Azure managed image in Shared Image Gallery Packer config +validate-azure-all: $(AZURE_VALIDATE_SIG_TARGETS) $(AZURE_VALIDATE_VHD_TARGETS) $(AZURE_VALIDATE_SIG_GEN2_TARGETS) ## Validates all images for Azure Packer config + +validate-do-ubuntu-1804: ## Validates Ubuntu 18.04 DigitalOcean Snapshot Packer config +validate-do-ubuntu-2004: ## Validates Ubuntu 20.04 DigitalOcean Snapshot Packer config +validate-do-centos-7: ## Validates Centos 7 DigitalOcean Snapshot Packer config +validate-do-all: $(DO_VALIDATE_TARGETS) ## Validates all DigitalOcean Snapshot Packer config + +validate-gce-ubuntu-1804: ## Validates Ubuntu 18.04 GCE Snapshot Packer config +validate-gce-ubuntu-2004: ## Validates Ubuntu 20.04 GCE Snapshot Packer config +validate-gce-ubuntu-2204: ## Validates Ubuntu 22.04 GCE Snapshot Packer config +validate-gce-all: $(GCE_VALIDATE_TARGETS) ## Validates all GCE Snapshot Packer config + +validate-node-ova-local-centos-7: ## Validates CentOS 7 Node OVA Packer config w local hypervisor +validate-node-ova-local-flatcar: ## Validates Flatcar stable Node OVA Packer config w local hypervisor +validate-node-ova-local-photon-3: ## Validates Photon 3 Node OVA Packer config w local hypervisor +validate-node-ova-local-photon-4: ## Validates Photon 4 Node OVA Packer config w local hypervisor +validate-node-ova-local-rhel-7: ## Validates RHEL 7 Node OVA Packer config w local hypervisor +validate-node-ova-local-rhel-8: ## Validates RHEL 8 Node OVA Packer config w local hypervisor +validate-node-ova-local-rockylinux-8: ## Validates RockyLinux 8 Node OVA Packer config w local hypervisor +validate-node-ova-local-ubuntu-1804: ## Validates Ubuntu 18.04 Node OVA Packer config w local hypervisor +validate-node-ova-local-ubuntu-2004: ## Validates Ubuntu 20.04 Node OVA Packer config w local hypervisor +validate-node-ova-local-ubuntu-2204: ## Validates Ubuntu 22.04 Node OVA Packer config w local hypervisor +validate-node-ova-local-windows-2019: ## Validates Windows Server 2019 Node OVA Packer config w local hypervisor +validate-node-ova-local-windows-2004: ## Validates Windows Server 2004 SAC Node OVA Packer config w local hypervisor +validate-node-ova-local-windows-2022: ## Validates Windows Server 2022 Node OVA Packer config w local hypervisor +validate-node-ova-local-all: $(NODE_OVA_LOCAL_VALIDATE_TARGETS) ## Validates all Node OVAs Packer config w local hypervisor + +validate-node-ova-local-vmx-photon-3: ## Validates Photon 3 Node OVA from VMX file w local hypervisor +validate-node-ova-local-vmx-photon-4: ## Validates Photon 4 Node OVA from VMX file w local hypervisor +validate-node-ova-local-vmx-centos-7: ## Validates Centos 7 Node OVA from VMX file w local hypervisor +validate-node-ova-local-vmx-rhel-7: ## Validates RHEL 7 Node OVA from VMX file w local hypervisor +validate-node-ova-local-vmx-rhel-8: ## Validates RHEL 8 Node OVA from VMX file w local hypervisor +validate-node-ova-local-vmx-rockylinux-8: ## Validates RockyLinux 8 Node OVA from VMX file w local hypervisor +validate-node-ova-local-vmx-ubuntu-1804: ## Validates Ubuntu 18.04 Node OVA from VMX file w local hypervisor +validate-node-ova-local-vmx-ubuntu-2004: ## Validates Ubuntu 20.04 Node OVA from VMX file w local hypervisor +validate-node-ova-local-vmx-ubuntu-2204: ## Validates Ubuntu 22.04 Node OVA from VMX file w local hypervisor + +validate-node-ova-local-base-photon-3: ## Validates Photon 3 Base Node OVA w local hypervisor +validate-node-ova-local-base-photon-4: ## Validates Photon 4 Base Node OVA w local hypervisor +validate-node-ova-local-base-centos-7: ## Validates Centos 7 Base Node OVA w local hypervisor +validate-node-ova-local-base-rhel-7: ## Validates RHEL 7 Base Node OVA w local hypervisor +validate-node-ova-local-base-rhel-8: ## Validates RHEL 8 Base Node OVA w local hypervisor +validate-node-ova-local-base-rockylinux-8: ## Validates RockyLinux 8 Base Node OVA w local hypervisor +validate-node-ova-local-base-ubuntu-1804: ## Validates Ubuntu 18.04 Base Node OVA w local hypervisor +validate-node-ova-local-base-ubuntu-2004: ## Validates Ubuntu 20.04 Base Node OVA w local hypervisor +validate-node-ova-local-base-ubuntu-2204: ## Validates Ubuntu 22.04 Base Node OVA w local hypervisor + +validate-qemu-flatcar: ## Validates Flatcar QEMU image packer config +validate-qemu-ubuntu-1804: ## Validates Ubuntu 18.04 QEMU image packer config +validate-qemu-ubuntu-2004: ## Validates Ubuntu 20.04 QEMU image packer config +validate-qemu-ubuntu-2004-efi: ## Validates Ubuntu 20.04 QEMU EFI image packer config +validate-qemu-ubuntu-2204: ## Validates Ubuntu 22.04 QEMU image packer config +validate-qemu-centos-7: ## Validates CentOS 7 QEMU image packer config +validate-qemu-rhel-8: ## Validates RHEL 8 QEMU image +validate-qemu-rockylinux-8: ## Validates Rocky Linux 8 QEMU image packer config +validate-qemu-all: $(QEMU_VALIDATE_TARGETS) ## Validates all Qemu Packer config + +validate-raw-flatcar: ## Validates Flatcar RAW image packer config +validate-raw-ubuntu-1804: ## Validates Ubuntu 18.04 RAW image packer config +validate-raw-ubuntu-2004: ## Validates Ubuntu 20.04 RAW image packer config +validate-raw-ubuntu-2004-efi: ## Validates Ubuntu 20.04 RAW EFI image packer config +validate-raw-all: $(RAW_VALIDATE_TARGETS) ## Validates all RAW Packer config + +validate-oci-ubuntu-1804: ## Validates the OCI ubuntu-1804 image packer config +validate-oci-ubuntu-2004: ## Validates the OCI ubuntu-2004 image packer config +validate-oci-ubuntu-2204: ## Validates the OCI ubuntu-2204 image packer config +validate-oci-oracle-linux-8: ## Validates the OCI Oracle Linux 8.x image packer config +validate-oci-oracle-linux-9: ## Validates the OCI Oracle Linux 9.x image packer config +validate-oci-windows-2019: ## Validates the OCI Windows 2019 image packer config +validate-oci-windows-2022: ## Validates the OCI Windows 2022 image packer config +validate-oci-all: $(OCI_VALIDATE_TARGETS) ## Validates all OCI image packer config + +validate-osc-ubuntu-2004: ## Validates Ubuntu 20.04 Outscale Snapshot Packer config +validate-osc-all: $(OSC_VALIDATE_TARGETS) ## Validates all Outscale Snapshot Packer config + +validate-vbox-windows-2019: ## Validates Windows Server 2019 Node VirtualBox Packer config w local hypervisor +validate-vbox-all: $(VBOX_VALIDATE_TARGETS) ## Validates all RAW Packer config + +validate-powervs-centos-8: ## Validates the PowerVS CentOS image packer config +validate-powervs-all: $(POWERVS_VALIDATE_TARGETS) ## Validates all PowerVS Packer config + +validate-nutanix-ubuntu-2004: ## Validates Ubuntu 20.04 Nutanix Packer config +validate-nutanix-ubuntu-2204: ## Validates Ubuntu 22.04 Nutanix Packer config +validate-nutanix-rockylinux-8: ## Validates Rocky Linux 8 Nutanix Packer config +validate-nutanix-rockylinux-9: ## Validates the Nutanix Rocky Linux 9 Nutanix Packer config +validate-nutanix-flatcar: ## Validates the Nutanix Flatcar Nutanix Packer config +validate-nutanix-windows-2022: ## Validates Windows Server 2022 Nutanix Packer config +validate-nutanix-all: $(NUTANIX_VALIDATE_TARGETS) ## Validates all Nutanix Packer config + +validate-all: validate-ami-all \ + validate-azure-all \ + validate-do-all \ + validate-gce-all \ + validate-node-ova-local-all \ + validate-qemu-all \ + validate-raw-all \ + validate-oci-all \ + validate-osc-all \ + validate-vbox-all \ + validate-powervs-all \ + validate-nutanix-all +validate-all: ## Validates the Packer config for all build targets + +## -------------------------------------- +## Clean targets +## -------------------------------------- +##@ Cleaning +.PHONY: clean +clean: ## Removes all image output directories and packer image cache +clean: $(NODE_OVA_LOCAL_CLEAN_TARGETS) $(QEMU_CLEAN_TARGETS) $(VBOX_CLEAN_TARGETS) clean-packer-cache + +.PHONY: clean-ova +clean-ova: ## Removes all ova image output directories (see NOTE at top of help) +clean-ova: $(NODE_OVA_LOCAL_CLEAN_TARGETS) + +.PHONY: clean-qemu +clean-qemu: ## Removes all qemu image output directories (see NOTE at top of help) +clean-qemu: $(QEMU_CLEAN_TARGETS) + +.PHONY: clean-raw +clean-raw: ## Removes all raw image output directories (see NOTE at top of help) +clean-raw: $(RAW_CLEAN_TARGETS) + +.PHONY: clean-vbox +clean-vbox: ## Removes all vbox image output directories (see NOTE at top of help) +clean-vbox: $(VBOX_CLEAN_TARGETS) + +.PHONY: clean-packer-cache +clean-packer-cache: ## Removes the packer cache +clean-packer-cache: + rm -fr packer_cache/* + +## -------------------------------------- +## Docker targets +## -------------------------------------- +##@ Docker + +.PHONY: docker-pull-prerequisites +docker-pull-prerequisites: + # We must pre-pull images https://github.com/moby/buildkit/issues/1271 + docker pull docker/dockerfile:1.1-experimental + docker pull $(BASE_IMAGE) + +.PHONY: docker-build +docker-build: docker-pull-prerequisites ## Build the docker image for controller-manager + DOCKER_BUILDKIT=1 docker build --build-arg PASSED_IB_VERSION=$(IB_VERSION) --build-arg ARCH=$(ARCH) --build-arg BASE_IMAGE=$(BASE_IMAGE) . -t $(CONTROLLER_IMG)-$(ARCH):$(TAG) + +.PHONY: docker-push +docker-push: ## Push the docker image + docker push $(CONTROLLER_IMG)-$(ARCH):$(TAG) + +## -------------------------------------- +## Test targets +## -------------------------------------- +##@ Testing +.PHONY: test-azure +test-azure: ## Run the tests for Azure builders + $(abspath packer/azure/scripts/ci-azure-e2e.sh) + +## -------------------------------------- +## Release targets +## -------------------------------------- +##@ Release + +.PHONY: release-staging +release-staging: ## Builds and push container images to the staging bucket. + TAG=$(IB_VERSION) REGISTRY=$(STAGING_REGISTRY) $(MAKE) docker-build docker-push + +## -------------------------------------- +## Sort JSON +## -------------------------------------- +##@ Sort JSON + +.PHONY: json-sort +json_files = $(shell find . -type f -name "*.json" | sort -u) +json-sort: ## Sort all JSON files alphabetically + @for f in $(json_files); do (cat "$$f" | jq -S '.' >> "$$f".sorted && mv "$$f".sorted "$$f") || exit 1 ; done + + +## -------------------------------------- +## Ignition +## -------------------------------------- +##@ Ignition +.PHONY: gen-ignition +ignition_files = bootstrap +gen-ignition: deps-ignition ## Generates Ignition files from CLC + for f in $(ignition_files); do (ct < packer/files/flatcar/clc/$$f.yaml | jq '.' > packer/files/flatcar/ignition/$$f.json) || exit 1; done diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..07fe5a6 --- /dev/null +++ b/OWNERS @@ -0,0 +1,4 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - cluster-api-maintainers diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e18f69 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Image Builder for Cluster API + +The Image Builder can be used to build images intended for use with Kubernetes [CAPI](https://cluster-api.sigs.k8s.io/) providers. Each provider has its own format of images that it can work with. For example, AWS instances use AMIs, and vSphere uses OVAs. + +For detailed documentation, see https://image-builder.sigs.k8s.io/capi/capi.html. diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..b086976 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,20 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[defaults] +remote_tmp = /tmp/.ansible +display_skipped_hosts = False + +[ssh_connection] +pipelining = False diff --git a/ansible/.gitignore b/ansible/.gitignore new file mode 100644 index 0000000..7e99e36 --- /dev/null +++ b/ansible/.gitignore @@ -0,0 +1 @@ +*.pyc \ No newline at end of file diff --git a/ansible/firstboot.yml b/ansible/firstboot.yml new file mode 100644 index 0000000..c5d91e3 --- /dev/null +++ b/ansible/firstboot.yml @@ -0,0 +1,43 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- hosts: all + become: yes + vars: + firstboot_custom_roles_pre: "" + firstboot_custom_roles_post: "" + + tasks: + - include_role: + name: "{{ role }}" + loop: "{{ firstboot_custom_roles_pre.split() }}" + loop_control: + loop_var: role + when: firstboot_custom_roles_pre != "" + - include_role: + name: firstboot + - include_role: + name: "{{ role }}" + loop: "{{ firstboot_custom_roles_post.split() }}" + loop_control: + loop_var: role + when: firstboot_custom_roles_post != "" + + environment: + http_proxy: "{{ http_proxy | default('') }}" + https_proxy: "{{ https_proxy | default('') }}" + no_proxy: "{{ no_proxy | default('') }}" + HTTP_PROXY: "{{ http_proxy | default('') }}" + HTTPS_PROXY: "{{ https_proxy | default('') }}" + NO_PROXY: "{{ no_proxy | default('') }}" diff --git a/ansible/node.yml b/ansible/node.yml new file mode 100644 index 0000000..15aa53d --- /dev/null +++ b/ansible/node.yml @@ -0,0 +1,56 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- hosts: all + become: yes + vars: + node_custom_roles_pre: "" + node_custom_roles_post: "" + custom_role_names: "" + + tasks: + - include_role: + name: "{{ role }}" + loop: "{{ node_custom_roles_pre.split() }}" + loop_control: + loop_var: role + when: node_custom_roles_pre != "" + - include_role: + name: node + - include_role: + name: providers + - include_role: + name: containerd + - include_role: + name: kubernetes + - include_role: + name: load_additional_components + when: load_additional_components | bool + - include_role: + name: "{{ role }}" + loop: "{{ custom_role_names.split() + node_custom_roles_post.split() }}" + loop_control: + loop_var: role + when: custom_role_names != "" or node_custom_roles_post != "" + - include_role: + name: sysprep + + environment: + http_proxy: "{{ http_proxy | default('') }}" + https_proxy: "{{ https_proxy | default('') }}" + no_proxy: "{{ no_proxy | default('') }}" + HTTP_PROXY: "{{ http_proxy | default('') }}" + HTTPS_PROXY: "{{ https_proxy | default('') }}" + NO_PROXY: "{{ no_proxy | default('') }}" + PYTHONPATH: "{{ python_path }}" diff --git a/ansible/python.yml b/ansible/python.yml new file mode 100644 index 0000000..023ddc0 --- /dev/null +++ b/ansible/python.yml @@ -0,0 +1,32 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- hosts: all + # Gathering facts requires Python to be available, so it's a chicken and egg + # problem as this playbook installs Python. + gather_facts: no + become: yes + + tasks: + - include_role: + name: python + + environment: + http_proxy: "{{ http_proxy | default('') }}" + https_proxy: "{{ https_proxy | default('') }}" + no_proxy: "{{ no_proxy | default('') }}" + HTTP_PROXY: "{{ http_proxy | default('') }}" + HTTPS_PROXY: "{{ https_proxy | default('') }}" + NO_PROXY: "{{ no_proxy | default('') }}" + PYTHONPATH: "{{ python_path }}" diff --git a/ansible/roles/containerd/defaults/main.yml b/ansible/roles/containerd/defaults/main.yml new file mode 100644 index 0000000..fe1a8c5 --- /dev/null +++ b/ansible/roles/containerd/defaults/main.yml @@ -0,0 +1,15 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +containerd_config_file: "etc/containerd/config.toml" diff --git a/ansible/roles/containerd/tasks/debian.yml b/ansible/roles/containerd/tasks/debian.yml new file mode 100644 index 0000000..f2969d5 --- /dev/null +++ b/ansible/roles/containerd/tasks/debian.yml @@ -0,0 +1,18 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Install libseccomp2 package + apt: + name: libseccomp2 + state: present \ No newline at end of file diff --git a/ansible/roles/containerd/tasks/main.yml b/ansible/roles/containerd/tasks/main.yml new file mode 100644 index 0000000..c7c5df2 --- /dev/null +++ b/ansible/roles/containerd/tasks/main.yml @@ -0,0 +1,169 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- import_tasks: debian.yml + when: ansible_os_family == "Debian" + +- import_tasks: redhat.yml + when: ansible_os_family == "RedHat" + +- import_tasks: photon.yml + when: ansible_os_family == "VMware Photon OS" + +# TODO(vincepri): Use deb/rpm packages once available. +# See https://github.com/containerd/containerd/issues/1508 for context. +- name: download containerd + get_url: + url: "{{ containerd_url }}" + checksum: "sha256:{{ containerd_sha256 }}" + dest: /tmp/containerd.tar.gz + mode: 0600 + +- name: download containerd-wasm-shims + get_url: + url: "{{ containerd_wasm_shims_url }}" + checksum: "sha256:{{ containerd_wasm_shims_sha256 }}" + dest: /tmp/containerd_wasm_shims.tar.gz + mode: 0600 + when: containerd_wasm_shims_runtimes | length > 0 + +- name: Create a directory if it does not exist + file: + path: "{{ sysusr_prefix }}/bin" + state: directory + mode: 0755 + +# TODO(vincepri): This unpacks the entire tar in the root directory +# we should find a better way to check what's being unpacked and where. +- name: unpack containerd + unarchive: + remote_src: True + src: /tmp/containerd.tar.gz + dest: / + extra_opts: + - --no-overwrite-dir + when: ansible_os_family != "Flatcar" + +# install containerd Wasm shims when the runtimes are not empty -- current known runtimes are 'slight' and 'spin' +# see: https://github.com/kubernetes-sigs/image-builder/pull/1037 +- name: unpack containerd-wasm-shims + unarchive: + remote_src: True + src: /tmp/containerd_wasm_shims.tar.gz + dest: "{{ sysusr_prefix }}/bin" + extra_opts: + - --no-overwrite-dir + when: ansible_os_family != "Flatcar" and (containerd_wasm_shims_runtimes | length > 0) + +- name: unpack containerd for Flatcar to /opt/bin + unarchive: + remote_src: True + src: /tmp/containerd.tar.gz + dest: / + extra_opts: + - --absolute-names + - --transform + - 's@usr@opt@' + - --transform + - 's@sbin@bin@' + - --transform + - 's@opt/local@opt@' + when: ansible_os_family == "Flatcar" + +# install containerd Wasm shims when the runtimes are not empty -- current known runtimes are 'slight' and 'spin' +# see: https://github.com/kubernetes-sigs/image-builder/pull/1037 +- name: unpack containerd-wasm-shims for Flatcar to /opt/bin + unarchive: + remote_src: True + src: /tmp/containerd_wasm_shims.tar.gz + dest: "{{ sysusr_prefix }}/bin" + extra_opts: + - --no-overwrite-dir + when: ansible_os_family == "Flatcar" and (containerd_wasm_shims_runtimes | length > 0) + +# Remove /opt/cni directory, as we will install cni later +- name: delete /opt/cni directory + file: + path: /opt/cni + state: absent + +# Remove /etc/cni directory, as we will configure cni later +- name: delete /etc/cni directory + file: + path: /etc/cni + state: absent + +- name: Creates unit file directory + file: + path: /etc/systemd/system/containerd.service.d + state: directory + +- name: Create systemd unit drop-in file for containerd to run from /opt/bin + template: + dest: /etc/systemd/system/containerd.service.d/10-opt-bin-custom.conf + src: etc/systemd/system/containerd-flatcar.conf + mode: 0600 + when: ansible_os_family == "Flatcar" + +- name: Create containerd memory pressure drop in file + template: + dest: /etc/systemd/system/containerd.service.d/memory-pressure.conf + src: etc/systemd/system/containerd.service.d/memory-pressure.conf + mode: 0644 + +- name: Create containerd max tasks drop in file + template: + dest: /etc/systemd/system/containerd.service.d/max-tasks.conf + src: etc/systemd/system/containerd.service.d/max-tasks.conf + mode: 0644 + +- name: Create containerd http proxy conf file if needed + template: + dest: /etc/systemd/system/containerd.service.d/http-proxy.conf + src: etc/systemd/system/containerd.service.d/http-proxy.conf + mode: 0644 + when: http_proxy is defined or https_proxy is defined + +- name: Creates containerd config directory + file: + path: /etc/containerd + state: directory + +- name: Copy in containerd config file {{ containerd_config_file }} + template: + dest: /etc/containerd/config.toml + src: "{{ containerd_config_file }}" + mode: 0644 + +- name: Copy in crictl config + template: + dest: /etc/crictl.yaml + src: etc/crictl.yaml + +- name: start containerd service + systemd: + name: containerd + daemon_reload: yes + enabled: True + state: restarted + +- name: delete tarball + file: + path: /tmp/containerd.tar.gz + state: absent + +- name: delete tarball + file: + path: /tmp/containerd_wasm_shims.tar.gz + state: absent diff --git a/ansible/roles/containerd/tasks/photon.yml b/ansible/roles/containerd/tasks/photon.yml new file mode 100644 index 0000000..b5f21e3 --- /dev/null +++ b/ansible/roles/containerd/tasks/photon.yml @@ -0,0 +1,16 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Install libseccomp package + command: tdnf install libseccomp -y diff --git a/ansible/roles/containerd/tasks/redhat.yml b/ansible/roles/containerd/tasks/redhat.yml new file mode 100644 index 0000000..f144a1e --- /dev/null +++ b/ansible/roles/containerd/tasks/redhat.yml @@ -0,0 +1,19 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Install libseccomp package + yum: + name: libseccomp + state: present + lock_timeout: 60 diff --git a/ansible/roles/containerd/templates/etc/containerd/config.toml b/ansible/roles/containerd/templates/etc/containerd/config.toml new file mode 100644 index 0000000..2e77c0c --- /dev/null +++ b/ansible/roles/containerd/templates/etc/containerd/config.toml @@ -0,0 +1,33 @@ +## template: jinja + +# Use config version 2 to enable new configuration fields. +# Config file is parsed as version 1 by default. +version = 2 + +{% if 'imports' not in containerd_additional_settings | b64decode %} +imports = ["/etc/containerd/conf.d/*.toml"] +{% endif %} + +[plugins] + [plugins."io.containerd.grpc.v1.cri"] + sandbox_image = "{{ pause_image }}" +{% if kubernetes_semver is version('v1.21.0', '>=') %} + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] + runtime_type = "io.containerd.runc.v2" + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] + SystemdCgroup = true +{% if 'spin' in containerd_wasm_shims_runtimes %} + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.spin] + runtime_type = "io.containerd.spin.v1" +{% endif %} +{% if 'slight' in containerd_wasm_shims_runtimes %} + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.slight] + runtime_type = "io.containerd.slight.v1" +{% endif %} +{% endif %} +{% if packer_builder_type.startswith('azure') %} + [plugins."io.containerd.grpc.v1.cri".registry.headers] + X-Meta-Source-Client = ["azure/capz"] +{% endif %} + +{{containerd_additional_settings | b64decode}} diff --git a/ansible/roles/containerd/templates/etc/crictl.yaml b/ansible/roles/containerd/templates/etc/crictl.yaml new file mode 100644 index 0000000..0c2b3fd --- /dev/null +++ b/ansible/roles/containerd/templates/etc/crictl.yaml @@ -0,0 +1 @@ +runtime-endpoint: unix://{{ containerd_cri_socket }} diff --git a/ansible/roles/containerd/templates/etc/systemd/system/containerd-flatcar.conf b/ansible/roles/containerd/templates/etc/systemd/system/containerd-flatcar.conf new file mode 100644 index 0000000..192af7f --- /dev/null +++ b/ansible/roles/containerd/templates/etc/systemd/system/containerd-flatcar.conf @@ -0,0 +1,6 @@ +[Service] +Environment=PATH=/opt/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin +ExecStartPre=mkdir -p /run/docker/libcontainerd +ExecStartPre=ln -fs /run/containerd/containerd.sock /run/docker/libcontainerd/docker-containerd.sock +ExecStart= +ExecStart=/opt/bin/containerd --config /etc/containerd/config.toml diff --git a/ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/http-proxy.conf b/ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/http-proxy.conf new file mode 100644 index 0000000..90e8a8e --- /dev/null +++ b/ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/http-proxy.conf @@ -0,0 +1,10 @@ +[Service] +{% if http_proxy %} +Environment="HTTP_PROXY={{ http_proxy }}" +{% endif %} +{% if https_proxy %} +Environment="HTTPS_PROXY={{ https_proxy }}" +{% endif %} +{% if no_proxy %} +Environment="NO_PROXY={{ no_proxy }}" +{% endif %} diff --git a/ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/max-tasks.conf b/ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/max-tasks.conf new file mode 100644 index 0000000..14aaeaa --- /dev/null +++ b/ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/max-tasks.conf @@ -0,0 +1,3 @@ +[Service] +# Do not limit the number of tasks that can be spawned by containerd +TasksMax=infinity diff --git a/ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/memory-pressure.conf b/ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/memory-pressure.conf new file mode 100644 index 0000000..4345fa7 --- /dev/null +++ b/ansible/roles/containerd/templates/etc/systemd/system/containerd.service.d/memory-pressure.conf @@ -0,0 +1,8 @@ +[Service] +# Decreases the likelihood that containerd is killed due to memory +# pressure. +# +# Please see the following link for more information about the +# OOMScoreAdjust configuration property: +# https://www.freedesktop.org/software/systemd/man/systemd.exec.html#OOMScoreAdjust= +OOMScoreAdjust=-999 diff --git a/ansible/roles/firstboot/README.md b/ansible/roles/firstboot/README.md new file mode 100644 index 0000000..6c06e79 --- /dev/null +++ b/ansible/roles/firstboot/README.md @@ -0,0 +1,2 @@ +This role is to be used for operating systems that require some operations +that require a reboot. diff --git a/ansible/roles/firstboot/defaults b/ansible/roles/firstboot/defaults new file mode 120000 index 0000000..58e5f3d --- /dev/null +++ b/ansible/roles/firstboot/defaults @@ -0,0 +1 @@ +../node/defaults \ No newline at end of file diff --git a/ansible/roles/firstboot/meta/main.yml b/ansible/roles/firstboot/meta/main.yml new file mode 100644 index 0000000..9873725 --- /dev/null +++ b/ansible/roles/firstboot/meta/main.yml @@ -0,0 +1,26 @@ +# Copyright 2022 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +dependencies: + - role: setup + vars: + rpms: "" + debs: "" + when: ansible_os_family == "VMware Photon OS" + + - role: setup + vars: + rpms: "{{ ( ( common_rpms + rh7_rpms + lookup('vars', 'common_' + build_target + '_rpms') ) if (ansible_os_family == 'RedHat' and ansible_distribution_major_version == '7') else ( common_rpms + rh8_rpms + lookup('vars', 'common_' + build_target + '_rpms') ) ) }}" + debs: "{{ common_debs + lookup('vars', 'common_' + build_target + '_debs') }}" + when: packer_builder_type is search('qemu') diff --git a/ansible/roles/firstboot/tasks/main.yaml b/ansible/roles/firstboot/tasks/main.yaml new file mode 100644 index 0000000..c4ec519 --- /dev/null +++ b/ansible/roles/firstboot/tasks/main.yaml @@ -0,0 +1,19 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- include_tasks: photon.yml + when: ansible_os_family == "VMware Photon OS" + +- include_tasks: qemu.yml + when: packer_builder_type is search('qemu') diff --git a/ansible/roles/firstboot/tasks/photon.yml b/ansible/roles/firstboot/tasks/photon.yml new file mode 100644 index 0000000..4f211fa --- /dev/null +++ b/ansible/roles/firstboot/tasks/photon.yml @@ -0,0 +1,24 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# no-op task just to have something for the role to do. Right now +# all the work happens in the setup role +# - meta: noop + +- name: Set transparent huge pages to madvise + lineinfile: + path: /boot/photon.cfg + backrefs: yes + regexp: "^(?!.*transparent_hugepage=madvise)(photon_cmdline.*)" + line: '\1 transparent_hugepage=madvise' diff --git a/ansible/roles/firstboot/tasks/qemu.yml b/ansible/roles/firstboot/tasks/qemu.yml new file mode 100644 index 0000000..d00d1d3 --- /dev/null +++ b/ansible/roles/firstboot/tasks/qemu.yml @@ -0,0 +1,17 @@ +# Copyright 2022 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# no-op task just to have something for the role to do. Right now +# all the work happens in the setup role +- meta: noop diff --git a/ansible/roles/kubernetes/defaults/main.yml b/ansible/roles/kubernetes/defaults/main.yml new file mode 100644 index 0000000..aa5057a --- /dev/null +++ b/ansible/roles/kubernetes/defaults/main.yml @@ -0,0 +1,41 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +kubernetes_goarch: "amd64" + +kubernetes_bins: +- kubeadm +- kubectl +- kubelet + +kubernetes_primary_imgs: +- kube-apiserver.tar +- kube-controller-manager.tar +- kube-scheduler.tar +- kube-proxy.tar + +kubernetes_additional_imgs: +- pause.tar +- coredns.tar +- etcd.tar + +kubernetes_load_additional_imgs: false + +kubernetes_imgs: "{{ kubernetes_primary_imgs | union(kubernetes_additional_imgs) if kubernetes_load_additional_imgs | bool else kubernetes_primary_imgs }}" + +kubernetes_cni_http_checksum: "sha1:{{ kubernetes_cni_http_source }}/{{ kubernetes_cni_semver }}/cni-plugins-{{ kubernetes_goarch }}-{{ kubernetes_cni_semver }}.tgz.sha1" + +kubeadm_template: "etc/kubeadm.yml" + +kubelet_extra_args: "--pod-infra-container-image={{ pause_image }}" \ No newline at end of file diff --git a/ansible/roles/kubernetes/tasks/crictl-url.yml b/ansible/roles/kubernetes/tasks/crictl-url.yml new file mode 100644 index 0000000..9ae4f81 --- /dev/null +++ b/ansible/roles/kubernetes/tasks/crictl-url.yml @@ -0,0 +1,54 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Download crictl checksum + get_url: + url: "{{ crictl_sha256 }}" + dest: /tmp/crictl.tar.gz.sha256 + mode: 0600 + +- name: Register checksum value for crictl + slurp: + src: /tmp/crictl.tar.gz.sha256 + register: csum + +- name: download crictl + vars: + sha256: "{{ csum['content'] | b64decode | trim }}" + get_url: + url: "{{ crictl_url }}" + checksum: "sha256:{{ sha256 }}" + dest: /tmp/crictl.tar.gz + mode: 0600 + +- name: Create "{{ sysusrlocal_prefix }}/bin" directory + file: + state: directory + path: "{{ sysusrlocal_prefix }}/bin" + mode: 0755 + owner: root + group: root + +- name: unpack crictl + unarchive: + remote_src: True + src: /tmp/crictl.tar.gz + dest: "{{ sysusrlocal_prefix }}/bin" + extra_opts: + - --no-overwrite-dir + +- name: Remove crictl tarball + file: + state: absent + path: /tmp/crictl.tar.gz diff --git a/ansible/roles/kubernetes/tasks/debian.yml b/ansible/roles/kubernetes/tasks/debian.yml new file mode 100644 index 0000000..9c491e1 --- /dev/null +++ b/ansible/roles/kubernetes/tasks/debian.yml @@ -0,0 +1,36 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Add the Kubernetes repo key + apt_key: + url: "{{ kubernetes_deb_gpg_key }}" + state: present + +- name: Add the Kubernetes repo + apt_repository: + repo: "deb {{ kubernetes_deb_repo }} main" + update_cache: True + state: present + mode: 0644 + filename: kubernetes + +- name: Install Kubernetes + apt: + name: "{{ packages }}" + vars: + packages: + - "kubelet={{ kubernetes_deb_version }}" + - "kubeadm={{ kubernetes_deb_version }}" + - "kubectl={{ kubernetes_deb_version }}" + - "kubernetes-cni={{ kubernetes_cni_deb_version }}" diff --git a/ansible/roles/kubernetes/tasks/ecrpull.yml b/ansible/roles/kubernetes/tasks/ecrpull.yml new file mode 100644 index 0000000..ec5c403 --- /dev/null +++ b/ansible/roles/kubernetes/tasks/ecrpull.yml @@ -0,0 +1,29 @@ +# TODO: This task will be deprecated once https://github.com/containerd/cri/issues/1131 is fixed +- name: Create kubeadm config file + template: + dest: /etc/kubeadm.yml + src: etc/kubeadm.yml + mode: 0600 + +- name: Get images list + shell: 'kubeadm config images list --config /etc/kubeadm.yml' + register: images_list + +- name: Log into ECR + command: > + aws ecr get-authorization-token + --registry-ids {{ kubernetes_container_registry.split('.')[0] }} + --region {{ kubernetes_container_registry.split('.')[3] }} + --output text + --query 'authorizationData[].authorizationToken' + register: credentials + +- name: Pull images + command: "crictl pull --creds {{ credentials.stdout | b64decode }} {{ item }}" + loop: "{{ images_list.stdout_lines }}" + +- name: Delete kubeadm config + file: + path: /etc/kubeadm.yml + state: absent + when: ansible_os_family != "Flatcar" diff --git a/ansible/roles/kubernetes/tasks/kubeadmpull.yml b/ansible/roles/kubernetes/tasks/kubeadmpull.yml new file mode 100644 index 0000000..c8fcc0d --- /dev/null +++ b/ansible/roles/kubernetes/tasks/kubeadmpull.yml @@ -0,0 +1,14 @@ +- name: Create kubeadm config file + template: + dest: /etc/kubeadm.yml + src: "{{ kubeadm_template }}" + mode: 0600 + +- name: Kubeadm pull images + shell: 'kubeadm config images pull --config /etc/kubeadm.yml --cri-socket {{ containerd_cri_socket }}' + +- name: Delete kubeadm config + file: + path: /etc/kubeadm.yml + state: absent + when: ansible_os_family != "Flatcar" diff --git a/ansible/roles/kubernetes/tasks/main.yml b/ansible/roles/kubernetes/tasks/main.yml new file mode 100644 index 0000000..36d973b --- /dev/null +++ b/ansible/roles/kubernetes/tasks/main.yml @@ -0,0 +1,75 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- import_tasks: debian.yml + when: kubernetes_source_type == "pkg" and ansible_os_family == "Debian" + +- import_tasks: redhat.yml + when: kubernetes_source_type == "pkg" and ansible_os_family == "RedHat" + +- import_tasks: photon.yml + when: kubernetes_source_type == "pkg" and ansible_os_family == "VMware Photon OS" + +- name: Symlink cri-tools + file: + src: "/usr/local/bin/{{ item }}" + dest: "/usr/bin/{{ item }}" + mode: 0777 + state: link + force: yes + loop: + - ctr + - crictl + - critest + when: ansible_os_family != "Flatcar" + +- import_tasks: url.yml + when: kubernetes_source_type == "http" and kubernetes_cni_source_type == "http" + +# must include crictl-url.yml after installing containerd, +# as the cri-containerd tarball also includes crictl. +- import_tasks: crictl-url.yml + when: crictl_source_type == "http" + +- name: Create kubelet default config file + template: + src: etc/sysconfig/kubelet + dest: "{{ '/etc/default/kubelet' if ansible_os_family == 'Debian' else '/etc/sysconfig/kubelet'}}" + owner: root + group: root + mode: 0644 + +- name: Enable kubelet service + systemd: + name: kubelet + daemon_reload: yes + enabled: True + state: stopped + +- name: Create the Kubernetes version file + template: + dest: /etc/kubernetes-version + src: etc/kubernetes-version + mode: 0644 + +# TODO: This section will be deprecated once https://github.com/containerd/cri/issues/1131 is fixed. It is used to support ECR with containerd. +- name: Check if Kubernetes container registry is using Amazon ECR + set_fact: + ecr: '{{ kubernetes_container_registry is regex("^[0-9]{12}.dkr.ecr.[^.]+.amazonaws.com$") }}' + +- import_tasks: kubeadmpull.yml + when: (kubernetes_source_type == "pkg" and ecr != true) or ansible_os_family == "Flatcar" + +- import_tasks: ecrpull.yml + when: kubernetes_source_type != "http" and ecr == true diff --git a/ansible/roles/kubernetes/tasks/photon.yml b/ansible/roles/kubernetes/tasks/photon.yml new file mode 100644 index 0000000..ebae5d3 --- /dev/null +++ b/ansible/roles/kubernetes/tasks/photon.yml @@ -0,0 +1,24 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Add the kubernetes repo + template: + dest: /etc/yum.repos.d/kubernetes.repo + src: etc/yum.repos.d/kubernetes.repo + mode: 0644 + +- name: Install Kubernetes + command: tdnf install {{ packages }} --nogpgcheck -y + vars: + packages: "kubelet-{{ kubernetes_rpm_version }} kubeadm-{{ kubernetes_rpm_version }} kubectl-{{ kubernetes_rpm_version }} kubernetes-cni-{{kubernetes_cni_rpm_version }}" diff --git a/ansible/roles/kubernetes/tasks/redhat.yml b/ansible/roles/kubernetes/tasks/redhat.yml new file mode 100644 index 0000000..3f5eb85 --- /dev/null +++ b/ansible/roles/kubernetes/tasks/redhat.yml @@ -0,0 +1,34 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Add the Kubernetes repo + yum_repository: + name: kubernetes + description: the kubernetes yum repo + baseurl: "{{ kubernetes_rpm_repo }}" + gpgcheck: "{{ kubernetes_rpm_gpg_check }}" + gpgkey: "{{ kubernetes_rpm_gpg_key }}" + +- name: Install Kubernetes + yum: + name: "{{ packages }}" + allow_downgrade: True + state: present + lock_timeout: 60 + vars: + packages: + - "kubelet-{{ kubernetes_rpm_version }}" + - "kubeadm-{{ kubernetes_rpm_version }}" + - "kubectl-{{ kubernetes_rpm_version }}" + - "kubernetes-cni-{{kubernetes_cni_rpm_version }}" diff --git a/ansible/roles/kubernetes/tasks/url.yml b/ansible/roles/kubernetes/tasks/url.yml new file mode 100644 index 0000000..4176db4 --- /dev/null +++ b/ansible/roles/kubernetes/tasks/url.yml @@ -0,0 +1,115 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Create CNI directory + file: + state: directory + path: /opt/cni/bin + mode: 0755 + owner: root + group: root + +- name: Download CNI tarball + get_url: + url: "{{ kubernetes_cni_http_source }}/{{ kubernetes_cni_semver }}/cni-plugins-linux-{{ kubernetes_goarch }}-{{ kubernetes_cni_semver }}.tgz" + checksum: "{{ kubernetes_cni_http_checksum }}" + dest: /tmp/cni.tar.gz + mode: 0755 + owner: root + group: root + +- name: Install CNI + unarchive: + remote_src: yes + dest: /opt/cni/bin + src: /tmp/cni.tar.gz + +- name: Remove CNI tarball + file: + state: absent + path: /tmp/cni.tar.gz + +- name: Download Kubernetes binaries + get_url: + url: "{{ kubernetes_http_source }}/{{ kubernetes_semver }}/bin/linux/{{ kubernetes_goarch }}/{{ item }}" + # TODO(akutz) Write a script to separately download the checksum + # and verify the associated file using the correct + # checksum file format + #checksum: "sha1:{{ kubernetes_http_source }}/bin/linux/amd64/{{ item }}.sha1" + dest: "{{ sysusr_prefix }}/bin/{{ item }}" + mode: 0755 + owner: root + group: root + loop: "{{ kubernetes_bins }}" + +- name: Download Kubernetes images + get_url: + url: "{{ kubernetes_http_source }}/{{ kubernetes_semver }}/bin/linux/{{ kubernetes_goarch }}/{{ item }}" + # TODO(akutz) Write a script to separately download the checksum + # and verify the associated file using the correct + # checksum file format + #checksum: "sha1:{{ kubernetes_http_source }}/bin/linux/amd64/{{ item }}.sha1" + dest: "/tmp/{{ item }}" + mode: 0600 + loop: "{{ kubernetes_imgs }}" + +- name: Load Kubernetes images + shell: 'CONTAINERD_NAMESPACE="k8s.io" {{ sysusr_prefix }}/bin/ctr --address={{ containerd_cri_socket }} images import /tmp/{{ item }}' + loop: "{{ kubernetes_imgs }}" + +- name: Remove Kubernetes images + file: + state: absent + path: "/tmp/{{ item }}" + loop: "{{ kubernetes_imgs }}" + +- name: Create Kubernetes manifests directory + file: + state: directory + path: /etc/kubernetes/manifests + mode: 0755 + owner: root + group: root + +- name: Create kubelet sysconfig directory + file: + state: directory + path: /etc/sysconfig + mode: 0755 + owner: root + group: root + +- name: Create kubelet drop-in directory + file: + state: directory + path: "{{ systemd_prefix }}/system/kubelet.service.d" + mode: 0755 + owner: root + group: root + +- name: Create kubelet kubeadm drop-in file + template: + src: usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf + dest: "{{ systemd_prefix }}/system/kubelet.service.d/10-kubeadm.conf" + owner: root + group: root + mode: 0644 + +- name: Create kubelet systemd file + template: + src: usr/lib/systemd/system/kubelet.service + dest: "{{ systemd_prefix }}/system/kubelet.service" + owner: root + group: root + mode: 0644 diff --git a/ansible/roles/kubernetes/templates/etc/kubeadm.yml b/ansible/roles/kubernetes/templates/etc/kubeadm.yml new file mode 100644 index 0000000..e21303a --- /dev/null +++ b/ansible/roles/kubernetes/templates/etc/kubeadm.yml @@ -0,0 +1,11 @@ +apiVersion: kubeadm.k8s.io/v1beta2 +kind: ClusterConfiguration +imageRepository: {{ kubernetes_container_registry }} +kubernetesVersion: {{ kubernetes_semver }} +dns: + imageRepository: {{ kubernetes_container_registry }}/coredns +--- +apiVersion: kubeadm.k8s.io/v1beta2 +kind: InitConfiguration +nodeRegistration: + criSocket: {{ containerd_cri_socket }} diff --git a/ansible/roles/kubernetes/templates/etc/kubernetes-version b/ansible/roles/kubernetes/templates/etc/kubernetes-version new file mode 100644 index 0000000..06f9cac --- /dev/null +++ b/ansible/roles/kubernetes/templates/etc/kubernetes-version @@ -0,0 +1 @@ +{{ kubernetes_semver }} \ No newline at end of file diff --git a/ansible/roles/kubernetes/templates/etc/sysconfig/kubelet b/ansible/roles/kubernetes/templates/etc/sysconfig/kubelet new file mode 100644 index 0000000..290bb5a --- /dev/null +++ b/ansible/roles/kubernetes/templates/etc/sysconfig/kubelet @@ -0,0 +1 @@ +KUBELET_EXTRA_ARGS={{ kubelet_extra_args }} \ No newline at end of file diff --git a/ansible/roles/kubernetes/templates/etc/yum.repos.d/kubernetes.repo b/ansible/roles/kubernetes/templates/etc/yum.repos.d/kubernetes.repo new file mode 100644 index 0000000..0a6904c --- /dev/null +++ b/ansible/roles/kubernetes/templates/etc/yum.repos.d/kubernetes.repo @@ -0,0 +1,7 @@ +[kubernetes] +name=kubernetes +description=the kubernetes yum repo +baseurl={{ kubernetes_rpm_repo }} +gpgcheck={{ kubernetes_rpm_gpg_check }} +gpgkey={{ kubernetes_rpm_gpg_key }} +enabled=1 diff --git a/ansible/roles/kubernetes/templates/usr/lib/systemd/system/kubelet.service b/ansible/roles/kubernetes/templates/usr/lib/systemd/system/kubelet.service new file mode 100644 index 0000000..28ab711 --- /dev/null +++ b/ansible/roles/kubernetes/templates/usr/lib/systemd/system/kubelet.service @@ -0,0 +1,14 @@ +[Unit] +Description=kubelet: The Kubernetes Node Agent +Documentation=https://kubernetes.io/docs/home/ +Wants=network-online.target +After=network-online.target + +[Service] +ExecStart={{ sysusr_prefix }}/bin/kubelet +Restart=always +StartLimitInterval=0 +RestartSec=10 + +[Install] +WantedBy=multi-user.target diff --git a/ansible/roles/kubernetes/templates/usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf b/ansible/roles/kubernetes/templates/usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf new file mode 100644 index 0000000..165becb --- /dev/null +++ b/ansible/roles/kubernetes/templates/usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf @@ -0,0 +1,11 @@ +# Note: This dropin only works with kubeadm and kubelet v1.11+ +[Service] +Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" +Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml" +# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically +EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env +# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use +# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file. +EnvironmentFile=-/etc/sysconfig/kubelet +ExecStart= +ExecStart={{ sysusr_prefix }}/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS diff --git a/ansible/roles/load_additional_components/defaults/main.yml b/ansible/roles/load_additional_components/defaults/main.yml new file mode 100644 index 0000000..44aa745 --- /dev/null +++ b/ansible/roles/load_additional_components/defaults/main.yml @@ -0,0 +1,22 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- + +additional_registry_images: "" +additional_registry_images_list: "" +additional_url_images: "" +additional_url_images_list: "" +additional_executables: "" +additional_executables_list: "" +additional_executables_destination_path: "" \ No newline at end of file diff --git a/ansible/roles/load_additional_components/tasks/executables.yml b/ansible/roles/load_additional_components/tasks/executables.yml new file mode 100644 index 0000000..77e5adb --- /dev/null +++ b/ansible/roles/load_additional_components/tasks/executables.yml @@ -0,0 +1,22 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Download additional executables + get_url: + url: "{{ item }}" + dest: "{{ additional_executables_destination_path }}" + mode: 0711 + loop: "{{ additional_executables_list.split(',') }}" + retries: 5 + delay: 3 \ No newline at end of file diff --git a/ansible/roles/load_additional_components/tasks/main.yml b/ansible/roles/load_additional_components/tasks/main.yml new file mode 100644 index 0000000..8b06531 --- /dev/null +++ b/ansible/roles/load_additional_components/tasks/main.yml @@ -0,0 +1,23 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- import_tasks: executables.yml + when: additional_executables | bool + +- import_tasks: registry.yml + when: additional_registry_images | bool + +- import_tasks: url.yml + when: additional_url_images | bool + diff --git a/ansible/roles/load_additional_components/tasks/registry.yml b/ansible/roles/load_additional_components/tasks/registry.yml new file mode 100644 index 0000000..2e25045 --- /dev/null +++ b/ansible/roles/load_additional_components/tasks/registry.yml @@ -0,0 +1,19 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Pull additional images from registry + shell: 'CONTAINERD_NAMESPACE="k8s.io" {{ sysusr_prefix }}/bin/ctr --address={{ containerd_cri_socket }} images pull {{ item }}' + loop: "{{ additional_registry_images_list.split(',') }}" + retries: 5 + delay: 3 diff --git a/ansible/roles/load_additional_components/tasks/url.yml b/ansible/roles/load_additional_components/tasks/url.yml new file mode 100644 index 0000000..05b361c --- /dev/null +++ b/ansible/roles/load_additional_components/tasks/url.yml @@ -0,0 +1,37 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Create temporary download dir + file: + path: /tmp/images + state: directory + +- name: Download additional images from url + get_url: + url: "{{ item }}" + dest: "/tmp/images/" + mode: 0600 + register: images + loop: "{{ additional_url_images_list.split(',') }}" + retries: 5 + delay: 3 + +- name: Load additional images + shell: 'CONTAINERD_NAMESPACE="k8s.io" {{ sysusr_prefix }}/bin/ctr --address={{ containerd_cri_socket }} images import --no-unpack {{ item.dest }}' + loop: "{{ images.results }}" + +- name: Remove downloaded files + file: + state: absent + path: "/tmp/images" diff --git a/ansible/roles/node/defaults/main.yml b/ansible/roles/node/defaults/main.yml new file mode 100644 index 0000000..08bdb0d --- /dev/null +++ b/ansible/roles/node/defaults/main.yml @@ -0,0 +1,118 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +common_rpms: +- audit +- ca-certificates +- conntrack-tools +- chrony +- curl +- jq +- python3-pip +- socat +- sysstat +- yum-utils + +# Used for AmazonLinux-2 distributions +al2_rpms: +- ebtables +- python-netifaces +- python-requests + +# Used for RedHat based distributions == 7 (ex. RHEL-7, CentOS-7 etc.) +rh7_rpms: +- ebtables +- python-netifaces +- python-requests + +# Used for RedHat based distributions == 8 (ex. RHEL-8, RockyLinux-8 etc.) +rh8_rpms: +- nftables +- python3-netifaces +- python3-requests + +common_debs: +- auditd +- apt-transport-https +- conntrack +- chrony +- curl +- ebtables +- jq +- gnupg +- libnetfilter-acct1 +- libnetfilter-cttimeout1 +- libnetfilter-log1 +- python3-distutils +- python3-netifaces +- python3-pip +- socat + +common_photon_rpms: +- audit +- apparmor-parser +- conntrack-tools +- chrony +- dbus-python3 +- distrib-compat +- ebtables +- net-tools +- openssl-c_rehash +- python3-pygobject +- python3-pip +- rng-tools +- socat +- tar +- unzip +- curl + +photon_3_rpms: +- python-netifaces +- python-requests +- jq + +# Creating photon_4_rpms for adding future packages if needed. +# Since empty list errors out, jq is added. +photon_4_rpms: +- jq + +common_virt_rpms: +- open-vm-tools + +common_virt_debs: +- linux-cloud-tools-virtual +- linux-tools-virtual +- open-vm-tools + +common_virt_photon_rpms: +- open-vm-tools + +common_raw_rpms: [] + +common_raw_debs: +- linux-cloud-tools-generic +- linux-tools-generic + +common_raw_photon_rpms: [] + +#photon does not have backward compatibility for legacy distro behavior for sysctl.conf by default +#as it uses systemd-sysctl. set this var so we can use for sysctl conf file value. +sysctl_conf_file: "{{ '/etc/sysctl.d/99-sysctl.conf' if ansible_os_family == 'VMware Photon OS' else '/etc/sysctl.conf' }}" + +pause_image: "registry.k8s.io/pause:3.9" +containerd_additional_settings: null +leak_local_mdns_to_dns: false +build_target: "virt" +cloud_cfg_file: "/etc/cloud/cloud.cfg" +external_binary_path: "{{ '/opt/bin' if ansible_os_family == 'Flatcar' else '/usr/local/bin' }}" diff --git a/ansible/roles/node/files/etc/audit/rules.d/containerd.rules b/ansible/roles/node/files/etc/audit/rules.d/containerd.rules new file mode 100644 index 0000000..1d1a3d4 --- /dev/null +++ b/ansible/roles/node/files/etc/audit/rules.d/containerd.rules @@ -0,0 +1,10 @@ +-w /var/lib/containerd/ -p rwxa -k containerd +-w /etc/containerd/ -p rwxa -k containerd +-w /etc/systemd/system/containerd.service -p rwxa -k containerd +-w /etc/systemd/system/containerd.service.d/ -p rwxa -k containerd +-w /run/containerd/ -p rwxa -k containerd +-w /usr/local/bin/containerd-shim -p rwxa -k containerd +-w /usr/local/bin/containerd-shim-runc-v1 -p rwxa -k containerd +-w /usr/local/bin/containerd-shim-runc-v2 -p rwxa -k containerd +-w /usr/local/sbin/runc -p rwxa -k containerd +-w /usr/local/bin/containerd -p rwxa -k containerd diff --git a/ansible/roles/node/files/etc/audit/rules.d/containerd.rules-flatcar b/ansible/roles/node/files/etc/audit/rules.d/containerd.rules-flatcar new file mode 100644 index 0000000..2add8c1 --- /dev/null +++ b/ansible/roles/node/files/etc/audit/rules.d/containerd.rules-flatcar @@ -0,0 +1,10 @@ +-w /var/lib/containerd/ -p rwxa -k containerd +-w /etc/containerd/ -p rwxa -k containerd +-w /etc/systemd/system/containerd.service -p rwxa -k containerd +-w /etc/systemd/system/containerd.service.d/ -p rwxa -k containerd +-w /run/containerd/ -p rwxa -k containerd +-w /opt/bin/containerd-shim -p rwxa -k containerd +-w /opt/bin/containerd-shim-runc-v1 -p rwxa -k containerd +-w /opt/bin/containerd-shim-runc-v2 -p rwxa -k containerd +-w /opt/bin/runc -p rwxa -k containerd +-w /opt/bin/containerd -p rwxa -k containerd diff --git a/ansible/roles/node/files/usr/local/bin/etcd-network-tuning.sh b/ansible/roles/node/files/usr/local/bin/etcd-network-tuning.sh new file mode 100755 index 0000000..0371381 --- /dev/null +++ b/ansible/roles/node/files/usr/local/bin/etcd-network-tuning.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Copyright 2022 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit # exits immediately on any unexpected error (does not bypass traps) +set -o nounset # will error if variables are used without first being defined +set -o pipefail # any non-zero exit code in a piped command causes the pipeline to fail with that code + +trap on_exit ERR +on_exit() { + echo "Error setting etcd network tuning parameters for interface: ${DEV}" | systemd-cat -p emerg -t etcd-tuning +} + +if [ "$#" -ne 1 ]; then + echo "Error: Usage: $0 " | systemd-cat -p emerg -t etcd-tuning + exit 1 +fi + +DEV=$1 +echo "Setting etcd network tuning parameters for interface: ${DEV}" | systemd-cat -p info -t etcd-tuning +tc qdisc add dev ${DEV} root handle 1: prio bands 3 +tc filter add dev ${DEV} parent 1: protocol ip prio 1 u32 match ip sport 2380 0xffff flowid 1:1 +tc filter add dev ${DEV} parent 1: protocol ip prio 1 u32 match ip dport 2380 0xffff flowid 1:1 +tc filter add dev ${DEV} parent 1: protocol ip prio 2 u32 match ip sport 2379 0xffff flowid 1:1 +tc filter add dev ${DEV} parent 1: protocol ip prio 2 u32 match ip dport 2379 0xffff flowid 1:1 + diff --git a/ansible/roles/node/meta/main.yml b/ansible/roles/node/meta/main.yml new file mode 100644 index 0000000..62fb141 --- /dev/null +++ b/ansible/roles/node/meta/main.yml @@ -0,0 +1,38 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +dependencies: + - role: setup + vars: + rpms: "{{ common_rpms + al2_rpms + lookup('vars', 'common_' + build_target + '_rpms') }}" + debs: "{{ common_debs }}" + when: ansible_distribution == "Amazon" + + - role: setup + vars: + rpms: "{{ common_rpms }}" + debs: "{{ common_debs }}" + when: packer_builder_type == "oracle-oci" and ansible_architecture == "aarch64" + + - role: setup + vars: + rpms: "{{ ( common_photon_rpms + photon_3_rpms + lookup('vars', 'common_' + build_target + '_photon_rpms') ) if (ansible_os_family == 'VMware Photon OS' and ansible_distribution_major_version == '3') else (common_photon_rpms + photon_4_rpms + lookup('vars', 'common_' + build_target + '_photon_rpms')) }}" + when: ansible_distribution == "VMware Photon OS" + + - role: setup + vars: + rpms: "{{ ( ( common_rpms + rh7_rpms + lookup('vars', 'common_' + build_target + '_rpms') ) if (ansible_os_family == 'RedHat' and ansible_distribution_major_version == '7') else ( common_rpms + rh8_rpms + lookup('vars', 'common_' + build_target + '_rpms') ) ) }}" + debs: "{{ common_debs + lookup('vars', 'common_' + build_target + '_debs') }}" + when: ansible_distribution != "VMware Photon OS" and ansible_distribution != "Amazon" and not (packer_builder_type == "oracle-oci" and ansible_architecture == "aarch64") and + not packer_builder_type is search('qemu') diff --git a/ansible/roles/node/tasks/amazonLinux2.yml b/ansible/roles/node/tasks/amazonLinux2.yml new file mode 100644 index 0000000..aa2bc33 --- /dev/null +++ b/ansible/roles/node/tasks/amazonLinux2.yml @@ -0,0 +1,28 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +- name: Ensure sysstat is running and comes on at reboot + service: + name: sysstat + state: started + enabled: yes + +# images need to be immutable once built +# https://aws.amazon.com/amazon-linux-ami/faqs/ +- name: Disable security updates on boot + lineinfile: + path: "{{ cloud_cfg_file }}" + regexp: "^repo_upgrade: security" + line: 'repo_upgrade: none' diff --git a/ansible/roles/node/tasks/main.yml b/ansible/roles/node/tasks/main.yml new file mode 100644 index 0000000..a010193 --- /dev/null +++ b/ansible/roles/node/tasks/main.yml @@ -0,0 +1,133 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- import_tasks: photon.yml + when: ansible_os_family == "VMware Photon OS" + +- import_tasks: amazonLinux2.yml + when: ansible_distribution == "Amazon" + +# This is required until https://github.com/ansible/ansible/issues/77537 is fixed and used. +- name: Override Flatcar's OS family + set_fact: + ansible_os_family: Flatcar + when: ansible_os_family == "Flatcar Container Linux by Kinvolk" + tags: + - facts + +- name: Ensure overlay module is present + modprobe: + name: overlay + state: present + +- name: Ensure br_netfilter module is present + modprobe: + name: br_netfilter + state: present + +- name: Persist required kernel modules + copy: + content: | + overlay + br_netfilter + dest: /etc/modules-load.d/kubernetes.conf + mode: 0644 + +- name: Set and persist kernel params + sysctl: + name: "{{ item.param }}" + value: "{{ item.val }}" + state: present + sysctl_set: yes + sysctl_file: "{{ sysctl_conf_file }}" + reload: yes + loop: + - { param: net.bridge.bridge-nf-call-iptables, val: 1 } + - { param: net.bridge.bridge-nf-call-ip6tables, val: 1 } + - { param: net.ipv4.ip_forward, val: 1 } + - { param: net.ipv6.conf.all.forwarding, val: 1 } + - { param: net.ipv6.conf.all.disable_ipv6, val: 0 } + - { param: net.ipv4.tcp_congestion_control, val: bbr } + - { param: vm.overcommit_memory, val: 1 } + - { param: kernel.panic, val: 10 } + - { param: kernel.panic_on_oops, val: 1 } + +- name: Disable swap memory + shell: | + swapoff -a + when: ansible_memory_mb.swap.total != 0 + +- name: Edit fstab file to disable swap + shell: sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab + when: ansible_memory_mb.swap.total != 0 + +- name: Disable conntrackd service + systemd: + name: conntrackd + state: stopped + enabled: false + when: ansible_os_family != "Debian" and ansible_os_family != "Flatcar" + +- name: Ensure auditd is running and comes on at reboot + service: + name: auditd + state: started + enabled: yes + +- name: configure auditd rules for containerd + copy: + src: etc/audit/rules.d/containerd.rules + dest: /etc/audit/rules.d/containerd.rules + owner: root + group: root + mode: 0644 + when: ansible_os_family != "Flatcar" + +- name: configure auditd rules for containerd (Flatcar) + copy: + src: etc/audit/rules.d/containerd.rules-flatcar + dest: /etc/audit/rules.d/containerd.rules + owner: root + group: root + mode: 0644 + when: ansible_os_family == "Flatcar" + +- name: Ensure reverse packet filtering is set as strict + sysctl: + name: net.ipv4.conf.all.rp_filter + value: "1" + state: present + sysctl_set: yes + reload: yes + when: ansible_distribution == "Ubuntu" + +- name: Set transparent huge pages to madvise + lineinfile: + path: /etc/default/grub + backrefs: yes + regexp: "^(?!.*transparent_hugepage=madvise)(GRUB_CMDLINE_LINUX=.*)(\"$)" + line: '\1 transparent_hugepage=madvise"' + when: ansible_os_family == "RedHat" + +- name: Copy udev etcd network tuning rules + template: + src: etc/udev/rules.d/90-etcd-tuning.rules + dest: /etc/udev/rules.d/90-etcd-tuning.rules + mode: 0744 + +- name: Copy etcd network tuning script + copy: + src: usr/local/bin/etcd-network-tuning.sh + dest: "{{ external_binary_path }}/etcd-network-tuning.sh" + mode: 0755 diff --git a/ansible/roles/node/tasks/photon.yml b/ansible/roles/node/tasks/photon.yml new file mode 100644 index 0000000..ecf76ba --- /dev/null +++ b/ansible/roles/node/tasks/photon.yml @@ -0,0 +1,53 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +- name: Leak mDNS to DNS (cloud-init-nics) (enable .local domain lookups) + ini_file: + path: /etc/systemd/network/10-cloud-init-nics.network + section: Network + option: Domains + value: "~local" + when: leak_local_mdns_to_dns + +- name: Leak mDNS to DNS (dhcp) (enable .local domain lookups) + ini_file: + path: /etc/systemd/network/99-dhcp-en.network + section: Network + option: Domains + value: "~local" + when: leak_local_mdns_to_dns + +- name: Double TCP small queue limit to be the same as Ubuntu + sysctl: + name: net.ipv4.tcp_limit_output_bytes + value: "524288" + state: present + sysctl_set: yes + reload: yes + sysctl_file: "{{ sysctl_conf_file }}" + +- name: Disable Apparmor service + systemd: + name: apparmor + daemon_reload: yes + enabled: false + state: stopped + +- name: Disable Apparmor in kernel + lineinfile: + path: /boot/photon.cfg + backrefs: yes + regexp: "^(?!.*apparmor=0)(photon_cmdline.*)" + line: '\1 apparmor=0' diff --git a/ansible/roles/node/templates/etc/udev/rules.d/90-etcd-tuning.rules b/ansible/roles/node/templates/etc/udev/rules.d/90-etcd-tuning.rules new file mode 100644 index 0000000..f010162 --- /dev/null +++ b/ansible/roles/node/templates/etc/udev/rules.d/90-etcd-tuning.rules @@ -0,0 +1,15 @@ +# Copyright 2022 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ACTION=="add", SUBSYSTEM=="net", SUBSYSTEMS=="pci|xen|vmbus" RUN+="{{ external_binary_path }}/etcd-network-tuning.sh $name" diff --git a/ansible/roles/providers/defaults/main.yml b/ansible/roles/providers/defaults/main.yml new file mode 100644 index 0000000..66825bc --- /dev/null +++ b/ansible/roles/providers/defaults/main.yml @@ -0,0 +1,17 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +networkd_dispatcher_download_url: "https://gitlab.com/craftyguy/networkd-dispatcher/-/archive/2.1/networkd-dispatcher-2.1.tar.bz2" +packer_builder_type: "" +build_target: "virt" diff --git a/ansible/roles/providers/files/etc/azure/iptables b/ansible/roles/providers/files/etc/azure/iptables new file mode 100644 index 0000000..a79b6df --- /dev/null +++ b/ansible/roles/providers/files/etc/azure/iptables @@ -0,0 +1,8 @@ +*filter +:INPUT ACCEPT [0:0] +:FORWARD ACCEPT [0:0] +:OUTPUT ACCEPT [0:0] +-A FORWARD -d 168.63.129.16/32 -p tcp -m tcp --dport 80 -m comment --comment "block traffic to 168.63.129.16 for cve-2021-27075" -j DROP +-A OUTPUT -d 168.63.129.16/32 -p tcp -m owner --uid-owner 0 -j ACCEPT +-A OUTPUT -d 168.63.129.16/32 -p tcp -m conntrack --ctstate INVALID,NEW -j DROP +COMMIT diff --git a/ansible/roles/providers/files/etc/cloud/cloud.cfg.d/05_logging.cfg b/ansible/roles/providers/files/etc/cloud/cloud.cfg.d/05_logging.cfg new file mode 100644 index 0000000..02f0107 --- /dev/null +++ b/ansible/roles/providers/files/etc/cloud/cloud.cfg.d/05_logging.cfg @@ -0,0 +1,67 @@ +## This yaml formated config file handles setting +## logger information. The values that are necessary to be set +## are seen at the bottom. The top '_log' are only used to remove +## redundency in a syslog and fallback-to-file case. +## +## The 'log_cfgs' entry defines a list of logger configs +## Each entry in the list is tried, and the first one that +## works is used. If a log_cfg list entry is an array, it will +## be joined with '\n'. +_log: + - &log_base | + [loggers] + keys=root,cloudinit + + [handlers] + keys=consoleHandler,cloudLogHandler + + [formatters] + keys=simpleFormatter,arg0Formatter + + [logger_root] + level=DEBUG + formatter=arg0Formatter + handlers=consoleHandler,cloudLogHandler + + [logger_cloudinit] + level=DEBUG + qualname=cloudinit + handlers= + propagate=1 + + [handler_consoleHandler] + class=StreamHandler + level=WARNING + formatter=arg0Formatter + args=(sys.stderr,) + + [formatter_arg0Formatter] + format=%(asctime)s - %(filename)s[%(levelname)s]: %(message)s + + [formatter_simpleFormatter] + format=[CLOUDINIT]%(asctime)s - %(filename)s[%(levelname)s]: %(message)s + - &log_file | + [handler_cloudLogHandler] + class=FileHandler + level=DEBUG + formatter=arg0Formatter + args=('/var/log/cloud-init.log',) + - &log_syslog | + [handler_cloudLogHandler] + class=handlers.SysLogHandler + level=DEBUG + formatter=simpleFormatter + args=("/dev/log", handlers.SysLogHandler.LOG_USER) + +log_cfgs: +# These will be joined into a string that defines the configuration + - [ *log_base, *log_syslog ] +# These will be joined into a string that defines the configuration + - [ *log_base, *log_file ] +# A file path can also be used +# - /etc/log.conf + +# this tells cloud-init to redirect its stdout and stderr to +# 'tee -a /var/log/cloud-init-output.log' so the user can see output +# there without needing to look on the console. +output: {all: '| python3 -c ''import sys,time;sys.stdout.write("".join(( " ".join((time.strftime("[%Y-%m-%d %H:%M:%S]", time.localtime()), line)) for line in sys.stdin )))'' | tee -a /var/log/cloud-init-output.log'} diff --git a/ansible/roles/providers/files/etc/cloud/cloud.cfg.d/99_metadata.cfg b/ansible/roles/providers/files/etc/cloud/cloud.cfg.d/99_metadata.cfg new file mode 100644 index 0000000..57b3ea0 --- /dev/null +++ b/ansible/roles/providers/files/etc/cloud/cloud.cfg.d/99_metadata.cfg @@ -0,0 +1,2 @@ +disable-ec2-metadata: false +datasource_list: [ Outscale ] diff --git a/ansible/roles/providers/files/etc/networkd-dispatcher/no-carrier.d/20-chrony.j2 b/ansible/roles/providers/files/etc/networkd-dispatcher/no-carrier.d/20-chrony.j2 new file mode 100644 index 0000000..0a08273 --- /dev/null +++ b/ansible/roles/providers/files/etc/networkd-dispatcher/no-carrier.d/20-chrony.j2 @@ -0,0 +1,26 @@ +#!/bin/bash + +# This is a networkd-dispatcher script for chronyd to handle its NTP +# sources. It sets the NTP sources online or offline when a network +# interface is configured or removed. On DHCP change, chrony will +# update its NTP sources passed from DHCP options. + +export LC_ALL=C + +DHCP_SERVER_FILE={{ server_dir }}/chrony.servers.$IFACE + +clear_servers_from_dhcp() { + if [ -f "$DHCP_SERVER_FILE" ]; then + rm -f "$DHCP_SERVER_FILE" + {{ chrony_helper_dir }}/chrony-helper update-daemon || : + fi +} + +if [ "$STATE" = "no-carrier" ]; then + clear_servers_from_dhcp + # The onoffline command tells chronyd to switch all sources to + # the online (routable) or offline (off) status according to the current network configuration. + chronyc onoffline > /dev/null 2>&1 +fi + +exit 0 \ No newline at end of file diff --git a/ansible/roles/providers/files/etc/networkd-dispatcher/off.d/20-chrony.j2 b/ansible/roles/providers/files/etc/networkd-dispatcher/off.d/20-chrony.j2 new file mode 100644 index 0000000..9e276b4 --- /dev/null +++ b/ansible/roles/providers/files/etc/networkd-dispatcher/off.d/20-chrony.j2 @@ -0,0 +1,26 @@ +#!/bin/bash + +# This is a networkd-dispatcher script for chronyd to handle its NTP +# sources. It sets the NTP sources online or offline when a network +# interface is configured or removed. On DHCP change, chrony will +# update its NTP sources passed from DHCP options. + +export LC_ALL=C + +DHCP_SERVER_FILE={{ server_dir }}/chrony.servers.$IFACE + +clear_servers_from_dhcp() { + if [ -f "$DHCP_SERVER_FILE" ]; then + rm -f "$DHCP_SERVER_FILE" + {{ chrony_helper_dir }}/chrony-helper update-daemon || : + fi +} + +if [ "$STATE" = "off" ]; then + clear_servers_from_dhcp + # The onoffline command tells chronyd to switch all sources to + # the online (routable) or offline (off) status according to the current network configuration. + chronyc onoffline > /dev/null 2>&1 +fi + +exit 0 \ No newline at end of file diff --git a/ansible/roles/providers/files/etc/networkd-dispatcher/routable.d/20-chrony.j2 b/ansible/roles/providers/files/etc/networkd-dispatcher/routable.d/20-chrony.j2 new file mode 100644 index 0000000..c67dbda --- /dev/null +++ b/ansible/roles/providers/files/etc/networkd-dispatcher/routable.d/20-chrony.j2 @@ -0,0 +1,27 @@ +#!/bin/bash + +# This is a networkd-dispatcher script for chronyd to handle its NTP +# sources. It sets the NTP sources online or offline when a network +# interface is configured or removed. On DHCP change, chrony will +# update its NTP sources passed from DHCP options. + +export LC_ALL=C + +DHCP_SERVER_FILE={{ server_dir }}/chrony.servers.$IFACE + +add_servers_from_dhcp() { + if [ -f "$DHCP_SERVER_FILE" ]; then + rm -f "$DHCP_SERVER_FILE" + fi + echo "$json" | jq -r 'select(.NTP !=null) .NTP[]' >> $DHCP_SERVER_FILE + {{ chrony_helper_dir }}/chrony-helper update-daemon || : +} + +if [ "$STATE" = "routable" ]; then + add_servers_from_dhcp + # The onoffline command tells chronyd to switch all sources to + # the online (routable) or offline (off) status according to the current network configuration. + chronyc onoffline > /dev/null 2>&1 +fi + +exit 0 \ No newline at end of file diff --git a/ansible/roles/providers/files/etc/systemd/system/cloud-config.service.d/boot-order.conf b/ansible/roles/providers/files/etc/systemd/system/cloud-config.service.d/boot-order.conf new file mode 100644 index 0000000..cdd794f --- /dev/null +++ b/ansible/roles/providers/files/etc/systemd/system/cloud-config.service.d/boot-order.conf @@ -0,0 +1,3 @@ +[Unit] +After=containerd.service +Wants=containerd.service \ No newline at end of file diff --git a/ansible/roles/providers/files/etc/systemd/system/cloud-final.service.d/boot-order.conf b/ansible/roles/providers/files/etc/systemd/system/cloud-final.service.d/boot-order.conf new file mode 100644 index 0000000..cdd794f --- /dev/null +++ b/ansible/roles/providers/files/etc/systemd/system/cloud-final.service.d/boot-order.conf @@ -0,0 +1,3 @@ +[Unit] +After=containerd.service +Wants=containerd.service \ No newline at end of file diff --git a/ansible/roles/providers/files/etc/systemd/system/modify-cloud-init-cfg.service b/ansible/roles/providers/files/etc/systemd/system/modify-cloud-init-cfg.service new file mode 100644 index 0000000..f4253b6 --- /dev/null +++ b/ansible/roles/providers/files/etc/systemd/system/modify-cloud-init-cfg.service @@ -0,0 +1,12 @@ +[Unit] +Description=Modify cloud-init config +After=cloud-final.service +AssertFileIsExecutable=/usr/local/bin/modify-cloud-init-cfg.sh + +[Install] +WantedBy=cloud-init.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/modify-cloud-init-cfg.sh +SuccessExitStatus=0 \ No newline at end of file diff --git a/ansible/roles/providers/files/etc/vmware-tools/tools.conf b/ansible/roles/providers/files/etc/vmware-tools/tools.conf new file mode 100644 index 0000000..f2b63ed --- /dev/null +++ b/ansible/roles/providers/files/etc/vmware-tools/tools.conf @@ -0,0 +1,3 @@ +[guestinfo] +exclude-nics=antrea-*,cali*,cilium*,lxc*,ovs-system,br*,flannel*,veth*,vxlan_sys_*,genev_sys_*,gre_sys_*,stt_sys_*,????????-?????? + diff --git a/ansible/roles/providers/files/tmp/cloud-init_22.2-outscale.deb b/ansible/roles/providers/files/tmp/cloud-init_22.2-outscale.deb new file mode 100644 index 0000000000000000000000000000000000000000..39af9404f62e7581793c00deb973e2b981d2b2bf GIT binary patch literal 482784 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%0A4r}3E2TFLKlEr3O_!!{byfYOGAf`ZV(kMiCDp^ zRfvAkQnc-vrRNl=vN~|4Z%F^^$AZGcNL!^_tS6EWHV95%agV_L&4K#qO^i#CEF!ff zmUTstBDGwNMmWPmccy$Vd0KuO2sy9@n58=CYdB%wh)j^Lt6axv@9zM~w8)fASjZb` z!*RoSWC7=o_04~0*h`egdgCCHcV=Kwu3njG>K|R86_q+B_?|zMEZ7(mPIlB1t1-OY z776b1-Xd4%(};pW*6b1_D2?np4D|qRhnHOE^T2j-o*$4{l(jb;i^mMeIpZLXXRCAG zV%6dW*T7|+M*Rzl$#U+&TGNo%rmeKTQUu&8npa$Wwkfha`6i0WXJAO^ej1WI>sY40 zrUM4nsO}wH8t~lvtcS@{y~>q4twE@#^hePAW0k;|+k7KtRi@M9KA=|U{!RM!9|tAc zHMCzf%N3KPo-hw(oZ==Omw## zuIN&<(l-d%+RV)xHdJrF#TRPM-@3rXY-j}H{CmhuS~zEtqCq_EN(U=bI^q-J8~YLs zic+fa6R<=eU;s7UVI(u(Qhh6nWDnHVKqDk=?>ZH3YKgodT%kS({n&MR zoS(PdFvV|9jE!^pug(T=H{@MMhL-vqHLTeU`pWjzHej)irlFqV?&4Wc$C0E$L>?2* z58G{Mc_quYuSmjbd*v~_K`hToJms5?e+VXWR8)Ki2|1*-eBP4_`|CzEJsz>@;D!kE z`6`Cr2ogsD5xghs!Uv5+8;ig2&fY6?3nQEOgc-|dpOiaI{%mTEKItDKVN{83_OVd( zf*n7_G(_C4$7t+Ly0KhXKMOgPf`?nymYgtqwZOF~5#|7K^^X;CNj;yfv@<>1Nc{ds z#8{>W>TuvV)MgKhsbO{FOw7<&&lpgi@R~c75TT;k z5tD8XTq8tP6tMJ`=b_Wh042eM&g%QAQ17FBoJFl_B5OUl5D|P0&4>q5!%v(S4XuaA z0k4jJg^~sUM?kp0JN@|4ADqU+&EicAkbB27{QBM7o?gFMg*<=UVGKZaZQ6{CSn)EI z3mVXl(LHaDoI>2z=|~ZGC`MRCdmYQ$vsFhK!UzwIWQ+DH*Sc7TbUE)!3VB5S4#XQM znU`nsYy|G~zi&_p7u2UMI1QWJF;Cx8_A}RwFtWtBQF@k>(t1bIRy4H4l7eC3VFm(U z!`1k~iz*k8q!(b_)=t}(SG(b)cI;@zXt?B4ytuU=zcWlx`PFa69=JWeqR`Lx+Wsa~ zv?I529|XZg(W~+;x)vA4Yf6jXd-D&;UwJbG(B(#Qw5;NmtWp@Hu-t05v`9!F&%qB^ z!vRS8M2H{~&GJakW8Qnfopm>ZCW-TKalL|Pmm{f20#q~hsYtj3JY!17l7FEChz7i{ z8nQ^fA{18>IF|(`2;pJCE^qY`>T}gBB#Mon<@*4EBXJ?f z@OKs+RX&z@*@QL^v9_$nr(wVrs4|Fcz$IqEZMsA_gTVh31OGxdgC_Aoqh+fjvWc6t z4JGV|xpw3$zcxzMf5dqX&=6XHN- zv;^G`mxjh^#NZM5!1AH*zOOm8M2_QE*CH;^9k5<2FS)hhEokgws%oy_`b zAyd0v0ySYV4nz9`_fG9+JGwT9!T4q6|m~lT&)7gBqR9&HD9ID zfomJYG}hhUga6nU|@im;$3WzC-U45P?|n8VdI6P&xgwO%Wk z)qH`Kxf(d@5Da%q9;}bJd`9`%j4+xh_mr!cUddPg=u)V19XoNymDD>(Us`s- zv(r=Ijw`L6aOcK1eooTUTz8?V=)Qw6@EH~}iWOm{cLq#}+ua42M7p==7p^ZJ$SqW# z0eF^lel8{UGoXOWxcHT_)0g$Z0K;Qqh_J(^b=lQV8mh^%oeoT;t0=;|>nflH1dM(i z@ULm`oPBEJDD|_k!Fr0x#}<*gaIiZsZ5pqhYp5?r78C5ytYLCTi5R3Zg@s}RSfVoM zf!f^XiCyQ}k2z$-A^%y`*HrY*Z6bUUE?B6+%`2FEH-N|$P)+c8{mEJ^-Qc^*{6u)a z@eaDIw7AYT;j8gZ7mr{1ijzqs>DN7u<55;G#RIX8kWN5qpcS%OxLH(Mey7izQe3S$ zzq!I0bmrSVHy_eeFK4tt?sdMaAjJH0w+Vh*pG2Du`gjg9tGi#sG0^W+tZhj*1B5GX zkbGCZ1>fZGr%8d{VxvouE@IQal&Tm9n8A$lRUp{Eryf3xAS$K5I+Coyt^xo}IS+N! zi_}y%ctTBq`D?7F)uiS(m$)0tj9P)4f00?t4MyjjxdRx0KDXFm)HzwI!DJ@0Aj)H) zK11^kJ-+eMFo8#iTk%trnfLk2wDko7q-Nzrn=Y*NN~-P|0Ct8--ypeMSfw-Bp(}vR z5;@nJZO95nucS;AUm$`6?B`rZeV%r_1h~rhf1+S?k;ay@7(XT(^|QexjD@Z|M9}o; z#pFY&xjTRC@n(5SC#Hp%=Sm-|yqj4Xx@W16KO&lygl8R_U@4u|Ep@G*^GI=zXC^=Q%&yZ)cK)Uu>pDW^BVig9d=VQuCcYXs_KUG zMgSibg)k)bmTB-!z-Fm<<_@}WKfdVr2W+1qc!pfh2VNP(6PR@o;5hKYaoxYyPUU0Q z_`Lb`PNa@~U56hjSsd(z{b*_qJpMSktp$H5-N3%sI9*^tWol00&79T~d>z-s7Ol}Y z(f#B4SxLpkuN;Ri-+Y#qDJd83$NqzhuX}xeoPTVXkppKs0ak%oZcR>8r+)@3)?njq zCKfSxec>Or_-b$K=9{{SnA3|Mkfhcl0GrrsSeDdzih@UFI{9*#&{%%HdDy*amcZ0ZJ-1wriXA34d_%1gsF1{GWXbWdcoz;J?mleFJ;ZO0>c z4TZKjnNoNJ)GHbnXfxZVp=a14!7*=M-~I|ejSuCi6WSig=tiswQJ|&9ATtO}51*;u zL@b-i(BRKLzzSDp4CpbTgNmC5B?NsB_Hn>IY@d`K-G;N@!*RM_W(jgwz|$>ZWhAfQ zW+h6o4h}VV%?o1q`XEOC7F?3z;70qm;#5za0m0|r+0MdzsU`^;OD1)gA&$}%|AZv$ zrozKh1-H|I+gNt3jLb)_h=9__A;^|hpHVlGjaLVF37IGRpCrUr`!8<|K=^oFN;7Vr zE~yOjZLNmVSPP=z?BC+ZgL0JRi~&Cthd$C*^MQ0(5~?sJ=^w9r+5CR-Iu9Tbv;Dl| z4n3&rc6V11>d8FrajuIe*>%eDE>#xQGR^aO{w1p|qD(GW+QY?Ca_BCs`u)b96*x+P zb^k+D)-{p8#H(G&3h~mP00%)0$;a9vBtbMY0;x@~P7uRqh@Fs4di|XeLt!d}5=pl^ zzJ*T?$UI%kn0`a>g9aEmd>vG&kXhKQtGK&7mbdEdwi0HPNsHrFgNL+~%@7<7mTt$9 zaC!Vk$Y9`hqAeOF!%pL`sp-BvC{Z!z(;PkaPp}JGY}>gEQ2PH4t#MD@iI)F_*RUuK zS!oKui;N0XQM(LbcNaor8=g?-y^`V|tm^AUO@jZbtI`9Txoy&Mq}_ukPP|U5?|@Z3 zyA*50v12{7%S2p&6mASaO-*bDeDI?&vwZurjo2Qw)bU}yk;hI4&B1ZJ_%~PXF%c$i zz&k{_yz)_8A!5zcv7` zszMK16FaEY#$cP98moIhVWv13u|_zYs}8wVmr?yV${2xgKpaU!V>iwV7%7_QRNuO0 zkMj@`;2bGq4yuLuQ*+QGY47}%PP^Q8;^6zxkmZOyOu@S-I*ZIjw*Bq5oC5;pF?7Rq%vyW9#~aE$mL%~ z-v6))zspP#5m>3np3fm>;ky?$M2t7!YB!a7@qAk#XcPUmTnI~=Yj zn=OOByzUfBtwuI_u=A+7Fp6Ar9;mq|OPP}VakViC9WWWB(B|Yo90P05)H;;TJ60jc zs2hNzpf@Ht%pIaBoqS{Gk>G4$!LNa8sl~_)-rrGH`rdZ$3(`M83hLUyKh6>4Xvi%^ zKWmn+s~2_4k@vWHlXe4W#-JWu9JSt%c9=lw2}R8vG&yPa&ook_uQcoh3s}0oW(_B9Xw#31UQy%L$#`o8({`IlzWZ# zv$tHQV1m9uj49DtK*~*l39hlKNIjask$2R9$bc1R%L)fLru_1C5XFB>blk549tH*b zG3Y+}O3u%Fm{B$*ty5JB2YF^G>`NVA;GJUOVk-RWrDK?dj%z!g$QXGHOXHV^ELVV!9eAx%DLKnT^K27G zx(RB5Lm$d{cwd7QXfL99i^Hx6x9+&{efC3Z0;r4Nc)|VrQ{u2Jck?!9jm+9;aXHeU zIatg>JZdgys>Ujs7+ki@uzkDBGI&e0roSRHt*ZO`)hCUS*$a-TXvj!t*H&+WasK@@ zBF3HGCvDV0(vL9}I1=x*VtBXTnKd_zTBD#D#c_6PUG@Zlk}c&l8=HCCjDfrA ziyY!653quc;K5xIiX4U<7GlPM8O5zYG)td$YASkoWjnGaZL0dRO&4_t--)@`u6N<$ zaq$#sp*=S9k?JfupBX5&n8TIuy_rp*$0uS2nH=_WF>oS=s*`C9AalTjbcE(Tf!W{M zH+Y~%=MtXmp_CeIqJtpQAK|&*aA_t2ZTwd}=d3$iceu3nX@fjR(l40rfDl6m5Kwlwh$s(ic{RGdMCw zW_ju%CBhoo1DuX16WKo@G~BZ3CNZ78BkB{Ow{ejJynXGf`{N!2DfJ}rLl2Al(1S7+ zlUJJ~_=&(mJ;e$Xp`$RZ_Z3h=BnqKP~Rt{R=DoOai3_%2PcAs7cZ!A77f2UU0$fZ&rm_x#Vg(qq-W>y zZ}tcwG#hDtgAf|KuL+^2ZjtUQRN_E^dYT2=!J2=qts+K_J!{tt{*edMuXO?v#(FcD zk*lU5k{wj@LW0^kJJITTPM{80p45j2t=4wThQiS&`o#EeycqWh3&#~ho5)^F4k-6U z+)ZIA%kI-M{T`=R&8=nQ%3w2=;LpkO=oX1l7oR9&=P+Wp@W4wR=p7+O6d-Oi`)2j1 zZAH5c8O8maO0ERZntduE@5kDt*1+x)|HtWsJVk;^94NG1oTF%5QAH7@o;CL=h3`nj zm6B+787^c6C{_@?uV`&6Xy>W#Eu8J?m&f zBfq$cpE2SP_HUvg6JT8+DuwR@u*)qde2VOPKtRukd<2GF>s!J%toygixL&I3B^T$O zJIBL&qVTNaeGs_oAb#*x?fr!ec?t95As5qLeok-}CfCzDF2LDcpXU?6{5^akx5dEX z@O7d5!X^jqrP{rzaK5zl#Vn||Xh>%Ds+nCmO7^w0h5FR8eb%iZ(AD7eo<3RIF-r-~ zkL1NsPb3Ywf%iB7uqq?A0Qy}@+Hvr3u-B?Qr5Fxnl$UN(j%JdKUpXsyTc(Cf2M4_& zfc$T?={Y`3&ZuCnHIv3QyWWJkB|L=(vv<@y%%?Q-Yu4quVdO0^2u&vX$ z#=5|5%Xo4tPVQh=_FE)W0NlTHZ6$a?AlwAIQwt$l^XmThkSU10rE1&pB`)19#af(y+=i!?PQ9b9~8` z(HThFp1W3Xd$sMnhFG{Ymhc|q#x^+BL+csX1w0;$H?G9yYdAl>8cT8??R%S;=!7ar zN4J8Zv4gI4#kwE%MAR)u2rcu(Jy z|E2R!$AwOQ=i>dQ|G{H6Ooi;hWR{3qo{FSspYWdNmOyaoEzM)206v$P%j8eLvt zKw-RHz?VyW2hG&HeZkWg#}az!@gzVjN0^II3dfbk<2R~idb+W(B()zd7t7V$)&980 zMnZdM$~Tl}V#N1WrtoL+B(P~W78}}0tr;*HC8mSDv6rEm8|ACGS<&Kqcc_G(G@=Df ztBF^RS;L{g0qrcNM$s32KkemD<)sOBQdH$p=pE47kw z)?4!#usQ+BMb$C)`{rvUKc|3Ydcmn~(B|;MPI0-J0*M9tAD$l>ZWpYn7~op;aQRSQ zC-Mc+v5m?5mZ-wDbWtO{OQZkfmavW*rdGoig7LXbNzf6!^n1=zp+0XroRvGTi~V+| z@_3O?Jpj4;?i$=&6WGM}#z!0=B}Z!8MbIiX%01jNx$(@BF;+!Lcq(nRCd|ud`2H_3 zhpHquSjF)uR_!{{$A60k0aJA_!eKiPRbi>W29!n(#c~a}&c3KCw#8ZB=pRKn*1X|% zQ=LpVFckkc;T+s~*Flzh#w2>#?L4N#y)pB6)-nrHISGr+wH2)G$pa1tn8LfvXh-H{ zuKA-0yQr)Kysgsp`%k-UIP`B8Ie;UGF)S%lY_lljduvVu<-HdW1my%bfA{1GL0tyn z_k4lrs?qLvPmwC*kZQM(K(Y81f>}FB18sU$y?XHLEjG9lTBo1=vX%RZtwIJ>E>tTT zj6P%Yq}69-!9wr`J!U8GgY{lAFW>(mVS*itlN#b$!M2H>K~N@pr1e`uuFcDt(3E!n&N<`O! ze=5d0=s|!B^u=yE+O6;(f>G$LRrYLDWoiPWV(q`{{P9YoQEVSna2o$n#z`gv4DNU=(6+ z&J_*(SWB!?@3Mu1HaNy&9<+eZhcZTshDXgE8ENrZXy5>B)A#B3Ai8NczDr|ld7OX; z-PeezdNk|K`(;|if$`R5};z7t>(pfURXIogoU9zReLP+5-Xvli}>Gq);=z; zkyI$kjLUGV;l{&Fgn(8Rxt*XwNqMEcKS^-_E)qMHm)zfiiVhv}sC9bV9W*>Hk=tT7 zWNf^N{%Z4?%#T1bicLC`Q|w2C&0?Ebzi{6j&%FhM;YtmSZ8b6q`W)#Z+| z&b>Qlmf*?1PQ2&KBibnr5@+>Tv-O|c-mED2K~tn=mgRWtcs(r1q!VG*Y?S#Oe?8PZ z$`iu?ZuOTipHu>^tS&q8_bOSQh~5ip?FL|(mU|Bcx(r)hYz{F>kncQ+8y|-anUPRG zwq_mav+KbdywzNU_bl@xs{SADv6UrclC%o%XLcT8A`v~W9%B$S8^S}Snn+R|j@$Z8TdcCnEc%kSc!ablr%E|mUj63pRn&)Sy4or;E_8{}nIdt| z_!(n1(AreWEatD8uhUjzR}%ol%<*^(qpzN02Ayh6fi%rYzt)ow+bMRx z7VyLy>!;s3_k>=j-R!D>Z728=aQbUdc5?yS(oQ&IW9)`GD{lJiD*I|#lnOp(gRKQE zxLq*+?a2^RutQXB!kY z01TuqcSgI7+N8a_O9wQ0Z}A1#C7xa$e2?Xo)T961iKC@gnzeLCY}0za7>M1g7gj#2 zErMjK62HqVu|=}TZkVc54)?4u(JPN@0KLW6`9Y!f{P~P+N-l5$eyjoD2l$>zo~)eE z0?{^FFbXlg9qL4>V?`n-b-?NW8k{QhUc3>6C$#5|7qSutbJ^p4SZ>Af-6pEb?YD}% z*9xI7YdZj_uQ)W%A|63-O7<6J9-4cln4q^26d6-}GF9~Q(4a*&b*YVx0{xPaq`G$7 zs_UhzCpz6Dp@)jK&pjQYwS5|90y#RcaET#2x#g4p48bZdY0@-q=n=@52qz1^`R3x9 zZH?CZ+e`vOuap#STD8BvQIWe{?N|SR{S@!(Qz7(%{PLC{fluJxH)zN(^WEHSi7@YC z_X|Yl6_<e%{y>Yl>G01TeoOyNy@EHsGY_BGcKt+HsN1$9=H2l@V|Rt;ct0VSB`J4+gWSj$?3Vc}SUvcFhv@F^-l0g}Ob{m_+XIG@ zzBc}+nQrNv78v&I@>vGIbbq2|s|5ajxwnBSYw z--SxuiA&@9QqFB|+mVe{?|Id|Ho)0pEFy&aglZ-q9UQ8n0!FM;v?m0L7RbZQxTR5q z7Y{JqiLZYkQg;70!tP1shIVdNk;y`|=%phzuI)7j77*FA(( z8W~gV4&y^eZ1$v2DY9a2!X+243YpCDNsx3JCJY%cuABX{AILsjh2A^D`ZrFnUm1Lh zM&GukawW?J<%indxsFq(jH`>)>Owa$q^C!u>#>rtv^&%rB)Z3!CWk7uN;0ks&s1v% zxL*cNKsuCpF0JAh(ol@TLosTemhk`H5PGWnpf=nW)=#1SSRnYHrtlDD(|M=~ADPTa z+sas>v(&Q!dba}@u!E$ovnsj1GEn0JRbNQzi43dd1nIxi=x*aT7;n4sOCFx8z-f zKuhNwD!@K9knxgNkK=Q?dzd1vq*ngbX#3i$JZW@WQb}s$oGcI|Y_tu(zW!})?l$Kz zQyOD3jOGd!Mu;Vaa3Kn@W|m3Hoj~utJL&Ab#|`yYC)svd6Cm^kk*D3KDkAk=#TB#M z-IjjU3OA+a1-<;<8PXq^-w|HLC(c&X8hQ?74E;=kvlMV)GGQrM&-B+kbJC&!iaF`* zqaQ~S2R|PvB};fIyi!@(w`xhdLdx+nZ!`M_B&5}!^kLUbt(}bqoSVa z=K|hGbDS@Q4T6rB-Df(#t5352&)}NR?8oK%$pZhMm=9tvU@0|p-P0{P$grxlFXfTm z1yuO&7dt~Gej@Q%SdxMeI|w1BZ%5k>BPbB*4p9($=}~s`Bxmh1MO01G0vPhDfmR)N z%MCB(8c==yIZxxU#n3=BA2hwh5O+2Kthv4*m*~Nh<6AAPpTW? z*@;0iHS{ds^>-s6`emlnw_Iu}dc9B;@6zyxhT27U_$319^CT^k+@LDqf9?E|@(OY0Z2L2BN`ViyuWyX{<!lDTsg>rJ#zW6S<(0})a3X&C`4f|97^qUNJF4!?L@m^ z_V7ybMPMG`i$U{^y_oE|Iw5KyR@H3BU{m|(S56!=Xz z+?()8yAy;#cide%R%jvJJq@H?<|Mpx{MT5>5xD<%3baM}CGfgH;dKM2U*3xw%Jy?e zrOgef)?-r%(S-;|^sEOnw=@fTRp#LEkBte4Nwuq_CVz*G+HziXb|}V5fJD1BL-i7o z%44%*zaIf`MM$^$oEd**?yGVkZ^!+2@H;3^Peuj8lke1Hn}Ky5e=q>TJ^WUOl4pKG zu*GKCN^Qv%55!@{OgO)#x+zkzepCpQ9>RXD9gj*BA6n={Jlk}GpY~1inKBxl11nsp zqgnf&@==>EXVHjm$*t#!z0^%E9~c=XBU|Rt@t?ezkTHoiDH9x_>_B;vNWYZ z2Ew87p!sFSddebT$m4Pp0v(HCWWhqRw8NSHY7X|^R4ePUk}*N893wUjsER0tNiwme zW#MdNC$`?2li5;;G@Kx~Q&of7R<`VeYl2I=##B0l`d=v>Qz!<)Ld$Z_-GPG=rU|SJ zCe%FY*E*#6Y-KR=f71>kKH!gX{IYKT7jV<6UU$6r72L)!M2{Vjj!;A5rc{8_1cisY zFEXX{yJ`3SR%Ni6D~Nq33vgxm^7@~%^9<=pmeL@IDMhohE}cBP)8+VxNOkl4z_Xxl z<_}9}rAyDrMo9g%8E)Eir7}VL?jVjuPxx-6?jRNrQb|?${ni&)Ti|KrglkFL|JCg8 zc^xLSDWju(0M*KJ(o4@UgI~rIX6Y@B7pr|q#y z&tj1%6@Bk6ey+csIJZ_Xa{m(rrz;iam}e_HX^IP2_w!mv8(!^;dv($vQcm5ITAUcW z2h;&qckmzbfobX~$ zlBNRBYYKHaB}6Is#e4{Yh{S(11}9{ZS)uvspS8|+)a;Di<_S(qWKQV1%)+_7(-|r2jTVb4ldgpy8Z>L4x3oi+2HqODupyZ+0J6`Y z0AXqRe+q2lBMR9&hqqCb-t3Uyye+ z}18;odtfpu#44dhs64XE0E?dXO&>){BA*ZObG1jyWU z!2+-qWmOuG?MR$0Nr(3A&GLO1j02hvW;~G55ZPYC-Lpz0KpZRSZ;W0{aT@wQvro*m zLcpnZaSZ*r-mBKGGW?&w^#Te#^KzsPQD`CyG{^K}Y92$G6Y}RayTqxk4%hcq%9nV# zMRd~*<5s#z>RI{|DwxQnKZ5$L;}U}2)iGg+E|#lygNq)Ol~=Ctms<^xdSk|1YN}9p z|Fo)3F)4lCey9#0NJYV z=Jk(MH_l-7J3Gz9_pe}L^@k6)PQnC>W%{7w3 z?I8UVaU8xG#(G)<9`yPxPdGb~M3P<1kGAxw%J;JrU5nF*GL zbRCg+fb@#l^BgK~9gUvP!S~TqYe#XQ#u36;>fC>WG^X=uvsI(IuyDQWEy{M;RwuL_ zhGLhn-5E1?0yd#6yWjFr{(o6c(8Szgz+O)z4`JkpHJ*a|KFJPWTNI^&-~}Lfou*(a z@{;M-=x{zj|5Ll2(9+r7&#UFJWWlh8@aSg|M_S*Vt&lTLhgC<9AHdz)kt6ixml%`_ zME%((;cl!jqF(B+DHdJ^;sv1el2YvN>bkgGcAKvc(&p!(Fofb_5IhgF!g5NYYLS^- z?fVaEaE98#9ZQtP4{kX3(eNkmomhN>W!Z@#sW|W+b2&O>TeTja-L>OO`yi5$iPI!v zt`t`ON{*_euC^s$S;M=|{@NU<+*N)?zq{&M*sLW_3BtQjHKcRRCjZJV4Wh-L$JXb` z&5Wh!_O&qt+*em=*-g8|JTTUQI6WbJEd#xv^0Buc*K-^)NcvSvMBYnMW3rT|_9jbP z^XYmX^nv^1GI@wzKYdY!_!+=mcNU(9-hQZZopzDnVvrQm1JYp_?zICjSi4Sl6j?zsTlQ-5Y)IBAI~lQ&&Mm5f*~@`?B0a-K zCK^{{lUu5XOkf32ovzY$D;1OIPS#!92Tgrn-u?97y?#pQDT;%{%)0t{Iag#-(i!zU zW&YE}CdZt=JsjOH(IIS5ZG9=x9dx8|5%g7p{Wnq-{7a(xe_qhpz2m>EafXtbm%cjO$LBVinm5@> zEgN@kDH5)-c^IZQe0-&XEe$gm4Ib%!CYZMcH@h4bAllbfJR{dn9x& z$5ZJg24Z45Or?iv5}MTZNqqdWuaUNDXs=(+yTo__1!hM?jh88^D&+HzjD+sg=g2Y? zqVL~$#S}KGDb5qWr>!kkFVt+g)hdZ`(>ffZ(;C(6_DX-^tROyDBwuez1xE21k!yCF zUI1mt%BPY=tj5k`#u;ZR#%C>G*?z4@V9#$ z{wjw9&tmWI7r%cjXwu0wF2G)LLP%)b>Yqz|Xa~2@p6~C$UfVp!LZ(GkB2<>whUUYf zbi!RfA2D5|b>CwZ*2lV+E`IuI0ik&<)cq?*=YOHL)g`3rI6>0=ko zGdZZyZsNNX$tAz;thxuqLcro6sMr}iBp#!3_Lwi^;P*D>?Q166+&sI-fQdA?UDKDLRc`gRr!Pys7$N+4W?dA`t%2%?#R4 zMeO8~++?Q!;_Ihb!`O1 zZe}BiQoNd193rpj3FL=>L1EM|l>vsNb6SROP`VMMvO#(Q`TM?fw>L?fpA0x8_NkNI z<}PW!RyhozF7*Cu@QX5Hgbn>7X;q}A7Xbk?4!3&F<;eX|FF|U5DZ+t+!}Y3b96#5A1g1fP zwWmG88>-oy;3vx&rNA+BKH7n>>W6U*b=CI{>y!*+vEA-Ex1UTOu3LoG_|j^o5KBfO z?n*{ZM@cVWmnH(5V>h)d(nr}+@&?h}6>NbWG)psBxUcSDD$d*`#AUi>$f}&)fil`@DXnXLtfz-iBYEhZEKjj3U&%J7%QhR zhQ@21lvoGtjw+>?v+*Sh6Jl-sJp2)!r}J#`Ia&IOux^~;e!|KI8T_xENE!Q{OU=ES zi3MJIv#%~|W%b@NQY)hx8sP88p&e@d=ADwoLo4G)>}?uU@+y_pII>={_LyPFP=cbq z*^62YsO>S!=xnkV$D#un_yMO7HRhH=5?>`}EZmE1fxA&eQuTz6F1HNB!@rt_xqpQCbI@a0ABdr&vS@nK7i3FA7bR*e` z1~&<=3aW;;KXzJ|Zf*~<-<|Pf$ORVWHF%BF^uo)K9tMimv?^K1w%KJ?gIf*XR+Ep@RS#ojqzC?PnpB}Cx!)Iid??N9;2fg*w;=!+?Fsn&Sdzf?E^6$N~qTn5e3U3?SBQH zKr=`aZa9tw3h4@4+43mr5F}E4FN?{kjS^`l@>ddIS^8d^{8N_O^mRLsxXDuJJ_!oz zrZ!*f;^M54!G@u;vO3`xe9L}U!=Tg2CQ_ofRn^4CTQFyC3`BbSLj^k({(0O#WmPEp zW>FD3RHkNJj!e|#tQvNh&Y0|cz{@e@A;9d;RROc9Q{l>M+@WhWSDTpp%m)+{(_TWX z-Gy#Wo&Z9yIrySqSk%SAsLx1W%tlAnMu-iZl~7&gm>OW3*jJhRcE zKJgo7Nn7HI0qX*o%)hoq59rwqBR@c@0eL@%6H)c5RDD5zX2uvJH9qp73U<%b9R}-# z+ly(bt(V`z8X6o#DUA9`yVW2;ybQoj#n!L_MN!ZbAK+0H+pFfH4qCEBERI~4P5*)8{Mh7%YQ?cJl;VVXZ*ItJz;4W zLd~i#-Kv+BdUxX7s3V2C?u4U8$)OL7VchA3TAY=fdGvCWJE%?4aPy|r6HFJMCq0{) z`UyK8xR_ye!g%vu7FB@fNI9NOm{BPyr+Nm9eK~F1+FCPp{7KGnWHs>|MC2)V-!oIJ z?_7~rG&2U*4=*UA7*(20NAv0|*Rkv^ps_iBf|8MwDWWh2daI zIN_Z97++v*Ie zAA?tL!$+;h7&2)Y)7rpXZA%xixE-sea-^DouFcjQjbQ!T^RgMS`dljFy;xbXZ>V<@ z+!9V9JFv&gpf5D(r5tRvR!!?_WF57sNo0O7&?M}W5n3(EZ6tc!#-1v zb|1xxo{STVB7Owx!(L(aIiVg$t2l85{M?)37c3p>G{q>8CQ(3uC> zUkRN)04sf_9K{;Ib?w`TlR1?(o|eyET0Xx!3HOW=M^jf?Rsj_&=88Qks@fyRWjQJnZw^b}&k2S=5I>s=bxgL=+lp9&9Q%D=(27p}{>( zCKQYQE|b-p(C7myF4?~KDmBiL2hpufQ#5UDPNX}`uPfWqlnGSx@1<5veV5%nUCI$5 zE2$Y?&X5P*JmeDEQ2)b55MPB#nT~#jt~qt32eQP4_Y3?ZNFF$#bz#Q&gEFqZX!i$e zU8)+?JnUSi1P7X}wrZm9j2!(k?rSJiIZT*mD4S^*NDQBfRrR?xdkBW_7nu81uGy(Vxq6b5cdATxPzr9 zn9k)LbC3~$B3c;0B4WoNw$5gfT8T>h>ZgRncKj|V-n{yf%C%nXG|dZWE6~{68oS5s zy8D-n>n`+&Fety=KSexo@O@J{H!-}i_!2)sa0ZDdqipb2<7B>h)6>YvuQP*Fga0us z{Q=q-k)x;Ksu#zM_%VCrNV|h}C;N9%FffE4%O2n8-^vvy)lv*`p=7z_#Tn2fg^maO zuI-+Gb8J+O_s_5;d4eN#haGAmJ=LpOd<@SG?J&T7GO7%BdYfLr9-xKekII|g2$;jz z9l7Ku5BR2Y_jt?)w_W$t*26BX+FwfC?m@IZ{13ZNxC?buR?_+6Gua4-Cg8npd8%S3 zpvFee2)pHQ!d5gW`e7}XpUg$t^5q-OdL7`O8a&_OOpC@CE;n2KLDRxasd$-X*3fM% z8b6ND$)ah?%9Nj3A3P#{NaIwq=0<2^uwU8M3do!F=HJv8LZPl(*4P=|C5DxoHTn15C0$+ec@VAP zHY7O5wy5%z7tT4v1m<01P+ZSy)1Y4d;*wARA+)Ji8)j|ySWPmVR7^>U`)v5Z;hOI& zHP#md;S5bs+Au$Edd+VF301T!IzIM3?g?Vw;Kp~>^z40O-8#=dChf^|VwFnz70!## zjF!}MWRD@JJO62xMw51HS&Sy55VI}KT4D@n)|{VP;0%X*w{Cd?-QS$rBr`%L##_yj z_6inW|3$fG$~w8l_3leJC9l3~46jwhzeZSQJ8Elh<@&tl#Q*(>H{*&ughz?<`C`6Q6oAr`(jL{~HsLTrEIDt;t;GW!EI3;SEG zF}qmCjKYfOBv*eSFc)L9>BgKFS=Rt_ML^w83Q}l1R;^oFL%Dxopvx^>QAwH33dmOv zn#n)aTNBZpX;D{;L@kQDo+?n1pV9SpY^Lml6j2wV#&q3C=rA~$Bv=BB&B>)P6!~K< z)_ncF6RWl~4fb-ky(J^C*gBPmd`)>x@#p8ri&jF_xE@~aYE1MILQ(8Zy?{bdcbGJJO3(J*uZdO_fKARPs_9@Xq6;z zeE33@Cal@{hu1vr*mij;e}f=oX092d)?N>Z=f0-2tyo`i(9|=q>7TN6qqD_xPDL!s z>{Dyd_>5H10KKI*YVaRbh5vADaAsuYg6{6A6zObY=$##~-E-bbwh51^GwKD=XS9~f zAdTfRnVWG!QhAs@yop<%VdgqJb1jD#ch?NgimYqqSKB}N2%T{GFNu0g|Ht;plk)s5 zKLu_*=8_9~%-RH|PAMco3j%KtqJ-A2dBhU3+Wjh&PxtL-h|xlB>5y8f;5D~p-@T++ zc0jdBS4NU1h);CI^l-$6e_{%x8?^71gsrmexEbW8cHG_FnqI|e832)@^x$$mK3YFX zfg8mzHk4y(#rE%lkkzi4|0W{5cP%8So(-lqZ2=FwaTxTdAcCR&;S?%{X;^;`huK23 zxUtf@QL}lDihVrq1ldPZw42A9%dYW~m#mzS5dM`&-emR%l;m!-C2~&0U z;lVc51=am)f|IMqqBqaf(UUhT`?7&9fPQOtBI{w(dT#x_&95Bxc-J(>n760N+Q?%q@hqlq=o zjNSG)vJgUrn+pAa{?I)H`_GD?+8PD(FRmhF;l2gSqaKlfjUnMSM@M+JB493{n^8TX%;d>+ClYe zHTyq5mQOOfzyS&Sv1ia&yGb2qZd#v-ys0seB-SqpJW)n%W*g&GyuF@5GR52(W>P&i zzSU6K^}h0f1#ucMt3bO>r`#2?o_Z5zn|ZL&MWatn_*f?lm4*dFib|16w^aSweL*2% z{aVj^Qx8CFR0iM)lkZ&f;|L`$NNRGgiw@o*Vd=+?Ia|}Eg zoU!IMa!ACLD!U~q-JGrQQy42F*Y?08mL;o5#$zk(#zomF#EuLif&@k=m%ZwMS3(w4 z@897MK+Zu#ABmljJNvdyM`#+){N8<_NECDZe8VMTVvaO?1$eHX!oIM*Rj>&&a^{Eq z;Z_CZVrT{rxA%v;vp2Eb)k*m@>R-y+YKij~Sh+t{m9 z0=b$HATlh$yeD+777rs+;Q6zB;6@DaENj??|5VXgv(1nSv}0behNDYAm`)2&-+#8v z{Gk&AC%CRy!C4Y~XyKgkEzxsF{~>0_=mZ(3kh8RpI)}+tmPxFJHT1b-w8devZ^?n( z!r)OWWJ_NKerx}3SEBZ5c+@MqUL@mPQ>|ot4N_2?42#k+1P*xVG}R}ILwerCIc~BP z;v!(=UirWTy1Imp!+h}&hc76dzESe#0LxsA_S7O0RUiC_xem3RUnYGGomR_=fMbk0 zBmXHs@hQ1X&=Jlgw?<++^Gs3tBz_xMWjum0R=tL(QD{ z*dz|n^}4hKn7b3_vg~dW^4n+Txwh%Bn+s->i91$b@pRd1igC;&1;7q+$*~^LCZ}`D z8`sIPB*vplB)UxJ@kKbNxFaj2H4svDT3an_^Q)0PfYUbE5{9x;qDq(?5qnR(LY99` zN^@I&SXn?f5U2cBh9EvMp^l%DL}EtDJzQqOL6XMu*5zTB1(u|Aw)}zCC`)|{U~rf$ zSfvSMn}Cm1R9ddw`uim}-WE8<_%o;bej7On>eVtu>6d2o!}9ds?wgI?-qKM_To>l< zYRVD|eGitnf8bXzT({XwM127Gz|{mKWdoRhvQs z>dU2j)%a^)mO}ulx_~7qHWvFGi?w7=+h+4F_yH~yA9e+d+z8?9(Cb6ote4z`(d0z? z#$f@`reA1o?i zj;B$vf2-J-m+>qak1toIvz&-ieX>LLhwn_lbgw>#NhyMYeNqK^FjlzZO)31D@MFoO zu!ihfq`Tlr8B{XXI`^zcg|lf#giP<+YT}sug=D34C(TlAd0XA*J*wK{UI=3c+)}Ez zPlQtTjbg1gB)~W`*Up{QDx@#urhNELySNh&t|4x6G%-i=3&x`TG-DzbD>zUJM0{Av z)+hjO+HZ(9CyI4|3q~;jTZG{T+=lTNU=0rPu!i#ZO305IYO}9^YX0~RW1(S{=q=P< z1(VE{nGx~sT~fs&$kVQbaGY}MjnNm#$^1g%A#+)Da8NAV#rN_l+;sDE`d2P#Bd0*l zuKX#g6I|Jduk&KWi8d`j-);Q>rq27dB;49GxEFRn<^=*505q3y>lIYxsxigfM8yC$ zV>}hFf}lbj2{gqi8Rta3*diGd?TLGstggg`2LFSptx=Nt2tRmfv=ps)H`V{uPoJ{h zLK(LP%g*yOd?NN?j$uWK!uVmeNHx_M52L=)G0f;CpxaIv5}<7t(grALc(cnBP^2WQnrNIJL%);EXtC2 zsILSH9>cCdMxK=p#G(_!o^|-%=m`~J><%BHBHfw$y(P(Pt?JZ*IIlh8#!}rEip=Ci zRgB2o2TMKsV7Zi<>LIz_ubs|-Y9L|zjQFVE{598D=UP=jdhLv}XIBWdjOuk-+Bnyz zE22d2SCB(IA zuvX@dOlMbLkn3oV;%!pJTh@&N*bpHFI2B2(iFX|X40f~>(fTGFprm6+WDmz z7o2tgOPNPSLLWL}@NQKh8tOAA0)E#egFw6{CkkE1^dIhQO%&9S)$4)3$7C<3u#38| zH~6FS>D|$ncdbPYQ)9m9b#nhBc|i+C%s6Jdu{W-#%;Gq$t*qQ!1JxSd>+dRipYVvS zy{0Fh&8bbk_xtF{1ty|EYWafHI{W2BS>v*7P%65P5vGjp1hM|oA39O~jD=m+jiwwile63QQ22wvMY$!BlaK`>6Q_&eYBwC))r5o$1vn8QX8%P zNHO}KKpMLeZwkcf%4&p#3?h?Mq@?RSCny_GupKyFTthL%v>tkOQ|HFrbr(;YnFL}- zQa&&C3SCKPmBBSUv-q%DMg%C7C&WBs!m@inlM}cm%cbn{O=8UAa(}x6lg&S64*VNY zQzVc!_>iF9pQ|m7LMK+qE&&H|3##S=5Of!bXdN>z(#;Tjt%Hjqw^`FJW3+y~-wLpA zD65G;ui zS-!rFkyeH;Ps^OX7XjK(W@;&uo>0$q3U9+>std4UCg=bFX@YB#B6SleX^}xSQ%sOS zi`R9=NbGw`=COO%oq_#S$Zf!HR!cXChzGyhR%JS0yldQC1JpAU7w*Dbm@$fjiqTLp za59lnq z|IDCFyN9wwvy|k3;#f1vbG>4+W*Ctu52EQbWgF4O`bE)uR3$`K|ArDPXt@>4s@Tir z`F%x3(9;I_frSpLJHL$oBYwjtMwWL$vQ99PzmoK055KRPG z@2UehUyix1P8NtBW&Ey(luql!I>GzXJM6yViLnmZ2hCk_5XVG1yVe{(%0-kJ%H{#E}sP zbKRi`MCCPgzdz|E-ENTnZw^8s9{b$kvLfD0o2555|F(4Pl!FT{Vv!ko;vz!C&`i>| zvzUT}{~gKy;=#FM;0$=H-NW?P>0ZLhaqf#Fioaw)Kq>E@dxBJ!pMrOiAUq;q4hebO zD}I$d;knyr88bN)@k6YR#2SVK#Mv($zMbK?j)0*hZ|@Y}5$b3ZDth>jefZT^<<{MG zud5_Ojws0g_`ys8^H_q_B7)0ct{7Wnlucf4mrv1xR3UTEeFQ-Z;Rybs!-GKEZpvMPt-$MXTd#h3(pf6rLDRb*0NZZ zXK8Q+EGLQR&}~q~0oHM?>H)HlQvwPlRz2I)FwaV<&ZyGi%wrp9l%RGWy5_3b^_eh_ zVuF(ZGkjfVWHl{$RBe97TM_2~wqi%aVdQf+Zvs9^}M`%5{&VDzd4$bdXpGi6uw^*OyLoVxAHI&mR5d+zpR- z|0B}27d%cy1(!@~n;rpo%$m)S>Ah~DZFxqyyFgZZ>cC4`E6=sEe+72*2yOd5;T#7~*RD?A5-PAPig? z%l4pBzAUr%W`yJVVgyHEXAsMeJaXk!`V#Do_cv0ZNow@aV;(`@P(wK{Z|B#3OEUfF z?&igxaW>}_PF-Zs$IIS6j95ggr%$w^cWpk zSOEk_LUqQPZu-pbhjr~K{}2Sv-Qyz@wq$RABB;=6w|Fip2 z4JCPRU7usW@o!!@)Qxm6#~U9$&|8W;3(!84^K>Ut;CQuL?8nmOE(;B7*j2GYFU3*y+!8a> z<(~}7uC%P81n;Yh#H6ok*;*FN)+hg%And~dpMSpK-}l~1t-I=KLkhgs`3*^bV9cwY9F%g9j-N1$gp+4Gr$ld zD=K<($|qYPUEb^l)0;rLhTlafr3v3KISZ263tbS-9H7Y6pvH*vx9)wMQy<5srWN!4 zjHLx+PMvgC!}r9URLMoS(*T4bx%Y(%N}$F+xuLo+1qy-bbM{GbnM2G_L!OZt*PwxP zgANu1h%wEd+YQ7HF5!qy~1jorAnX~X#w^p zL9gx`iTQD^OMQ`fDvo8;AK@B=OrrjA15xS;5<8~RLnK*%us3x14?;~wwaL; z<*NVwG(>bE!{;T+P*zj=jT?Ju(kbAEZ!SJUhWcSrqkQ&he+vozOK-2jSaQ78yVin{M?? zD6~at{CUho;_9jD<(v3EcRtS(*)|}S=uMWdGO<6`a_UP5D6l%>PJ+Vp26WM~A9jKL zCwGU>M3bYj|27*VQgFMn0&1~xK;NS#bP5B{;07NA#^zfSaFnC zfU{$oSU&ejd`}(isvH6qEI_;|{ixhR6D{*Zqjm~u9%lqfAX&~^&n|tPE08#@0q<#y zm4qN;jn%O`ek`Ay&kJXqr?@pe{s_&_C|JbP;R>ctGG)7NJUtCRql4+`+bqDqlIM6B zoFY?Vk`eT>->vtvaWgk2s#p_#l?scD;vSWX5B$d*Riw(EozzaacR&x#Z7k^Qdm=SL zucKB-2~j|b--JSlO37!Rg|pBZ=B1U65?<^Q-3{_g-yxfp*n-{!`9!U#*m!}OD?zypAjb)qNc|DD}` zcFNW0KyR)5&nEJ$O#!KNaOuidH(rcJwXDV6RxUy(_;Vm}ck525=e6#v?_h12fHT_x z06e#jdQ!1GHt>&u!>azV2mzqW!?`~U685A4LxL`5NDvZs!6yEod%1j(2e~E8XAqY_ zmh|rL(2QPSgZd@K)UF_G8%I(7-qII!#U(oaNpJzpwqO- zJBarPCz>>Ry&^}SV%{pO)a=Q-&2%jwy6$q>2nKb3FA`AF;v$vRJ3_*Bvy)Kh@FIFp z7Zds)q1S7Ud_Ir)($Qj-bI58Oq2Gs$5<}fKNhMlVbIBQh!*RTA(u_Y$C)|;@(j#(5 zjSjQ$jQ>eO+{j=CD2kNzy$Zq=Mlxh>-=sm6d_w~n==k*rs6y&xuA@%d|0TDiv6m!s&IKbuQQ#~x9K3vbh+S9KY}e`d=|ZXz+I%O8LS3j`En}iL_+Dc(aw|D*jahuK=+JfSH*`skqy$NTD&qxrCz0 zuyu)vEn6qYmw>pDFDiZ|^nG~l1Xa;uXu4pE4MC`7v|DDecT$F&mrUR-L>Fq8oo?bu z++H#nO0RuIr51g|4mGH#-&iy>FU5NfNMzeab+**7pk|(UA?i6xTehR0AAgS*Jiy~GxiHWDpQ@>eO`5)z?CFMhD_2ysGW#OEI z;nfpT>^q^>y=`P3%kaTSfO*rU|NHg-3V}Tlvmd}uF70lF&)NWClb6v2EdBnYJikvo z6}{*WgIN&zk!As0)&}{%t_@NttaceRv+oC8G}oMRtu`4wUQi33xiZD;dk>F;z%X$! zA$xvk)i+SI{@NrV$g5MdZr8nMCGltwj(DR!>K7$=pL@|?=&SW2o6O-Iyo5y944g7o zXT#A*>dH6t7ah5|#-McYE5oXiE=4Kht2`bsyfeaQT znu$X&Hi%*D_~2LeEbaqx|3bmw0vN;f+YoZMCiy=)BJDJZ8>O*l4?n@|0;vKNd{+OZ z;%gjDAe`Sk+G=-Js9lRF^G8PBL>1FFpQm0-g+iUu)KMgv>AmJI3V-ldrl3$b5(ad0 z>_nBH=gw5~m)4&i>=~Vh-6{GEYe(S>U0tFgmQ-*dmHc9;V z_%AklpkI`O5_eBuCg%h)Fx)i#S7>t|m2i-R(?+r{MtKBN-pb>mN*V(hf|y>!(FF8N#DX(sR`3#9Ej<#W5O6^VnUSCo-gwHcgBNH_ zB2}#PT(q!^%-92vbT($fPoPBuZls{WaEsJy+W4`tv+Zdvv#zqObST=J?;K5cq*PPc z_3}LAq=E!~mpSn`k)I&f(SQ-Aop4_RPnAEU!jxI9nCIQwmx?K2<1b`KKN8Jv;Z=~) zPndu^nh9vZl)d`6#H_I)26a%j<-=GDeRWWpb33}i#Z&4KQ}1r7CI7qF@*L)0Y@`aX4<6Ub9$A zAEtRCdz^}UO+*VyI2KmW6;-Brmf-nm#Po;b)FrGhUZ%e7%~yQ%>Y?c8|I)?e0m!pn zuzBxX^A6<-<%9OV+739RVj{{W_FN7|Ps|cY}d~yq$?PI?)v`_MX)v*UySd0YW0)vMktcn!RYE;$`*|HI7}rNI zDLcU0WD4xvtrGQMU1_dUI(I2pjfyzzVc9{3v0&5zXLl)ge;RfcgSi4$>P8d_v+O%i z`}SD6K4aNfqy(dk(VOG$V6NjaC3$#NijP0tOwVjgzxx5JL5jaXFEboe0Jp;*=fu$sK=_}v3L+d0vYibs}oeb;)v z68Je`=ZvK??0g@gU)IFyeJAy4V%A1D)%5vColx(7Twe9opjQD?Nfe}o^1?T{xEr8CEj&<{5i6Q6{Y6pr)?UygpB1g%yQ4XL)(-pH#t~cAzVi3#mQMBl<;l zINnJ6tJ82>9Rd21>SL~0pUfevzz}9{BZxMfK$)dGDf7u(#D?IWo-VP1>8`(VZMemD zcnV{@0{HPjSePF_UFIyKMyI=qft;`wO25SR1$NbI^?=nUCXhfd2)Oatpaa3bfD^6p z8Js|FI^Q_LBtI1vA)-nmEMG)Bv^5L6moEu==Gk^|eDU`?L`@rWrRwIsIz^ius&nc3M0C+xN=YVM zH$YnaTZF{x%TqK&7X#IVL7L6WwCtdFe0CX2XveBODi0xm_lTYB#nH>i4T4-&&`xTF z9}4(vL2f}@7c6??+J)sV9I5dn)GnZDb-kyd+FA;}xlCGNACVXNe-s3u@?^RVl?YmZ z_zspxtOm<=Fl$GYEa6n2E>ozC+X(};a$5uYdz!fp5{(AUuey*0o29Z2leL7^SJ4{w zsCr%zQ`ksm73}=$GxNU9JlPLsgZ`zn-TgH=Bqlw}mztko`H-wR3X%v+JieMdQ0~q3 zhgpfMZU+@rmRRALAtX*{wqU(}r_Vq~6sO80){SXuvsHLZe>Ca|8=O4=!KCXXI4wn) zNEP3H<5k$0EbA`PFK$<2JSc@sC_H!*^p(7sZhNtTeW}=rE%SG{hU2RikI_jBbQ(Mv zutb8STG~Lj?!20&vLBm0bUPBn3|#Q?r>el*&92)CwRaguDzfD$A0O{HT<@OI4vZt}Po69- z(YYaPYLJ)k4b3M(>Ea%xc#zd&@EW;^u*FBMs=XF!Eo5tVDP@A>&>K@6KgX7NLz)EQ z-0giLJ4M}IGzR?PyZ;zJMXU^ok?~U&p(2JZOe)@Enn=C!W6qkZ2gn+) z*V_pxvH7_9Dt(H)YYc>OD^F8tOIen+6o6*kZh|r(;n*6%6sb_)zq}aHcWRY#w__qI zX9_=-k6@6}_3X_Q^6sa->Lp#&i~&NdpgzMs^|gveP;`3&1CE}~O~)ld#va)Yi+4z7vH5g_DMJKKOie#woMtVb=u4ceN1Gse8mMGh?9jKO3!x|0+;)!QTqMQ`?HH&{OKJ zBYorT-cP1DbYIGSNYb0NbcS`tI zXNV)~u!W&t)5QGsgDRk+zzh*6L?;#{pcaF}5S?l$DGUaC35G?{0}i41q_=w^SD^av zXEB?q@vy~;biSql7I>5iQAxU{pv+kYpite21&zBgNs?dMF!2@Y6In73_wOj&y)VQV z0k4Ky%LsSQrCJWKzu_3cks^5`9ud0oPv!}-9=E)ir{tfC2S90;d%3GggDEltqvZ%5 zl#D#Z-W`XW`Wv!7-j1kIv0%^6z(fihYF^~e8>d>nbKwtOab#z1%S3*7fcszhI<1CT zNz@ZIWgkH+qW->Tm*|zZ5 z4M&@CP4Crui2ii$Rx7^*I%!ymw^B~=O6fZ)C0cWGXAfi9eT{J$(e*^*+#~awVi4{q z=7mu5D#AUG2;-`sX|DppSg6$rUQ95ag<=Xd+dpmVQFxeB$-R0b7hQ)r6wJGoA>b4Pj!CEgRd0cJ|BNyn8af$T`7y85 zeMt$?F}MEN9vN!(WSTcJjMWKQ9WIHm$!>=CB$`ZGVXP%CRSV{pkpcgVq`KZ;eq1q- zGS23aK%tRYL~wgJI(UWauajK*`5bQBCMXUEl^w8r%|P{ANI=Tookdnk4Q6toH1tiu za%+f4czrCKnOohJ84G-of2w&SCO82OqiD_p zLD$EUcDHqin6E`O8R^~a&^`34g*@vl&e3t#4b|egc4#!7iCbYHwrmM)?yO zH)gu?qdwC<<8ChaMa6k_T5!&Zb}WpfVyNt-OqYb|Hi>p<(1Tlk!UBF7 z=L$TYlF1giuvS?hQ}(%l$~%3rqwDv6>~=-ct-|dli*(73J_5zNTLCHN<_$_cxO=VA zni9`$Xj65Hp*2+;0P!c}Uet>(!$JA38N2IM%;7{PH2u8R4$j1=R30i+0r*QFJTDwA zhz9K~)57ckS8GVNCQjb*cLunl20{Hr`bi?aXW3O5_m-*b64ojx^J+yV+AZ0oD-y;| zKm}io=>&9{E?5;__cWBs0{o;RX=DF?iRA!wJIg2!ebEUzqs5CI_BJ0!gr0RfvmD?T z_A&6eAo%W2NjnvcrtA?d(r22$8OTBt7J0~II@Z+hStMdnfhM}bZ42L?j zUxP7lR?DS!np70Q>O$QggL8K{TEjyUIn|p<`|sB(=8qG~V^3ygQ2qT%GUMf~TeQM8 zo4Cm`A(!Gu;*!AmlEVOcahWg5r*+}t&KXpnxTUl{p&v*}9CIMZ59U<&q!BuG0*2HX zez_7v)rs6_GGoi)TfcIrp?&=m7|;=rvj1MPw)Y?wiyBDnq5Lv#+%V!d)!|VtN_Nps zk;-@D6U@ve+Cc(lqo%!C^+lM^1*7-#YcR>jv6CEUdi}FB9x>JJU~WKEnrnLZX5?z9 zQyBy21BbnBFAd`9+UAkr^Fj0;Mk%#yT6=8TanN+b0AC$cg$Bs^8SYJ27dL)DnGYz@ zi>5|Yr4^xHh0dXpNn%$@q7jm&GpB5?q->EP%>XVWer{*4e&?_Lj@IMu>~=Rz#;CGv zc-9rDj3jRze;tYx<0FrUffpzGwG%D)n3YwDibf%ZCv;#G`|A?2+3JbI+R_Nu9%t7I zv1MlAz*cym9yyRQp)1EPV(e1*x7qv4)l%h{XgF5x54%BdAzv{v7JImQCND3vlxRbB z{(DECa1ot4qA5@Ni(NHBsS_3EP6;jYo-`hki+L@-h5BhZ49d_v$rS4SpYCyV_wE#& zACkN?zv;lK)?UX50B;vQ{J;~im6m2Hq<^QMv` zxvU&p)8Lr175_3EmSG<#Z_0L3;2-5s{elU&Wy1k5e<;dQm4_-6d2hxB_A7k}g(YrS zX}Y^pCO0?(*RbFrP^O$TCMMJ-J`xo(ZnIXAtW6X}zq4FVx9d>M)!&JX2B(S?o3)H2 zN0vP3Qz6TXCptSLI_J7wd{+Y7XO>z3f}#nkK|rnWEuNnMx6ZLKZ_i{Iai$|XhEn@U z6uo6O9k&<#b7y5m5&IlDob&bl@yOqDv@O7$45el8O?SlOLI`%^j?)h#Nr(cr19S)Q5LmHH^rOT$^Lt1BGpcfN=H;;lS0De%lX z`IVlZ4^r4Q%Fy;YFgGso05OjF48lTbgfjLHFF8Fz)a3I`6mMOV+bQ~luy@TkJh4Ec zf6@j<1^}DvatM+T9V(eZsL~$Y2K6qhtMCl5L)JgT=4f$TfO+@Ic zpwjv}t`!Q#EGg}!)AUEH2KQW%i0L*}@MFrjf|fNFQ3 zKLU^*2-|xbqx0lB$q~F0kZFnA&1}x`K*g#4JFulv?D3u2m%Clg>>qJuih2X?7BW4j z=<9ptD4Mld!Zp?7bWwJg&?hZUJhyCdIf+I;zMZBE#xObi)x@B@?X4A@1%&-?gUH|w zq2vUIPexGG3~SI^W->3$R5i9hSioGbeckT~(bCvld|jh`xIPd3DEzI`{V{j`3!;!9 z%boFS^%F|41o42~u3*EWFK>1;^jk|CbBAdiF5 z{fo+7e>>M2;N823Geo0UQ$a3Vp3^59UOqvZ9^RIZ>sVF?qsxKiL2Eio!{y%+z<1WQ#fhmjl!WD{AbJc1DrYWI<$&{qK<7;RrygCipgc|_K=_}exxs6(M zA(2=cF2fvmwue~GH#(4lq^zMOGNw=IyD7xBFtguU%={}@!X1XXD8rW1G}>Pk(M529 zKu|0Xkffqmz*$R}OdJV|iBumLI*+fBMUMHye^DqiF=IkEXr8bq^8GP+t>{1Jhp^Q# zx77IjXvtvpU|%VDPK|6UfE;qh^ktqFlfyd<5KI5h&7f?asYd^O){%+T)Oa;X}!6(7YVC`lI=tz z)3B82ijn#_>0*~~i;I;?oDA@pQZxWYjTP!P6;KgAM;|?&u3r04B26R)DHb6(!?=i_fQoGITs0+m&7c%YvTV48I zvVr`@QWh8xLH07aPm9IO zg8QPHvE(QaGE`-s(S`c@w{(=M;enN(4C#W3lm-ktKBdA!=5sT{77y1ozc)B>3oSka~{c z4Ip!O^iY>s9}6PH^w9rFBoUw;=J^(6V84W=``rzQdzyo1>GgBKTplHC2?5DV+H%ms zVK*&50*;m*qWV~}i=5bI^`#2;d5Zgs`@Al~0~ntcr&SU-e)8>7Tv-p2*{6W*1o=$R z?)wueLd4o4)dbL^4+Q2kug$#%zn{;3uXJ$rABzW}YgPPUeY4b1j=lwNMXzJkVVjtx ziKt9|Ji^Y;!#=a%H03PYx{#GFnsae~fCs($jCuBdL&u7EMr>d3uDO_UNbkFt6>X?BP_eiheu^hd?G&Kzqy)rcL&Ef@gH}5 zlpJCpE@=n;;#L+z-)VqG7F~N;`X|k|mkLZ8u6^3-j8d`GO;=z~h{rEox1{0d(EaGo zzMEoV7)br$nVT5MB915%DZwlZrUc*-BsGO`oPgjAfKzMJf9B{PbE#8;Uqg7X_h&j2 zTBJWP&V^PU|1be@Z@36OAH;$P1e5Y!Tsel!1bvw88vt^Bxnt?PHe1vlw7ZPq){MEh}^odfMakqsqz*5ABD|Eg<=W$v3l6& z2>tD41v2zK*x{ZriE`_MfH=9L%htt4<$@47E0>pFPj`MIUXSkGo?^R)*>h$EYB{@( zQebFS9*7ow@gW(lm?q{+9-%42>sTm(R6Ma*8GadS7w6FUBjkT5THT)2t4n4O9 zYu^(Ge)V*z%3x$^#CN<}(3c)ZMOhD=M0&=!EDe1>#Pl}#=nd!DiVy7I{thS}<+VSGh1@a2da}XwPzEH)B zWA8MR2P21qQTSu(5y{ac@{gWb24zE!qiP{_9pJZ?V=-tj<+D?L1DKJKYml*|yAYe< z-gD+8siITh_ARo0x_cF~HXl%MhMRuIJ}YzB#8T{*Xk+FcoK=wVdSTXJf9l;9?#nil zY;lC1{%ufa5M|`m#=GbQyttCl#$BAuGLn_8dTfOdqTY3c-ZEdJMQ1h0vcDXY1Z#9K zfbOvDzHR4>Pfw8RJ_|Ebv>Lo@8vcSF47%mxLl#CvKScLKK%Ym-i^Uf``#jy!{!PTp zf4X|CPbqR}Lj{pzx!OJzyoN^mzvAlmG`$`XWXmhA-kmijJgCbLWAT}e@Rnnjo>;Y` zP?V>-rlm>CJ0>>3MW&yAi*&dc9V~Hp?bU?YVqy{*NsXO5p8QEG7c@-nb<(>oHM?Q~ zCRUs#LdhFSbd#5OQ+vw!21IWQt)2?0c#JZ3*SM0lJ4<^z<`cx>bb18QuBUidrbA2$ta<@+Amg@25N0cHL*-0b-~_`SQ1yN zjKPDsv`c=y?=DuW0)o>n^7LTkY3UT+y@H!Ys+PHjjsY;NC3+2Ez~J%uaCXYEznkQ5ANn;g;jr6+%JjV?`lba0Boj*i3Wtwe$I3$D)C5+XP2fWVOIm6Yvw* z=SgF&R$$1ca`PK42{_B+l|I9eaKK>5Wc}_QZC@+Pz0*$zs>Vi(O~q#A{&3t5G0J#U zQH;SDzk9XoW%^D%jdY2~$R61JrGlVbV5hNI>NmX3r5yzxPm3KOp`u%w(MR%}dBNBa z-6!i!D(5Hvg3n!R3P`1n9~M#8GE3v0jXdZD6^y4HkbTm}=$CGX>7Lw+LPA#-ZC|gL zetGxFv^9?HmU&XjNrZ|2xB%JVTHqLx%Jl8oC8FzR{}T(rEcV zb)AwG10F7((;Pa6pREAK*gIAZ1sco{zDC+hEv26jeovitiHq9iH zm!EJ+kCFyZ6-l2rkB(H-jWBOY97l0Vcz|OcROgCT$}8Fp6n6{*k*N1`&&8MfL&0;S zJCRx9(kLQ;eZE>otY8>@E~E4mn9JXQc~P=2QIW`T*|Y(=VGQhPqvcR>h6iv2LU(EV zJVfF)^Xpx7V4j>1f7cAi+rF~qmPORNHixltSmt1Z64T!yRoGKD2kctu7Stc+hgvA# zkhza?P^qXY$2u29GSoDCCjqVe7RMCo* zYMdtp{cOhc)c(=Pbt4IRS6e_o>D3byPft}A9f5gK>1;|V>;0`5T(8&H;@k!mDMsh+-UvV_PJffVl&QMG@Ll3TdsX6?W*$P zXPNG_%Dgh^c1O|;z%VT9ss`Vre-&a(ACKRQbU)gb6rcJlkTJhaz$(VzX`udxLo#QW>_Y8XP4D^(?kk^y{rS6mn!u zLpaerA<^4fsmSH{sxhF$UZ7=ISd31-`%k9Gi3UZ|vLXVKUA5<5hb;%*wKE5;lU~yd zRFveBVmVL7Iy|Oe8EMFQ357yEUBE(iI_340J-KzG|6(eJq6hP7Fxn&|;QMJ{T!aJr zX_Kdzp-fn0wc*@-C<{&hx5K@N1HfFF@Kpt+(Im!?c6U?OoUCF`s(q)$Q;D3Oa3Gjl zm61!mV{9H%XC6h&)fsts!PL(5#!sZp z3H2etUO&y6Jn%}RWdc26bFJGe=nTk5mIX%eV82O$DLb)yHw%O3&d#AF-=#H^hw?rgJ=CAg8Ej+?Pul8~{o{wZBwY1fMw` zA@80UuU-pEkiumRDWb2#B)`|y(1z$)?xT+L^KkR84g*I_->u&fG)_B(iV?p=h1HR6 zSXVtQw4?dyXi!J1D@@U@zh93$)z)g0M*_F*+WeKI&fK9GvCoepu@-xzog$r_6B?Fi zZ$BHOo6+-8(CPmOoped;F{JY2M#7^E=-b39=$)fL8!e|kAiygjdr#xTsZ;7hv_^kV zVSpWe>=dw>D^)$f^;hZXR!<20TdxGPgZU4Bl86pJi_ck;%(R;gv>G4Hq`y%N{{=~GcN4Z$-v64PsU$ii(u|&vuM;WOyL1XX6RQsZ&&od3nMk|5FWZPA{dm=I4xC`p(JWu`4HBg`$W_Fj^HLD&q#P~-h?8pC%}M|ghKg8vxPDF;iT$j6 zz~>U0qkc~_r-}fReC==`eWfpzW-p~x}T6-*|I$}IWAxm0iYxynT}r7q~>IM0L(TW%aY=T zI{HTOm&Z5N>!kRh*!G0SXEs-g6-E4@@bi1;R+n#il?94)WH!UzLX8!Ac6l6nlHL7h zNo_5SXnRJ{D!ysK&M95sJA2c*Bd$6qF68DI881mqgzJ$q8Uy8uDG%5B(VSOyQMD_^ z!sz(NS>@If^PyuHjM&zle;o!tcF7%$gv@x0hNBafm$a%pVi{lncK!TTHO>K?kbEQz z04hYeh5+D3$S*p8v)-Suz%y!phkXojWo7?$QdF*~`b>VY*f*FQHMT2?-#6&c1-sLs zfh{V02spG;@gme<{Rf;1<<#Ylh_U-Ws1czFbH=V>Hj`VU@v$d+hA`;Nx&$wMY-Nk- z-XRkr%>PD{&daB%K2buK=k?pM?*Al1$?AfXZ(5+t5Z+{aY+oooxkAVIU!9-YfiX3w zFWZ>6WH}|Nbs@;KRs`))&e({sO-T$$?;mbwxryrM8&ESFa`QgOag00fGLf)`2Qt4K zm(I_26D!>tH1S9Bc`eq3F?XorpPi*il94#)u(3MaNNcE$Cg0i?vF{62ZHel`Jlh7u zkEF^)6@e?)VpD<}WhqBbsq9e#o{vQ=?pZ5!RRD!RkANn}xuld-h*@4jV7`^eu=H@M zJ+wS{l%0#?Xn5I4N<1JnIagU7!fxLrtLTOwoAHF3EwmuWS{7>7DK!w6T(1Lj!A z{_Bw0;!EHM{5(m2S5jrois8`v(fS5a4!wJmIgIja;Szf&&B|5T`7WuR_E3ZtHS3CI ztQBNyZSO^9Yjs$wo(u@se=w6ogOJ(vG6TaC3;HlRZw0zYPeGq;F{1;Kg+s+S!D)FQ z#Wrw!i6VZ1BC5;DOslrjaj%P?Z2gLwuBi2L3+g64B&d!wbU6ac_$}P&JcE%H(z?xC z1$rFD|5e$~M2zb$Qs!O10RF^d{{1eR#Eb`Oe^lr=)C716z=4U{Nnu%LJ^j8Q_q1|^ z|E0j>JbM_{mhwdOP&lzU+-c$`)m> z&1VS=i+eV%3i^s;Gf?}|;Rc7Wfl8D~%DYL2w2uIidAo7CwVqVRrWd1NC=6C-B3opP zLHu(7RZp@+w8if6R*W-Di?`#qu0jk11J2cqe$h^vhQxDt|54m&>B;2{$d)d4*?op# zGZKA-+0s&}KpK7<29g5)U)c+l;TWNHLP%4^JryINg zSu0m1G%?%ZJlLg(_mN-tZ0tiq4z-?pdJFB< z7?5IzkQB*8-N`(xU3?#sR?M*S|5`J4LRig@TmW`hOrgVx$0FJs*7iLS12~UE8rJ)~2{( zGHrSp^90w!a3Al5oElZ{$_9mZ0@E|1A~71jjNK39hZuc zi2-xv`57qiGNiGe&vUi7Y+_qhUIWCqxdz5K@r25!cOAYDd4YllF{vmD8c%9IOP{bO z?lFs>IwM?r8DAhyv}x>%lE3f{dQ0t~?wu84Xek-@hSG?;hTEBKI4P_Q`D`W0 zG-Cs<(YJx5>)>FyK$KU+8suIn72IH7E6WlQtya?wK+D)Ac>RSz^eKOLPzmfXgg78c z6iLsgf*{d2R{=2E7VA_cFnQXMr_`Xfn0o)1l412-pK;3zMI<*7dgtyTPoi=t(}>zg zNPnpdiFyt}za>(gtlPvzm?i>ksIdYif&l;TzzQ8= zGb?Vajp8s3aAfb7uHk59sy{1&Q)guWxJyE+A0DhqVT2$BQvx6--tt^?OBjpYOCyg~ z(Q_Uc1u*PGl6l3)52{W)f51iPMs=7Q;3v*}9}z$>6X(CrD&+zz?7fY}7d?|OTHHc} z{51b9RV8Bb@CXDaqL?Ol?dKGdtGGueV3)S8)KHNZZUwHVTM;6N65R~(FojLG^YOy8 zL2`4RWz~CpLDxP27zDluws>2zD|6}=mlqeX2erUMdnsgwv`Ng3c!$bl3QcP#Hj<=~ zcCsV68Bd3)Vt>_$4A`Q0I>gH(!^!1D+`jF)_mxS`7eiF#&-W zRZ9Bfah87g)+;^L)YII$jl|MWm00d!W*&0ulzt@Va|ky9TQ;EO9>E#b6J)#xWf|y8 zWyJ$HF#2|^_KldR6nH+p!f}2yaL5c;HRO!+7$l>7G&nC_Cvp|%J0X6S3>MyG&MBrS z16cMGsXza*&Ugn)m#LUo7WkX2R||^Mrj*r*)HEZpRs8Pj_420(!sB)16=WxT>Zu>+ zl}x&A(K_J8=}et?Xen_@&S0VDmx|KT+^$zShm z{U!Q=C&NFur2u$sAFfIh7Q-+6++{Y?!iAFf8g|2E%mz2t7Nb(K2b9PSNncI{Nn z7NRzant{Tto2|G1MJ@&dTS5MAM!)W2BP*4V!gbXWCJRYuU1-_?1z6YwRjhbkkRRs6 zwE9?QA?73%kk1BMFI+VvNvr1;V%eN$xG1T0U+g$vhNRlzi&sc6J<40H9d@kfiU#M_ z#ZDNbI?GF?=qN7E9lQ2>;Y9C*b3CtulcEj`+>2ocSoWKJF}No>aWcWFhD~{}6$qEl zKl|R>nq*Edb0ug{G<>r>a!x~{{M&4$gwuZ1X$!(|g$HqXh$=-|B&pFPnpP2%lqQ@Q zpl%m%9eg7V49WtXX$4be#`|C{&Y}Q2QU|o(;}UV41QC zb9T+nps9WF5EhN?35_Sr>AK&2Ezf2S34AwPJ5&%VB3~3dug5wjLV&{15S-PNYO#dI z_GGImL=Lf(0Di|zt&H~3@pN>Lpy73q&G18i>w0ZXihz9Zi5Y{d$0tB--9nEll@8i2 z1Y2m-4q=Wm>^0FM`#0KRJ)??UG95OOO>KBs>6YGmd#UW)&FhQiLEkB$SGDXjJVUVR zi#~$Pw`EP_L-nC4^N4=M_)sDSrfRtq%|}^}Hjf-F)GAdm3CmJdu>TRBsYG+_;va0i zWXTuP8^>@-Hl?d32{ws@s2NmGmVqRC3;S0I*8QnF#i$Gh-Fo;g;J{T~%Rjr-i>c13 zmXUrSxxNY5T!g3u8{EB+Oi&ZHw3|2e?CrSItRgd!pkw_ys>^dt1CHfRC*LoS=N}0j z08KnyMq7>B2lF^3>kl?J0?CBbe}(US5`GbJ16u8bj)V!EQr;#}q&0W=zzEIuH!8Cv z(3k)pI-&FvNy3K>@nq2_qi?};CbRXI)8`RRaNNF;Pv&(v`K2W6na;9XV|^vBIL)qSEXIi#MfEAw-bf#pcbBd<$_O}clBqedX zKv@cXLJlsjU{Smu7`?DdIvhgWb-bM8M|KGVyd^5&z8S@lhqV30jI3tsrGP>FpIpa^kpxL}HFGV0^s-jbi7Q=<6gk4E^d;mgOG0AO#xxT^yU z#~fBXa^&9z6j-xI>l`wcn(VIJP4*lKQoii7t&HpESi(g|buWvNa6!IiH)c~*KaI&7 z7_`n=(F`_$k#uX4E2Y*}`S_0!P!O(mbG(mxJL7jRaF8mgha{fV*gOqRUjA)pM>9Xt zV0H9~62kYL=$DI?c3<4tL-VAH=Vsn&W~0z=S#0R}*Hf7eImn77FCfl)`XN`6`-w= zwpw`X-6+UDb_W7mt_rbYtp-&1;K+h44H{oHf1T6Nrme8g*!iM1QsHB}lIj``Hs#k& zUkZ@#EnhU!kdlbIdG_a5%IEsUQ9rOzMS&xn(-fwQO%OD`GaU732FEk;h2u1(eaa|0jvygmM*&YZXX3hpMkEo)Akk7@F7hIqsP9$dqEc?qwxBr_jLEF9^zO zneG;~dHH25kfbz?4?c;5u|8I|^soF4G>cM(xov;G$IirDVL`xfr}eT=Tv)i<;^uhp z(@JybuUjgnPmP{VS;lsvI4q5d#Z$uo?a|_MbLo3N(QoN@RuhM|OW{H5bIE=x>NY&m z0WOIobzHvhk+bWLj5KClOl1mzh#Q(#7H`Xmym1$?uzpUm!afNDR`lHq;sio!&IL$m z@$0oN*%IkEA@}7q5z;6+8L&3RM8mrwjS@1&J%2Wu|G->1+6B#?sbv zTT8MV=JIajNHea-r-W6js%hp*cH%?7qR_gCOr}I1*sgrHG6a>nt3LtIJcs*+;*>R1d_*pZ^GT)QCbEPe1#JeK{v+jTH>rD1b}NS%^k5F z=}?Dcc`M$9%sm2>>b{XO@GarT7S&^e=pPR{VqFSsQ>s27XGX=YmB_FFY5Z6m;y`0}PS=s6P6^!Rax zS(^r&3zboT#O6V4Tw-=LxT1*~-2 zJ{*qPZ4r(9yFV9R<=7-n+GDBLxaNPtAZoPs+Se1VMlQZ_w7suKHd6WR?Ii&vC_2Eq zHZq0B6UWNz50a0{AsZL(R*%_-q@@I z0}BL-vCs2s*A%f{JIR5;(@Ou3UAR^Q2f*f+F3N|%_;l51*Fo-EX1V%F6Y;8f0kXz` z0_%*D>4D2#&HY=+N4xdG9-8zl?0-(O8klA31Gcxk>~9f1D)Jr5m{u+EG1%HxGfR&e zc<1qTuM#hDMQ^$u!P461c>z* z8+!Y}M%`hBZF{R#G>BSt>{UDQ>U=3~?C12^(Y3|U#inv*k*@Bgmj$~KXBg!Qd(R23 zq^#+Ckvx(;Wn<>x>w8_U;liduoQ;-TE$9u3eaQZKW2^PT=@9@yGC1|E?59mATCETK zhOLtU&2Svxmij%hIbl`gq;dmZtQ~FK4|JBd8K!fjbC1BYn$GbW!Qft z`aG3(MH{sHXUt*yan*Ci^4g+s2JUM(hwT6mVK&!%O|&>Z9LXfKoPifEyb7Jsptcfc z>UwQ*W@JA8@tF&?Ae5RCIP5a$pJk4ywp#_p>P3Z9_j%bb<}oJ4QnGd@4Qibn@3ZvQ zd}Ja=pZ5ZlfC5XSuyy2)^+RDIcK5}asSRB*2{1PcK+eW_QLasXl^K6Mco-n{zH5i|J80@>Hq1gMyjzBA>NhzIU$o~Dts1j1KtKgzgrHdh z_8F+ZyVESD4pYKae(3Y5N?eYaNJcCAV7{@i-v%P2mWE4wZe2j1eI`Hn5SYeL`s}wMQJkaPQukVtn_U6~7Nr@Y7*_90hQQ@I-@a|Pshe4?k9*i-8y``KN6~<1h zpU{m!5F=}veoIOg_ZY|~6`&tE(tCyY9ow*~gXJtHEWK&oPuUnlGNI^=EHn7l?8+C{ z6f1lyMZxobcs=F!27+pA#zD$baxn>l>b5>4Of9`#P?#b@i&}8_(tE?uCs#ufnyueV zh^vvcR!!@Aa45~{X9emU3{Id=M*675e@~2?v9qtg72?ys5}SsJch2gn2f2s)Q*TGN zvML-(>Grn(yx+})S)~WC8mfWqT$UeEn{P&|-OvuE3^Ae8iT&?Wj`mRrFAgcRyBQNc zLnZ;VukSm63B;r(0c$QQ>r6qI@8=Vm0ytSpDO^;X*h(}`N(U3|k?_JdIPv79$kQ}S zwH;B4NII`+LP$&!UVwlc)}ga;IaQgzq<#$x3T+Qk&Du^9+t$nwjlg`X#?hUWZ_7zg z<*H3a7oN|1GmM&Cb{FBo|9BpJ9_a zbsFem^q5LYkLgU(uaVHxXu3WmMvEyL+L@)7;+e!3I@uHNkh$-CRS?RDO@BlW)0##wMw8Bxjv+|X<{K|XxWbfMaRef>oa8}3 zbTm5#=n1mYR7vQ+~3X?ID|#u2Wg5xNT<s- z91rC>cgxH}hI3hs7Q=D-XS63&Prq*z!G@N7s^~!K(I!a+A}`_^Pr595AHa$`6gtjh z>k#x_?FcG|SPD7qrT)l+0u8N*-h;v;{ErYvqnJ1kiVTnift?No5-N(b6?G5h&-m!h zE64;rNz)0H7AD8;sW3b4O>{@2=29li;tGh~6nn1)i)_Z2Z9_`1Cb_L5g!setAIF@i zOGgp2EQ~rkg=TnPZiv--h_E}$vUI@`Q!$GEY+f$UAnabaMZpeA>pR4TyoW9Od-_2+wYLfLO`O~BU@erKT55l zQ|!d2U~mFbBtsge!zgwzByc5_5XLkij`igcdag=ks4W)HoO&n~U*2Mhe!ZjFvOvk< zE+KUw8f{A;VW3MRGVNc~N^=hF(jFZ+mprSum&PERGJ@RdTEXnYS~4mF4gM>HL0%Q~ zlJ7{tO#l}Y1{~#L3OC46hjLQo5>~TP2KC*;*WGvN$V9FqvGUde`AX{Wn<_LJ!<6@U z#{wMn^)StjBoN(~NXWm|%u@BD9i~WLhc>R2-YeL^dF6hznkd%?GEF58)!27U4WZb{ z7~kjJ-~jc7EwzN4JY8)=_@eb9Gkx@>Ma6QAQcBcBkZctRJ6^}IH)&fYHFTXqTt(L; z<$q43zB%EuC%Yp#_G4L+%otuIAMc#IhRAE+fCU)e0KCAz*X0-e&utl(R}Er$876m{1T z%nR^X`oblfD%wxKf=m|h*Z6g?6Ees?Gwk`{sF!h4ncwwS{%UQLBS_pT46D{MMEdBo z(qIS=y@E-Hmd1>VMEOgnKgNX(8HVDM4{Dxup$;M)Pt}wQ%$Xg6ipJRD2ZE}@>T3}- z{Un^2s3aS*cGElEv|VoyZtH4yKA2?MS?5iJe!Z=6tQ!qcS)c$h*ACF;Xjh1dWB^aRRgu(c!HX37F1O> zZOessIJS3h^e*t1{F}3)aZ{sOyw%eBF(aq)8qgcZ0r5SU?F~5+2R_ieCSodrgC@5B z-QS?Z8TmehWrb@+W8E$rYh_@<5C?202nsJ`v(m?B*}M*RdYkI8k1y?_gU_9x45zt1Rew6eGJ&Zf)rXQ z3Axi568kj0=oQxIfym6FvY{y^@mF%iK0L0oFN#vIv-j#pwHqpT_UY=`J%LJ<5J8vS z#hMP_Y&uX8U~D@1@D!=bRh*30=dXb4d~_w zlg5sc(ow9%L$Q4q+Ik47zmswzHxp7;XaF7Mq(Ue|J<};8JEFpNX;cF6k(+SQ?8+>- zF?>;**SnJ4KWn#AD~aYms5R`Z#PCjKY?}n~|59s&B$CmvBwtJkJujH;X>EA3t%k8! zUwIs@hsSpF)X=nX#~H9AWh3=vFOXI{M>Jwt1I~rNfy(=#aNl0Dvj9C%W6g4||D4mR z8f`iu1WD#iI=Rq%;Lg%zOvYt3Ysx9Gr-onvK{;!2VlvI8NR3Y-Ne;6r{QBnlUsFI)9w1C0LCYdZz}tOV8L6SPDgR6dYKUnf88C@UdMaeNkUM>Mh8$oNK+(LW%t$4%RjYre;V)r1CI5t)Q==VBz=E-oC%H|@5Zal$C)gW_mVH;9ixe8$6SH(nY z^&7HRpY(=V<)Yi<6X2TyFCioWcz=Py&lT(%NleD`5vqbk#q;+=>b#up^p#|o_+)Lf zHYS{)d)VEKlhYSYy#un#55eNSytt$`d#+h4>GNW%vF=f6fimu-Ub8<|gNi7~uysT; zjb7s3pCuZ-HDCAQcwB>}yY);S&~>Ny?~+>RXh2q1xq3LTLMS=J0D`RWo3_-k$;WaJ=GLmf# z*C3bZdCPJ|csJT&DpPM_owa1h|ONO(t$>IG*lB8bMn1Zke=9}l6rbxA|7 zwkZ{Nt&2M@6l&ZMjGZ|m z2QpUV)xBxT@kr|SOI?i`)~ot@h@E3N&Tjx=fv{PL3T@yii0S@Qzg=qZFqFbK4KW!1 z@oZ3Som>8s*)EL8)1EP5;T#~C;2WAvsLtU+M^7x78O4eWb1*>lQk$T6kkZ8@tB)`j zUlSxc_MxZYCm?NhYDyNYYV1>ogZ((_3Ewv$qxu;;`^B8%P(o5VXKh?!ZAiU0AMMTe z)ycN2f)@54TS7BNpKRrWJfecN2-uF+=&fP*?cW9`@^}?Z+RRTx<;cLKiitCA6PBkA-|o+jMNmi%2*{5b zHUzH8K*y=~uLzTdm3EMurY&9z?iJH5=e;aAB|e}rzf5#=sD=5H&;pcIrKsiNbgfne z`aXp55pIM?_aLSwxEZi@QU@(%=LjgG*48(vA)Ke2<)+wE6>X>PKs%<&^zl+Bh-q|| z2TMF5dp>jTFh{$r+}0Jq)$OuwVpH=G71>W#HPrFphRAkr#45Z_i+IV&H0U->UeHIK zUQ50GN)pTQi1{VTjH=v^hHFL2TJp5*?#VHhO<@l+Mb9gDIy((bZea@pZUl7>^Za^< zKuFs*9*BF>j_s7fD!jr_hp}j|DmK(SRzRLzW49!UXO-B%`!>q4SLGaj7oGr-L6-pY zHWV@5+tnIlu@6?!L2V==$jBQUMSX(7zdmj?1*xJZm3f=5hiFhUbefdn`XB*y}qo2H!stWJ7eW!f>t(<;!hiz|M^3H%4E+;rT_(vo^6} zVuQ6AJNc#F--zq_8}jbqU<2*?toGl~kzM>!)%_qyRcAdpHO&EQ#w3@`9`!Q&HVZwn zMc>EufriAx+({^R>>fCEhX|%0_BtbZ!a<_X;VXjRU1fjM$c^(^PIB_O(Yh4FXe0+Q zH#l)fD=xXeFpw^Bozdyc?`mOGPG;8l)OyTUx3$0F@a90X0n1?b6KE?Nhszvv);2RG zU-bI#*M0ESW642Qa_?*^Y?u-rMjL+7anUgp*R-#FgqbuBj~~;#jD_^&YYG>e@)y%+ z*+rg>GJ!cfA&;G=LAGEZ%ISv|!h6bwB?Xj%0e>rhIYBwLzPrgR6)+k+Io?}uCCTc^ zZTx@}Cbe~^V9ySm(jZlSfE+M557cQ}#+|j~?FZoLrNQ6E?6Uxc9{!Jdf)o)f<99U> zWosENY(kJ=aea1)F$9oK5&I<{t{{y~sFalMQs>Q<*32@sLibW;2_z1P(dK03``zS} z)0WE2X|hNvGJ&S$*)9aUFw+dEq)N}a?I!nMz04P*Rs~AAiLg?JrQMr#rOH;N* zt;YIuXF8WF!v^W-gJ|1#M5lDJiBhsBf1Ko=m@ zsb0sIEY3l4lbi0XUp{Ixmd^?50??6s304kS=cTfKEgUpCR%QB@WHEAw01}g{cU5o^ zI6_40!p!m1m|Q*NZTyUU%9aWcN9~4Eq8+8uhS*~>U6}r6Z?3_kb&DqHszpV6NPlr4 zV5=3TA3V1yKd3c7mSvzm2K4>HQwM*j>19x^fXw*^(WM=nCVHN%qJc~g^iXQaIkOX} z1dG6qFrLIDW$_$-YPZ+Nk3RK7=6|q-zr=*FW*_+lyH2O->t9x zoGot;!&*-VvsGlqVCarI(L@O?=Z?y$F{o%L;kmhuHl1-f9zm>6Cf6!fk3+2G6&Rj+ zTXg`B{!bAYY4C0mzyW_a97ftM2Iofbm#qElLW&!>n3USEQT`V{>jhWaUS&oHWOuvB*#(f-%KRO3?oXM+x%f2P3wbJ#S{N*>n(z1xT2R(bG znlNKYbH<`uYfQD8_g|ZnQfX3>a>4l%=y;^YaebS=-15QUwicRy%Xc>LtN&rztzJ|7 zr(<0*SwFa8`kYR~hNoR9@nkacRv+wQvr*S<-4V<5rk+`lFE65z?3H;#s1g2cKnZ8& z&B92L93`HH?>GdO96_VMt-xtJw4OmgPzd#e9Q8nyFrwVP=FyOr(Px+)a)L8v3n?9V#ljl z-ybjQdQD-G!2!rhmv*Xsn(lD9j&V2q3{|2{v_&GL%x?)(0Am+N-5)dicit&n(AsTT zTD(xV0yN_G77+8oAcj}2KmjBjZ{Mmsu@v6c{)6tbc1c>c*;0hsG`hz=W%z<&e2TP+ zL`u;5YZ`R+EFbbIf`Qd|yM@M5s!GDr_wWvY0i4%c=<-iOHa)CA;QVUCdv-tJ7*jN# zf&R9{VCCc)vI^2P{XHhljAi_*8T>f- znK-o@7Zl~L2A>0m4?N%xmCyT`RABGk!emI<@$@8@*PpnGckE^PS9RAir7L$maD3uM z-gDo4Ud2@1zlITfF7?R;#@%HweuZ(CK)N zkIx=+R_dTdGt#DLW|3&-U+JXsuOrSu`&6Z$?$rC+*v{b@ z+P0J{>=f7c$(&-%{*Td_6u2aE?>!klR@!a6Kdr0OO8bo*DB&Mgk^iq;{0k7y*-6Pz z;e?o9*TP-6oYx`{`HJ22y9fvD=GgM^quOHzzF1ZE*%!paknP~+6(i>(A}<)Pj3pzO z6jq+_d-+!n4wMFWROJp{ul>?6w6e#E6DeYCl1m#KcUX@+vvZP-9TU)4^XJag(0qrB z_jXjcRE8mdVz~ZAz40yG@5=LWFQmL}2!|m;Dq-F+(PN$iGrlx^r<(oBgFFNBO`}t# zSvENb(LKL>R|ihlfM?a{P*UvD8*1sV_i@5AwqOWH!kos85fp9FPcvc+*Kblt1?73i zK`hQU3rjjyp^l>QebG^4K^>*S$R`E;s@r_YVe9#FM{AFWGA!(VG8;-hM@tQz$94br zbpbOmX6GFh&?Y%$D&)g@5BQ7)q?qmf2V-SmG-`tI#W0QHxo9Z@IG zO&enHN-IcsJKSQDROM+Gg%<>juqH1_I}|f^mz-LP&zNz>?l1Cac6C9s##poQmjCB1GNqhruaPc0y7s@vyVZ-R zj6mq0uB4`78DP1@Vz89{85QfM+XmX4am&ko0P)nfF+>PmOf3fC+i{#BH??iF4z`?P zL{cW_j@N&DeE^KHENIZ=4NO<`ne(w_J-(twl2;J3JkJHy(<)xC@bJ;rgxIfBc9=xg zRdN|t;JEMs*4nPfkJq?~zI7!f>>dj~Nf@boiIimv2N@k0(F+C*R%h zZL%3N=;c~9;2FK@|0g5AcQ4wL{yRCX*1jkb9J>}DvNrX$_W-xyq(UWbt!iu!vUf)` zr#zw^LkW)bGS1taw;->*YP$$SKcY5)ceCtlaU+#DjL`cox0PuCVE(baDlFNNpF?DQ zdKBES8{qm4Iu$%gr|Wcjypaw*%SEnagFi_~%TXg)C7T09xS`VG)ye3Si(i%#2J+3T zQmJ!&kOQdPP}cr;Q~*L$ih3dL1dw;Lhl)W1jQQ-~Hz#TjWX_{b5ch9gCl(gld#8X~ zz}ks*2=WNsY-T~nbVY9({P5;x(dt_SiVwd|i4H_TN}}>3HTn|J?q-T{wjz?q*Jz5f zYYud)SYA|fnA{t>wnuyvr(8RI~#7v{Euf zlm-g$9Zb2oEj>RNPz3UF#J~%~?6s zuDVGF>T4uP_vMNGgeq0azt`9hY?<<#n*3cLfn?K4Bexz{k>kkY5%EJjJ*qE-~?tQK^8$a zn~D)BZAHC}pb2dS;ye^4V~W>(yXA6L>%>%&+`~=z+<%W1By|qd7v2w1Am;Z7Z*op6 zN!LLfJo7@N0DnJ=&?i>?m}ZvRxZ((&4r|5~G);K)9QI_TY;zlFSBbjfs?Wkc1;=^p zXbysf@H83v^ZRrMh*55}U0G~KGslrCh?J8=F?X{PEx=rH0;(oyHas}w0!Nk~+XpCw z5g7}9VKmEZm|a1Mxi%*wfek%~BrBvZ0wbztc#C^!$Jr0s1Ucc{W=MyV+WiY5kyt`6 zO>gnK#?JNsIXO1@Bov{(EAq)5*}*4Y3Js_Uow*}l;1_Ok=a&?KT&Hbt!0GnCC#-MY z9&>^S4OTN5kbBa{jP8ykrv}6+8Q8<=NDa-xlE+=^zDjlJ^*|tJ#ci~ zMwMavGw<()9qkNLi7IPIm+ld8Pp*2OM!(*)6OjODZLi$*E|djB!<)T^ofG9B!{{HF zSL|x*bkoVAQI3VL!4r{+#L^F)HlaTNv~VVu$LlQRFk|&i5$@iF9bgN-a}P^PKR{9z zXD9#XIRx7r<4a56`lddFKGKAOO{iJ57@?xam>NMiI5Gw!XeZ#KSh^o5`^byf3Pcf@ zo*m#P{}tax17G83$efNc+!ylp?%6Spu~;kyJDcW%`jFIS5%I5KRP2jm-fkJXx?JCriczv&UMbZ~YpY)3od|Y$R#7?f@$+O zvX2`lAl&heE3<99Q~@gFDw>||XN_4P096INl26N3vb-pvC&x)e=Dy3>D#6 zd2McndnJ(5xiW^3#~P&h?Cx(yS^xzp@IyEea_uSHu6*a>OEfF+icB=?Ofbu=7s053 zZgk2r<)OVO`X%!|-MaR_&H^j9?inVyK^y%l>k_dGvo#3!s6k%{L5_iZ) zRRkbUI@jo;V)!!UXPC!wit@X?a&;R+9{h7xs;^VTe{ADzPDLuo3LzwEx~31R&4LWq zSrSS^iM0FuaZjPZ=SRyqX=756eK3e#a;&7Dqx<|yFC!{nQMvmipFkFizRJG2N%dq5 z2F*y*z-nO%2WSb)2Z;9{XtqJy{?YJ0f|_&{BHHtw2v}^WU+gs|144DM|Uh+lGkMShH4yFJf)GAd8 zX~8_#-d3NWtGlVkPQI-WYqT>QQ?BG1HKEr?as>~tb7sr2mU`PmAl@ytVf=KOo>*j2 zTd?4ZS8#6{nkY)6m34~GAUGo3z?6+2hk$P!3t{A}3Aw|XR11X}n-c5J^o=d=STZ-i z3t=Q-T6d?xafY3gu)bjT^^nK?Eia)5eX+ZV6;T)Qzuv`bRxGoR&?Joa8Nc(yoQ zgeTex5IVNN4V_h9^RRn9`XvyRp05khv}fB7SovvaEK5q7a`@{T8d(8L4uuMa=2N@t z<8xSKlm(qJ`g+Xf>GKmhhLyM+#-JLe-+|WQ4Y#noxLh!&jn8tx+Q(3f|BL)`QX;+L zDYaVX>N<0sVDGrIt1s$9Iu{cxk|k#Z^||3^|@tJ9$+Rx=m ztz#eL)x&S<&S|Yx#C%opPYF#|=4o6Z8zs0DnT^s2t_QUS2NrQeQ4>!QqG@O^i8 zUcIIHFR|G7BOU7}Ys$2=SHYCtn4C!e!UmXgoO z5tA~pzwTHAOkn+plJv=olZ;Lv<9-sfKFA9GA+Kk5|6xCXxjbi;zxJKth4gIq3;_4a zMGbD>C0 zMkUxQeie+AOVf(*GHyB`Yp@O<{%A^xn{H;R(#j9V>|=M9_EycRLXi+6&Gc!wRvzv7 zyfpT$H5;;>N1`G&hrG3Kajbsa#%1P?V^>jHRN+%~W~&quR7Gf!3srpsHia!s{i?UM z#9472&-Kn|YL4m4obSXeXeVrA#U(d=`BW_s+!c~GJhcT3cg5*9epi9K03^c z)2chYt)Du27rVOdG$LweIxzP)hE+STV~&OKFI3Mxv2FXJY8<+Lrv0|;hw?g}m^K5} zNkdZpDXM>6$tT@B_oe)gVn!M*hk81Mrn3-fwq?;}YTywgV~>+tgWj7k{-+T@J;^v8 zI6%GQNmG9eLb7=kZ|ynbbdewrFp%bEV%d)oHJy(*Tq7$i7P|#FXWGLY>?DODPi-Ew z#F~E6YTu4eL9wb2r|86g)ZNc*Z#Jk7ZRmV>j{Cej@rYHhms0(3SpHJ3Qwr76nUoM6 z#UMguU#S1CgC?_lJ-A)4odH3C07XE$zn~Sz-T(e(IG}Q-XfQuE^_BJ#Wsp)8wc5N2 z;Pydna&W@is4TI3trs1m`iCL!?Rn1Y8Apj&>@BO|F>@<+OV`~f#EJ(asxXo@))lNk zzn_|7hZ`306l(fe#rneNNVV&u2|ckqLdOUfcX$dZ+`qLTF|UfQsL^rkZByle43b+M zH%_9c?qU=&Q}6)OjTe^BVFnNQ0Mc&JiM*-5-fj0#VCC&C{_ju>UGmhBTSDB)x(P z)hkpPaG?wM#A2bnn+D^+iiOeH#fsKez~IyQ-3_3B?s_AI7Lz#$e`~hhXhN*umd3oi)7=;UC?)U(Fo^h{ zp`Qip$SnkvHUaJ%QD9J5wZCIroX*Y;Hj~E{6>3Yvu`e^8s0`i6TWUx4!h1@!q1G=; zfiKprhul0_{wNbJKS%faADAqK5iE^89{LHwae(C!Am9lG7Oa3Ib-($sb57($<}^Qu zG=Sr!lL_lyq-E?-{g2w}erR#KED8sqM@r$5T@o35wAH_A1!Km0vN9b+>B!CQmKdgF zm4HzvwX5aRNO~k4WcJ`2>At3_|I|A4^^)n}rNi-n`{xC(Kx-mhybQzej4WX<=c_OSPBr*s2Fq+a@bXTkzahN8ApGV;RtyQyp>ruo6oaq zB8tclbe3g~QAxvbR0Hmg;!2B6l%z);a5Dr?^OBRIZjRe=5A#KXx(4znXPl1^;$Ter z)g6|nTwVL$81N8QycF1{#=@6m33f({1(O}`2)P}vjt_bRAWIs!z1O=)wnD=R&+rGK7*0$dx7OCvjv8){_T&+ERJ^D1 z_{%!et_g|B9R;rvrwg&5_F(;b(QmkdbP{|Hv&LKBU3VYln*76Nv=I29MOHwXNB+{C zI@mP;y#PjMC>#TuRLPOq8HrLBFdQYWE5G@^BAwCj36~8KMmfV0mlT)ER)-|+^t+7B zT9_4tjzZeXql=p!yQSOebxKT9`9XV@FR*kNGUKDl|HDcc;oDm&cMjyk&NDixB}oYs zD?VwVwS-zyahMgp;@ne$;6$MT13#5@^63$U|4hob4$tz13gS{CtC!i!B=bh8YJGK* zyRk;H0wZI)vj{v{Ieb?feNMVE(>@zHh22ZOc1ED`5n2@gEmIk~zQqxwBymW`OlY4r zqwyehF81b!eZ%fKEmD~|-n&&vY@Iv~zMgM0;c%$_b%8@MNGCA9+VGk#3W)lQU_dko zzQnY+Be-*yHrBPScmx;c?M2F9xVd>H-(vSMB~3j|fy zv=^_hWJhImAFF#l(K;2Bq@`UwQ7}EbLDQG@M4Xoeoo`Agba56ZgpWcQGp_pOn9e5uVHnA#e|BYpQnmsDL0ZBEB$O2KNPecgL|qMEfCXoWw@VD6DnwC$etE3 znFhKQw2zHN5@gs3S>r0kd%DkMf`^BP_VrGypPmlUHZHMKz3Fk)PK#fxjyu``9cz@$ zGpKV=)}fxN$9<_3I=kPBcAQc35QDZ8Zx~f0QbGvy8G&maUFLZ9_H8cOR2!8L(k(+D zzB!DY#3_p0SZ_m<#ciZLl`M~hBQLfjkdv44Q5MnjXs*NGM;b<^T$Zd3*?Kd{Ol&uV z{JN<6&4u%rgR=GBJLIp*A3iQ$pL%zvaE{QJc%23f3hCn8kkDwND^ksLqW?FXI?{*spNN~=fq@X z93v6U>@y(F>+qGzAj1!vhXYzYZ6_e2eDShSZgc1X*A$!O$yCc+-2#ZHH`wb##?s7m zv@583id^hgH-iR>X6H;z&go`B%ZGTwm6bdJ@}+FuYNX`JJedp4t^LC8F&)pZI8Vm? zf73HRuw`6dq-X^ykz7JJvv5vXwjE8tyPoFz3blZPtqtEVtCY26bT;C8gG^E7PQdKi zbTbB8ET||Iv|8^Dhn(Vx^#McKHB=p@9G_b*_8S&|Cv8$M9VlL7K2yLVZHScx#)^K_ z#Ex?4U*9}{ET@PBuwZ&1RN>45w-RlHG1xj`_L`LVXJBKo93K?Gi`3j?K_Mm3P7(2V zIa-Pm?hXsPrG5*_ouVgwWUp)slVK**kQiyoKoTLcK!>u4BSjWNoChl;_nSrs5y%KLZ` z*JT{cn>n~~%-tE7^yFe)JX8ECY60y%LZL4vvM`bJT70KZPHBto^rg6c7L|glmb9&# zG`^V{X$PT%9A=B=8F80p_KO4?*L8Y}%n`^i-uLqAy^u##SU zZX82A@?3-my9n75t>%mMb_?c~p{-{#wEF57((tY_rfbHKWS>zWbz?^-aEWS*7D^&b zR)tv78Rqy(Y&3biq{9eTD ztIuZ_@oA`zAOYvOWKmT(Pg5nK^fUM8iFuM<`_Oa3V~Q)eNF66bFpIZbIHPdy=ka`o zP#b4d78`+a0C>=qX#nE5`s(C5T2N_~fiBk1-u}kpv5Gk; zqIy{#Fn@L-tA#hvN1UJG0|pUZe9^e2brP#_EBN{sy-3-B^PCwee{a?(rwp5^XXh`J zTli#+SQguSzfw|d4VNHl7d~(V)cgAeqWD~~xDWbW5J8q3(9op7FP@2Ij2{+p=5(FcQdu@*>6Xu`2Hx3=X+`N9`JW!1RSx468KO62~zJksC zNAOMJ3tQpzdDyJ_e@3K|&j;70iE7O@lei)5&a<*e=4Rb`+0L}}bcYc1u#}66U0VFm zjzH00ZJBAk3k>ZtoJLhp3_A4fduzrFf)O$L3m3;hv*{IH_iGE59I-b)no&uj=|@}h zcgfqcT{j0v149*F1kWUB`44Bs^ceqT)$FhifDXtlYPCo_I_Qf^N} zX+LI2FA}g`-b3Rh#qH!LJm(yMUBN&L<0EL-=Y zVcsOvf_aOZpc^3PTUf-Q9Yl`+AglZyxz5DOSwGmW=K>Erz!lK#yp-tnTKkD{C^rA& zsu?FU=uCtjQ2Y?Q`v}bfeM-O?^8{j!FuV2@2XnTC%YUdxB9RR`&F3YhjM>!ldy( z?U|HI3$X345tVYWLgRQ6Rgt`k5Hy8J+tM4M^y&3`LjzDtWvm>M9k5HaLyx1i51%`w zV+r9GNz-6QUNDPuSC8lt-e#@MO?h4qzV5%rGLp(B-QJBGq&Kt(Qvo?}fZO`>^^67) zWxKcekF$(S+%lyV7n|gQI5)0Q{0+-yY3Df5iQL|HT%B?sn^Ks}T_9@1izjbxM-_Wi zmknTT)T&UCWS^6?P{#5veNSaMNY(Bs@(#XDz`_g4c$UYb=A4KADx(j2{K6uY)q0gH z^fnL-2sWC$eY>6zTiR-+m^!fLpCQO#fClRcA=c197&NDq$U2<}0IXG_D~Vt+yv5>H@g9apz%eCtzFfPZEF(7_EnD ziKBVyIM6u1zq+uiqd|7JJKg$$HXIP%Y`xwnPKF!ZStGFY;a8LFH6@cFoQ3({^=>=! z3}yDK@>(@uZ=UZ=u!?K}@ z+HOi6(1kFPzj%oqSk0i>0QpP^v%(O~I+d>ov;-A1S_c9j@jhL>;2vPS`&gYn$zDCi zFEPZdq33Z*{l)>G;WRBw6AOj6IlN1mV}t-zf31e?KDc%Dhj?AzIpzw12d$o>(pG*u zKfi1Q+GLS4>(u3!>rA+Kmf2>$q+@ZZeR#i+R}=YAB0djuasg7n!f`)rO8S}DY{o3! zybbl4e>vyqwg(wJwsOn1`RsdR>-iph9iZW*sru*=lZT=T6$JxKmUe+A>ftMNs3>JG z#e!x2S2#Ud{9qonVvS1T$Tp);x?7q~L z87K>w5Kf=^p6`vVaZ?=;Ece?TcL7mr==1o3_K!UeR z0VoJ>1}>7@$=yI5twE7{Y)AXn`R%)!2TNMr^m9+wATbhr_JOzr22T^dA`?Et1uIsX zAE_dp3+(~mvGG4;JLKO1?|9lC~{g7_1b42w3e^S!{JG%Aw$2!_WisC1nz3& zx`VPdX$utrrv3vB9IS^T$A4@bUJ0pZf2O8%HD&M*C9@%rTTSbK$ZDbV`iID|&tr7i zk9PnO$gr5E9sG!3sCKcgY{N@)qkZ#i1ng+=G^rzqlioR%S_{Qy-r}7oM zj|qOD>FZ|I@2icgYyTpE(v1bQ?MD65?AoFE(LG=9{*XO>cLaPm#Ys`I)*ne=vmz!o z5G1{Xm>X%JT*KZCthzSU-nV_=H+q&5*RX|Mz3n=Oc+sC$NvT>m$=VdBV#nD>+zRlv zUnWvT+Eyav8}Rbg7EzymTh6cxndGE?X(6TlDBq4I|6(-7pM9<_k|_S5kq@~AHr|Em z=7dvmLuYPCuTL+f`xG4dtxB@y)bypabu$B!Q^ ztt!PY8EutcYY`EGB>v689cZ#rix$n75o8_ocpUD#N@U|ti85hVjNzGEe_S3z@r569 zg7Y#-@xEffuYIw0@HyhLzHDFqe1{tJ7P-n@=hqV%3MU?HZROs}Kda#;&y_YcNRVRE zd7E+{0fbZmCL8F$_<{8xLlY4^%KtH=Kzc#aZ>GPA&Me;0jCZki&u;$yESW~YIOp*& zASUZiP+}^@fsX9@=n5*CbEisr^9cw1QBOZ#AtZ0=tvSnX3?W8#SmKo+{u#&0M^nwk zQaY~hg3MWqXKv+Ip7w`RME+lZO~;uk)7?cvwUQFRjs4muQh2hRST}%Of}F+q()Aif ztr5&zyg6v?X%I;c|QztWZ|!MVz1KeYz&)Gb8wuj)8DDnT(@f zAL8{h&X1l68Q!_K9tR1^?eATM^S_ISm|vy9{mh`fXe15B{D85ofFzU{N){s&Vo_;s zvp@8ROUC9MmATI;8ES|XNyLQFnCR}92#{t#)M@@u=v1I%KD-{+v+`(k->R&EF8(`a6oA1PFn0Nn{5{?P5wU8w zY0xC~$QAR5dEvt9Q|}mmgphxa0|4Ypga|=Mdmt1O|%F!?EvE)l_l_y{TWB-sD=Dg;~?bh3r zFf9l(orFI5s{JEm7UCDx3r*3~J$Xx5QGmukqG`WzdnzeU`YE{Rj)LU_q*=b6PF~~H z{E*ble(9{qvBJis&@CkDtYeA8y4gBrDx)C-HGMALRSw+WN8B15ZYS2Ew7A~e+v!;kbgsqW3ZlCHvjd=*4PQR`=djlO>2@$icn)Y(G5 zKDaMucc+4)&W0G#py3%;4u2{I0Ue&KOgt+CRAQ_7wk^Dc^AMuoA`5#j@Pg*0ec6%a z1WxAS`?+gCKt4I}PHS?b|3v;W#BZV`OvtNL5D?L7_=w#Sp6z-F+MMtJm6`l6t2eN+ zDt0ZJM=@+^-WZlmyZiZWF4!SZvhjjWTx7)%=dm1vdLR++?&43NNO1`)YO#$~CZd2c zJjTsnvUB$8rC2epbzft>$Xn#M_AgiB-s5IM$AZ|B4P9k6*sfp_VPuAN$eTNOVfOO2 z0fs9X_`B&DxUPqL6Tm;Ma_WZ66=ly&jik&kCJaVxdWl;8;yykHk8L9tWE4f~ieI+x zE&b_~>8U8rHGFYREP|@`77D2|u)&mdkpYw<1_ zFsG(`Ew<8Vl2DoHqGA`MFq24}aPGn)ABt=*JaA>dzO4V4?r1WN0@6t|H>DK@K$Nu9 zrbh@mBXMm#p%SetrudG9|8Fbb8l6l2G%O?N1=VLR*eptzT*!4P{rp|%4GR4H&)?UWYd8UsV)LJtcGE(6ctHB3d5qbH*ul4$paVydO zb`9UyKmUYHK3YP}dWsj*KI(?{1;_O>A7dDG4apAkyeAxF=v9Z}nq6l`uy?F}AbZyL z03ld;iohjA`~lm$T-F<7h!B-KJ(g_RGX|4UZjxt!oysre(Bc@-{duVhm<0Huc8_R2 zrN(O^WvzIpnzV}`I*qvn?n+G`<_js{&wacR%O&B(Nc>?;bUo^hH@7m-W;er!dpjdx zQ30Y)1#DKodBF5k{2AN#q&xcTgH}0cb%U;z`CX6 zd{8Ht^#1y6ze;lhZ4FjDWm{#F0n3oi85kMpwpfi_j3ir|+feWzp-JUhzRAH?CtxxL zAdqVoLJQoD=^u)^egHDfceyZEs;y@6%$LS`oz7bOj5yBnWW~zCWFRJylNJ;zft;V3P(@BblQP7z!0G@QU!MBLpR6k`eVoxntXc1 zSU|rO47lF#tnmc^Je7KFm$Xxq524(C;Fz5=T{`6ZP^SfI>J%!{ie32EQ{ z7a|`rx!r0hbzk#FztTY17Rs4u+(xm+WF`>&xlGKlI4;|^zULiTt>d{x`t6rP^DTLw zSerGb^yO@yBRA|_>CCa4fZ_njimpT`Nw~^=NMw?7THGegX+VA=5*O{L@SNMmH_geB z0z3&APEhxZDMZbNDFOUdF~Y1k^MvnnO|OL=a~CgZ^9kFTFED6YurKd&SDmChL+AJ^e&U;GIPOEIa>poDy}ByLUB&SF0?N?$-NqX#RP-qN_dr0+lmF zt-H9d38Tua>#(sHv8RWz8MMd|SF)=4*+e9gn*#u8D*Y(9C49TTQSAmdp?V!#$^Ny2ZbrTM@1A&)Hk4cRn z5wOe7pI6H~?01|MG^&O&>7w&3sTnr=SvgS|)80i0@GLUq>d+%g!-fJrfMI(3H{lZr zMQ40>J72?%{@h&0ID`X2vrG)?HLy;N5I@=kd> zL+rZtU=xN^kjMcZ9RZI6D`s`R*@V8#{rBJHp_->-a{ojBP9~%eY3AYxR~cfN}6F800LD`=;6{WsFcM|Qvr{CT*Z#_ufZ9dQP1YZreU-)?BJhK6#f$D(Hq zL{$wBBqaLC;;M4E=0|vQ1V!U72*k#?%ZdtE$fGI%A6=M?t=p7~)*Boh+ zBcx@l!3YoAouGm0XHiT#xrYZ0?dUuvWC1Rg85UnALOMqzbf*X#MTo_I?IQuR(OLje z)kWMfvK5UFA?jx?F1yP=N2msM#;Dca)JZl2LwA}$F06(Un3g(WzZ~QGUivo*;?+Fp z*A%Cw-JtZ)F7_p!WHEB8K>+D10wJg*m%0{zn7@>z2kW>e%y@=;PW@IPc*>n8sqsIi z17?H=xlf4$6TqnDZFAix@sRY(7UJunbX`v{J5AEBCe%8WxBU@KrH~nboe3Gx6(B+7 z#LIZXp=XZl04l~6(=OcaTLY(&YXCB1xcf=ib09t>Vk?CKqn-c;(t4WZBT|X;SAzo3 zjWa8zmCRxza9&_7A9c;(1Y(Q%R0UCWipLdJ=;&>kH|&)evXtJbrkY=1l$pRfe?$uU zIkw5ZhEo_REZJYYPPv9{I!?%RP*a5Q7*2Cl`N-$#1t{c8dsTvfkN|xayF) z*S#9!%ta8Vx5pewM8dP7Ib*UfTZ`_8Akjp3EqYYix>Hk&ee4)PVa8Dudr<3ze{UMy z^DlRs+b}6UaIds$#_%^0*>2?agZu;sm>N=o`gXH_B``1G2;y@Bog&%h>n>-sEPa)T zFP-2-=W1qe2(a}i#q4G~>{Pt3K#9<`BDvKGICbV|o_-$cUU_#_kgi z!c%G274Fmw`j+q#L@yyi{Z^JWjT)wI1GoM2f#Hj8iBm%JFguJfl?NYoX1fq_O#Q|i z{$S!t;xqKOj*Yr`}a;o*k1%p8dwU()2b7_v7 zs%>11;DpwD?`@?v^ks1F7r43LY`XV^jn#FCdDzEFyVwr$OQ*=dbRidoS-Mj=o^HFL zR&7JTe8P*o$NOD4uXNyASNGxRs)|^#VcjjT-Ayj-##x9uNf!Yu5ULB)Ohqo z6J~b)6G~!A&KeP>`+V|^7nnwbJA18LSn0BRN{ zNyorm2CzgM0yliNt3fq>C?q^9&{;hZ0M)4d7M}>fHY`P%&&{5VGF4Y6bHmYs?-% z_(NTK8Iuhksu)8~qdMvnIXu(z-r4aMMuN{{G|bS2`7>O?c?CCFA|apGSZnSo+JRPS z3c(JqEkHXI2vBsW8QKMR{3e&r1$dzkoX~o)T;pM2*ONaouqu@mUF-Mw)owmmg4LKL zqt}8>v1BME>Kvj;EF7oX$P-%I)$3CdLb*Ww;HriVLfQ^98gu83UKLQtL^9%kbNQC(CP};;Nvf7E;6~LxYfRmT#$tHN*>| za#U#%xS4iixwq<;NQqk&B~g2{Qb5~xZ?P7IYuvhCnt`>!9Pe5A(5x~3lcgvlc$sEL=fem zh^>Y5Zx8Ls`!))pj?w|$id^iDN3h$*{{uX~>2DK3Nd%_Xmwyr>!d|Jv zq~)q04#}~XYhtMI78>|#f=yj+LOWMTAX=0Ok8iUN)8G$@t|gAR=Gdv2J!q=Ecf2B2 zV(Q!HA01+Ie6C%Bzd029j6Y*b%NACG5)dX2JAFng;U>|D6yO=g*N#>&{998qv5Q7G zH=|)2ywJ~Dw0K;y!+_FBPw)Q=uDP$5=q##?v|((mTBvB+*|9~Sjj@Zqa#fI-t|#s% zp{I^Y^?fYAF(zq3wB`I?~F z*1+bLq(%}9^F>Y_TGzG_1nqeXOjy)MwUWjY2e*tBkOGduekEr)lp5~VM$H6zjblo% z2eCR7lkWLGRP_${vTr*^TxVBwJJ#_wpM_dBJua)fb?H&jiZ10F)U!~N@u?opY#ylP zsoF$n#0zA2y^ND)s=dTEK|Q4i62wY|UBLYKD<_nc7(_F#Uvq~j0{Hl59CoLt`2?DU@ zZgr2Dn5V67UcHn_aub!!)e@luOAn);>OQ<+s+2Ct=gR=q9IxLQu?0!%l7k2#YbR^+ zaHL0aTCO$qHJNof6~wetaGi*X<}Q`RUS4bHFoHA7RZ=oQ3388;D%-BxqaXrjV?_At zwFYqDWL#qV1_amxwN|Epc`TO>iJ6_P!-6^QY`T>`58_pr-}%X8#(QHFrOaUpx<5DE z#PPL(c))n-s2PuLnmyEa`2*}9*CwMcy6VpN3FMF4%6nH{^>HP7plNOU&hf&4svWPd zRz2M;ZAl880_tES@v@0?T&QkD2&zsGaGdb`Y-ld z<3p5y$>w6a)>-#HIXoNa{qC(?&!up;Ed@L6Hn!_*@YJvy+XN3N9Vx^?9d`*p8q|d=lG(8O3jK_ zh|MagME5XH-^)8@Ma_dEECmju&ngAcme^)=vhAn?lzh*`Hd>(DNJfApUey|2iW^S8 z6)c$JN-yat5|Wtw@#$|(<2@-={juTf{i)wh7$NbG+^!`Sb%puqg!>fMk>aIm3*|E1 zG-z{{i5_q}CM|yr=+d31n_C>4RKa5_xOpF*w>Lb9H9zw@S_sp1Y{~p$K=>3g6aN@8 z^#w*h<)oQ84S=p{Y(5JWxk<|-X7%d!2T;R+0Bb$E|9=s*-3@o-ufu~&x~u1R6>+;$ zjXsvi9PR)uc?sWUo1Sq7-b@%wV5mqR+fh2u5S0Bhf`GC?5l1Vq6&zN$MH*MTcGo_A z0g7fZp0LhT2)1I9PYRVgSiX{W3W%4Qxtj;mdOaw3hrA%TxRXuxiiKuJ4V zOPB3vUXx}h?~%nXTY@<*#SYyA>LZp+8j%FO;HkB+^{j2<$EvUau2}DNRpAZ&$r&wa zyY5AQTs1@A6vS(7I2w1Vl|DA!zPl)2%ZLSTf7GJYZvhN#sIw9k_ONdkk;f8x;-iUF z>%2lVPX1c6rKxCj`BjU84*r&PxGe2Vu@93?!7RnCnPiHrc1;?J!+}h;xYM zQsk`Lf%Hha`!$J@;l zd><>^4v~8!8lfgT-8O0bAMKB9Zml=lEvcv>c_y|U$fa6U+hN$pM(TY}xq#nfrtUyd zogZ31(6@kQWM)30v3AA#i8I+x!pvp)MM-qQe=MoGIoq*rF27O*^DZvBy!l;+~ zukZ|;aQK$3R$yjVMYE_ z1$YiD=I(3ldmKP&l{BVHe^;F!s4M(r!w~Em^vlw&C(sM&d@v(9`M1J-QOg_=(yxI% z1L(}XVs%|0h{R@LveE~T6FsN% z%?Dk9jkfZJo6B8)JAG3ZanQ>mqj%i20(4@qt*-8H!1!E@H4_ypoVOc0@g5_?Y>#m8 zVyE#SE?nB2%8T#%B1S5@-{ZNzMAcR4R2|{n*+NRgLhlJsEJTx%u77urT_|`ZN3mfg zxcu6HCfy=+%woju?1e9I1?U_1wr2DM$fMQmE%nkoO{bWZ4e)e_xL}wM4xN$mhN~Sp z6OP0VN$K_UOagAkzQGyr;)IdNv+Dg-*RywdqOgxNIBt4lywe|HyX!Uvn6E-tD@d`R zY7#M-(o(#HPbqYJkEQ`tLtG|CQ2GmSY5Af6N|f7KcnQMF=4h&z8s2Zrl}B8Hr^q$2 zOv(h`Af|;f6?CO%q)6w(&?Ief{^UUD`KpDWpy}aCM(8sHCBBjJ&X5gMhtJKbZj!z@ zF0Hc4F>3NYCy$mku^g_nPUW6N&0U_ppVl}_ShwJDX((ha6z48)b3#O5R-tiKr<^^9 zxZJ8!6j&qIY|p7nw_q>?LINNl77jd7n}5CwgHAnonb3E(mV7on-6Ey#i7@sGZLGr0}|r z&Yw*RPkKVDSj$j8CO##12eg&@ zSR2WD0&!=jF!dZ9cf*6vqPHi`H~mpnq6k|cM9}*Yo?8EVL5Xo70Jf*vEAVxw93}x6 zuZIgmEq%|D4+b%&7S{!s7>Tjh!5@6GzEbsS%l9IZcER9x^)vt<4-z8l%E8z;plWBJ zo8V+TtL+=0wk=9_5Zsw` z9%fS^V0A4HKu}7e#HJxnY!_+i3Bhxa1}h=3-E;%9L~}SWpbM}BBvjeUeSQp)^dkca z1heyUKMDIoAvXVE;?W)c@JO^CRdnChvL1D(dASQ2Dlv=~zjGeM`K6`Yz-yVRox80?QZoJ0=id10ZC4cCH%wi^3^w8viC z!e1>B;Sd+=P-%0bv_ntI>U>f73_xY&O?g{f zD9@s|+|hGx7Kuh9QbMo?vhIiQwPPF;dA;OzEd|N=gB?|}y$l6e(7NlNzGR$&2-Dzu zq`2J>F)#O4xv$FuioqmLCR=i4@I~DDLicMt&YS^HOKmZgU7^ZiHkS?pyc~KU<^-+7 zyhh%s7sR6}aAOc+%+xyn;m9$=L(Lo0&s`z;UYa=Vl8!{3^{>jwO?wf!{1LWGH0Xin zLA>A7-)}=A0O}!3y=CBylqW7UL*qYAZG>YpludaATtq`54+49(z(byJDzO4|5&r1r z&nN)l`$ou2T?Xr#)nlRV56czpx@xA}AiuD*EQ2l}qV^@=NYS!G94Zp~@WB%P*HNbnx_IlhU>W!`Sh-1zxIi z^y=)r0Xa@$=L3M-_7hP3(s3;|&k0tr>@Ya>eSI^?q7=8v3Meu?B~64>XLxR@FQm8- z&`t-6!lrat#B!>mRds*FlB13P9!8*{Ah zpdW5L)>)U=KgtMd;cGZwy#q97!n!l`z4^XbyIRjg|6z;hJ&Nd{HXhIILef-1Yu`v| zs#_A{)YfTe;=%9d@|FIU8MPTlh~i5&&Q(yBM9)4aUx}Ug0nhIH(y&LK*GrQ-zW8q? zHti_1Aqj^TUW4_hr%jNLjmeHJvy)ZummO{KdF6Z*stuLh{lX+6Q8%9Y)ljUWFJnRT zD*Y*z?S4RvI_j#Q)>U|}TTC1E-S5~Y;u2g8?$DrV9-}CSZO6`zs9D&TLzw(ahc>a0&pBgEK_sn>}xrNP$?> zhVpieD#ew9VqtfgvfR$cH^_EPZIXkIx^td|y5IT!VU=z5YW0vOjHq0|1>NCxef(mL zlVcOpb0}cWMbN`|bgt@1Vck5iqmaGDNkjAlq%R~TP0GwC03L`nv@`Y5){QlEm*|3` z7!y6#7O16JN_(oyHtlQXAR;Qu)6|~dqSl0ta*y$m2@lb*E>Lg+12H3HpHKg2uLfxQ zauunp3SFNc5H{_EO4E9`Jg5EB)BieB=z}eVWCXclUfbkf_={QY90FR8Zu`A`B{GjKD>++u z;vU9JNo1C60z%RD_`S?O(fdGify+9ao2SphbZ)B1;5Mw$-*A>@T_ zA;fksX%8BcJMRUfO<(xe17S!)IH5|if1nDqi+q;*-Y6m|o`czO{47mkZeG^u$Q2|ScsM0zTG&A(> z8)!GH`7yqF`=pTb-w6SoowXim3;S-*r#SL`g|lA=1LwmtZ{^6Cr9=u zuQ$^fh6E$*rW6tqwS!nCxy@vaU?)xP|CAe!V1_Dqx4Khlp_R0MYo#9*B=ud$*s8~l zA$3Oc0*C1==NYamWYqgv!p2J%-p2`S7suXpw6T~aeBLHmGi0yOd~TCIKoU^#4cj7q z)aaayNxLAZO9KdLT!xd@l4Q{S(BR~j!v%n`(%Ta>D{jKAuK*4M1FTuH6DBm_-z=U< z#;?YyPW=S}P|J209?`V#MHc?KVUiCHAWRo~h<+w86yFx@Ko&;0DAMoNL`MDF^AAy7 zJj*|BnGCSmh|9WE?7$H%XI}wu9y=iFq4#ajU^>Mk9n6NY6xZKM>=7)Vo>TrY`n&%u zJ-h?AMWy;s!8$rKp;O)cr?QN{GQQCyCf3Z(h`pPqdkryuiMAFfxRbz9KBJ}kPOvB! z$9A#6BiN8!++yoGqJH@irHw+7W_<-4Jnbwdz`YvTLi(?_`4SyNJLM{u|M~NBh1%PRpe~F%eR2<@%8*h0$k<18(7^6S0xC^&GZQ^MH5qYt zodC|}8)y;Kkg%W2ylBYS-68q?D$A|)4%<$2y_o7kxao1-k1ZLMfy2o3Oa_?@V!>WI zW{L~g6qWft0EeUhP_E3a5Rdc&uF=kYY?sqsQhMC1Gy%ly7pC(_y9R`7c;zAxROK-f zKlU%Y)es8iYX$!ayU)*)@YUTPPWX%S|9h>^xq*LRUVE(nu;>n{zQ6OGMMc?P7NI0D zI9yc#BBuPp_~n}^`??zV_@p^-6A1+!)BiZO*^is@j=Ze}_PHndgegvxV#D4`)$p)W zT3WLrFpP)VxX;1N<4lpm3`nzj%ObkPrTRy-aR)%{JvJnlb%8}yjZxg51|MiZA#J4_oaMoxD?EuY^D-A2+^o6w=vH)S?BW7Rrrw>XO=Z< zP*i#x`U^4{9Et`0@Yr+znK)B74tgZ+ETztxgp6UUhJ&X_Z@3GKm6Kn*(~s5Y8~087+dUSJqPj?*`qQCH6+iCy08ZBS<#xSmpGf&0cE@QN61*4Coh-J3_I7?N>oeO6U!eEm{-#Ok0VETc!c$fe*N}+8r|p>urw#Qi z%+_1BVg(-c>1FQhg_KqqK5K(*yH~7o4b1OiMFfp~{yv>`GPd1sW#xg@JH{$*x9Q{( z@Ip!!Hf~+EhiV+k8vpd{aP0jLK@X5*)(-|+OyfLG>ble6x(6BDx3#ONZJVDA;k1!KoR5yK*)~>K)=x_JchSt zX=j7Kyw?cn^t~knn8pUSPC}CZ53Xl};aUWSND(-wGYKF~3{-tVxCug5m(KKWfI86p zbcGWnRH{de2Md(-X@QBqoj;HiE;tKdPVBmPRD~9(J&clSV;?}7UEJvov6Z|Dk%4lm zn^a=H_Xa7Lu7{D9lqYtx)kQzR7>xV&Vf6nOEI0rjl3^w(r7kt!2+b20A!}=^{L-5b zOQPwo!=cBD3CV=1_lP_7eR$C~n5uA)nvA@$4~&I~8l)K>*Orzbm?^Hn19kMBe-|Xx zMef_7KE^FKJ#jvS5`xKC2XB~7+hD=l_5$h92|0Yt2Yq$adyYL)cB3H0J3s(OK)An! zh*UGInfwcZcKtvkVmcSEc_&HWx={DM)me&4g|~aEvM4SNc>GGZh~#kGV>{SZvR&o$ z#Zra9`}ullRhVRPbc4HOv?VQ;()DM)s)Av)RS_>cW^@-uA>UuHZ8G#97ZjUYYd(1e zEDPjfR;wl0-&P3?fXGL!JpyTDRH_ISJhNPZW9n1lvtmGqNl@SRwm^B@bynkmh6!7i zoc({`0o1~-TbR^oknm0n+yv`73?Gfn;gnV7m{N=Xw2W9%eM>1H)}WG@y%(Puda2e{ zpfg9+zgl7!Zstpvx0Ciat;Lf}qmaL>T*>)I6`k-83pMKwdU-Uc!ktWq<~N#Lg%TFy zS>#9`dlHwWWo%M3sUs(5T{m-S$e~nGRx!t*v})N?kg;-I-lmqZnp-cc2*o^EwlM&) zmTW$lM@U%`&LdBUIT;_6*-mSmPqCvV%Aw2wP_UEK_2p@>I@!B|xg&X_H%{Jw8%<%)}gjH^|N7U?Hz>Ibnmhl+J?o)2xwZ@~V1f(vc3MkQMK!9d zP6hS!HL>Eoj9OpoHa!@0PSo-AA&yy1L7Mwq&1@2h81guS3@6ApxFFfB-`A9gRL1Tgu2duDzh-|*@5c$!<0<9h0@*(dNZWi=w zuxc|gjiMak&teS@)(KY@ISjb5YbY%D6VDP$p_5DwSY4JyXuBkshwgtea*bsNl%hdp5av` z>FhtEBa)ql>d6!%L>?)JV)D6M4CLkG%Gcv0t-#|$MHLYb1tjzP*-8-*?W@u!7a$ze z&lfEY>C>Q)1{eS*u$=V=^_V~2LPqcJGrkeVRxj6;xa^+(rs~EmHf83pC4xk)CR=xK z_$Jv7x}3QFFiCJvzSoZdh&_{pKv+7}Nc$ws7GP|v52rSb(kb1s(|M*sgONe3kx>Ce z;m2v8p<=^4zP8c4B1}+;14OMD?ABhdtOj%KYD;7oga)Jeo zuTVrt8l3P${j4|yVz zzn~l82joV1&eOsF7ht2(hVh8zI-O^?>z6x2(qA%sz*_>*trhoGI3o2q^UP&-&C@DT<9AF{M{J zu_g{m&eGyb$uy2u#~Q4(Ig08Gq^p0}|VfIF&Sazudz31cw3qJpmW_W~u}mt{z+rWlTO2D z^T>#&zp{;MDk1x-bhzWE+@FrR5;E%~NGK(F0tU7s5XGvkVg!!qD}dMnKX^NR=#pw| zZmi-K>X5FSmw51gM}^@#pr;>NpnUf9n;#(RV+W|>xIaU%O|glgRgb1l7Wd9(hlU5i zH7V6PGFn$lRFRxEzv1Y8z5T78I{5lNq#4B)4*RDrIRaG>9u%aS=Z3yrhVzU$_+<&7 zj-U&L8XCt-xt4Irdj2IEj(dFaeK^y&j_0#(T}1%gN)L&PyR7|I#3s!c(}J~wY7Yu> z*{=*JQJKNaW;fNbgdY*E!tmmE^$DIU1w=Q6F}pmvG8kgx70@RI(l8;H$9^;&qbmjH z2DVXr@6%VjN%_6@*%JI^e!6&>sg-OMO`HUm_9E5?kOK|SzFZ)S^D+wcJRu1yt!(b+ z&`#`@IV?B7$1sU~-_?V)at^RHxo$Sq?B`b-8`DOaSYXj{@mJggs`mpx1QTH1q8!mk zg(#HeZS;0D=Agzw6gC2%x;fgWFLvdS3zROBE3S3Bz~Ew&)P$pSSHhs>O61!!?i|~^gr0HWj5L?2jN;LCX4yd6Z9fV0}aHRLt55?D->lK+cP={K4*5> zUX9xN~4e+nIegc0nrkSyH;Z){Zt5$y_|P=D29K;ponuXnJBp9B5~Tt@b}|R&Vi3t0@juQUXRAq@lY7r8nx?jIlQ!p*&phC_!5 zZ0W9*np6h~nu?c}@OSA|Y0A}$c9Pjb99<{n2 z(97M1d$ljVRc~Hod!a6y!?{T=F5vx+ree`2?)vYLeb@Dtd|q7MI1BtrC;q`*Vh;Zz zdL5+re>QdG~u?7@p8d5cjGMJWp~s}7Yg16N*oN#5@G5VhjSHG93yHl-V1YF z3aK*B_thw8Tp32xOpUtSR@yZ4rO&Ee>}`F}L9=4*zxvJ){!<^F=LnB;lgVY_wRBPz z3r1q4v$^+WoIo{rhl;kZ0NxBDr^3o;G5iKd`G1MtO({*R-_k3=twDzy(cT*_qzwxP+jTO}i*e5LND5NL;Mpg~hU-h% z=;ew>mRXPSYeTs1&VjlHOn_*R73y_W)LSPNnzJ@s5ig>Gf+)_nmdL-&9W*97qLTL@ zA)wPFXWB7RF;Dbr6iunqCL4j7BDoSq!P+?UyZ(q9WvcVn>i5p zqW0o;uu`)0yVR6UYTHIBz`f?9zSD?MxLts;5u zaK!NzUw4G>NVRNuS)rnn+-&H*du(<7nSiMFW2hYCs-Ch-EB@4gR9v5~;1{Xcw#V{X zo(MP|$+bz=MdsVpy}A6!x&|#W2TY}20~HIU*AYv?fmNR!WbrNC0djn`8j0e1*1Tp=}bV3GGDm|bh7=(E*0 z;z17C6e~! zc$ze5?$QW*&rNDxeuWP?&QD)L{4N7j4FJ$!7+{~@mEd_0t00SG3Uc`+KsJjCA0MUR zwljE=Sj+ZrnM@Pc^ilrwHS@BNUd^L=--ChYhX(gC(4NvGT>3!Se!K4toJqNt$=+i(1l?$J9XX!nY6W>#aaxdX`{^tpU5R>aLX-}_k z0h;2RCEYo~76}j-oE1Nq?)%!e)ZL$Ra#7CzO{qHp44I8YvM%UAe?l{qoN+@&h&6Of zBLBjB#=g?mO(2SWT~PE;O2$vp5g|$&iL9A04nqKolSNCf5ZZYhAwo4#f1Arzlv=A) zMV&F#zg%Dd5k^}A7I`TUCWP{q?uJZwrd;adO6G>^^t4=#vAZ`Bh$oYyMI3_%DPJyX zE!3(O7KR1dBZrz0gSw-y4xn^ZEH%>9;`Wa{RPD%_(g%g|tuWGG(iTB+=QLEi2F9=$ zS{ws(od6j01&EQkiOqzXlG*UEgCh53oc^zd+DQdPw=$3<%?{$FVUZCH} z>?)36M(KJ}QHX-F-9??OiYvT{bIMg*!C<)s$6`UW&1?5QADOpk_<1xHz(a9t>EL9n z(gk=~Dq_SXIvKr3-Z7PKDPpfyLm1mz1x93wr*0E@to7`rM_U-Xp`S@$4&>N$jmj@r zf*uJNYME1<(gS!$&nXLB?^l!(63a=gSb-owWVFC-@fKhZds4gr-C=wSb9IcPy)I@o znm4f0dOL2h8kR4>9qafl!WA*Kqy~z7UJzU#mjnDDKQPs|5Q~1$AceALf0G}2D|mS4 zA=yvuB7jA3BCy&)8tqsmxBYix8;vdQb+soCyEU77DAL!Bw8H}PaGR(W*~8!WspKe5 zAVM}9pgH28Zq3JK|2MIDeLYxl|C=?)&m^hlR%PkCe%3-@g)v&l@_iFNkYeVu)@Ej3 zj`^mVPa?&3a1H)}w<0Px#<0RzN7NO=(GrRD`~nt^Y1x9B7?dHY`;UmVev2)f;OoFi zep33FLuFDXLB0l$mLI9UTP6Ek*b6R zTB4yst_}Oyy$i90y}^so3BCEcCHW-3M~p4V>aInPdO*HJiYhlP4+FyQb&x#E_aoIN z08g9Q6bWcK_{s!ozh5~YK2k_Ho`99v9c#N5H|;&~p7i`MGuJu0V2H%zUBD!ZFQR_s z!?Pk^eM_Bgj~915wu|8zcf%HeVz@*pGZ@`!pQj}xX*uvNX?GtnU`A;io_IOp>h~RtcVA_(KcVE2PDIu1R8JrJ>egHHdAy1{ za=1l%wU@afZ<{U30&w7@EejwAy!v&(fLjn`iED7}_QyXOm~99&|M@zKtoz6FRAbh= z@e=lMzeBCtiJklcIxPd0JX&`D*?Y0!zhV86;FPDsnxFs41f6x0yaJk(1JjC8u>c?! zEZio(77j7^meh64pEWrCy{re<0E+a~!aznXc^xqQqL#-SMX#(ptuonKx98{aYDhCb z-8m)fi+Y0dHyV?WfrunN^QA{w<^hXP&a%jJANyh67th{kZ8NElDuB4ThM$~QsBi@h zyCg{vSZP6CyvyBcQ+4NleZX*1R>6`scxS91PQ|9e?T2j%C=l+i>eb(0zeG40=!*3j z7RSng-jaQITyJiJZd{hY3yvlek*hXND6yk>%S0L})PLXcFAzEn*|R@8H(oEZ+lB#C z3n*T=Xq7+Yg~t(UkyjxzIe~zVTcY6o-$Hh&(rUTiMN?UFg{>);-TGTu`u_E@)9yAh zBM`)mW>-}-2joEc!$*WbVtaVyW>OATKBtkazksVNAAY6$%R<{m9*i{RHrFMITTbQ>(KPP~jPQW2{9t!DuIF+H_g2{RxTx@IuiMIV} z2~zQzUSx|x^qm{HQYLV^&mOa}X7aVlR&rYC<)K%i&zA{7pnlX?vpO-k94;9?jZ#ba z-O}M#qoGtv#KBa)>Rl72FS@*2pS{wFDy^ovMNavGK#){%AivSsA2--(TR4{8dKX6= zpuxW_fBt9!z9v$1Y9+FyjK;deE%c_$!PW-7F8viFtEXA4bx9FgxDMO-bF}tUbAV@t z^ahjD3JT*~20m=<qMA_Zk;!UDGR-F!8W3cBEEE)Fm7?skN;0)?osz8+%$!<9JG7 zt)ZrrpHBW>&uLL&Z@gAdg%;~6AVZ5j_J(PiW2HeL>d9lkVTUyrX=553G& z;dibFFi6Yb5A8>4In;;s8w!>YD$OhuDS6{6gMGWX#r1Fa5CYdhNFha&eP+7ovV&gdD3gMlK!X*-n@34Xp--^gq&8H}4wB5-#$4 zTat$|Z*(!{dy5^)LdY{UeKhEm2rn(4nbTbWv+?YEz%TU<1No7%bq4<5c3AezBjh< zA;eM^8v+0kRg-lY1wK+DK&Bf5Gm+fY6hmT)M`=7tx?;NMtf?P2;v*rNV^}LH&$ivD zMevEkJ{GOSiO)uOr3I^cwa&1!_x_)?VBR6LJm*A^PFIPr+mw}?9v=)qV5rj(6n#RE zmdcm-s^`4i@nR`UR?_&}~rhmPwPn(_5HVeXGBQLcv83fJU?w9(HsxO#dE%;bb96--mS zBtj4@yzk^u4gcr7 zEmqH=$SczG(Gm2CSlgl2Cqhb*G1xm@SW)b>KGW$5r5A(C>Z*pA8^TzFG@9S|H^k$# z=K!tZNkNeP6l_{p*R&VJJ@Z(&si!-F6Vf*$*T~x@WR7k4DU1$RmLjx zDpcj-1rZV8@`y>oW;mHd_2qHOz>6vF!Tg&b$PLS?T%A=+^F`Rrfb7Gfvb-56XrV0M7``Q9N#e*>SXvpH)hB9f1`6^3d4pS49{hh|pnfp`rsmV@0-5Y&XMGyY0k_T%Kb5@RGgw`R~pJn{Es zpm5X!{Lqm3dAVsxAuPrrw+F@hL`akftm2>32EgHyPvC~J6LZGdwZ*=m#K~uO=Bg*x zNl$+0JA_s6e^>pL>*7oF(rPkpx6i(JM=yg+P-CVq?{hL53L4~r1>KpGi|p9o=E*#6 z<{)coEcKR7jt<+Sh+ncsl}OdA-xAg=n%O;*G?{)s2QZc-^}boTA{C>hM0TMtE@*5Y zelsPbX#XHi!={jdlU^77LE<1F=5N5Lv#$aYY*8)DWj+*72O|a zP+pMFnr@jvz{i8PdUSeG3sZ@tPZM^7(*T<9O8Q`jm;q02u!#w*Hc{qS>FD|njc{)l zZBqV5V?d!8UsBaNup!g4fS7tPnU45dUezzj7~FrD%J`xeOwTXZ#^gZA$t!+NGPr^+ z8E<*qnGvm-OH1K2g!wUMP4Z*W@ZQgIc-%!eOhoscNWi<@Lb-e!ZfcwPM8t%Deb(WX zb-B+oc=;qy3&);nlvxJX9FXB`PI5RW9HsE2EFXI8zI)H8PD3s}OQ3(sIWNp0=j7C# zZGkx$jTK=7tQDR+XPcy2dl}x&^h+~1u-O@OMyMBx)PJ!NN*gGQa0Y?D3kKWY-zW=aLEfQD9l<17 zqv#FBxfww35ZM;|0i zkVr#=F?<@bN;s!rJvK`>n5M#i@So>=S#SDc-!V(z%IB1bs89qNcRK8OWmEMP)HIaFH0b z-d*&rEDiNCb!=6$B&|c}e8y^Q2|K?QA#yb;%Xu&o`2VO8nWEpxvNY+jiJBw7sQZ{K zj1se}^AjoGo)E?uF;O631!Um}Z9#%#&-17rZ({bNX!pb4nFOcBMPnoIik;Mi+z(da zqho9w7d9_U^$VdrlYzx}32D&Nrz{C55D=GEF7`-hIED8 zC1H{PK*ltHIkSz&`vpIqEMFpU6YmpKqk~r5k87iXIu_1pSA9seF5nHOm7+ zppN@#XVDsH59rj_eJQF35wQwp0-@nF+Z%mjHxtr@ZtjJMSPQXaMJP#KnVK5bD$*N{#IyYaTUlaR@Gn{ofaCScnmMD5igis( zzI@LxS9($m!oTGwKEVO_e96%3wqpMA{M13|iNZr@f2sF9Y_(^`FI#xkomTsdiSWI# zO99*z36q>LKPNZaX)q6hdtqnyAlk+I+|Jm(!2o@CiW9Ff{d_mV`}R9cZC}f9^-u=X zteH33oCHh>cyqpwZ?biRh7=3^s}Ha3_SmG;v5UuvIW9|5IRZnmMSl3q_a;f~To|M_ zq(8I?5Mpo|K}|$TP&(W50h?sPH|G|cm#78xof ztF~L7sz|>x`Xw*82Yf-^L#+~Ns{jAYckCC>EanZ~OqFBue8 z4W7Bd(TR+|B|cc4hM2RpG!_${zCJ44(GwIl)Y@imoEK##_N)J3Ap>l@6JppexsZNUdRycR>XFOEp-X0nmzvx^Cwpcf_Lx(6X#jIKg*Yj`H> zN9T95_JijiIGh~{F+OE+>eyFp6oBzq9Z70jBpc|hpYG*$c0n?g&Z+Y`7glAd0mI9Fs0I%wW-A|wfBhK zizO;-*FEgAB58h`=ZE3~bsdl3Ik+eut<<3NEXtMkcY2_EBWwL^q3yL_W_rd>s(TQ4 zimy2@fWFGXKIrrOzoks-QCwN&qUbw$^0RpsuiVc~svOC*rlvDyha-6$MiSf)Y>_={ zQW~BuB8tQLVK0!JBY^WMRvi8~{G#hJ&?YgR$XT;7;iNwmq(k&njxC4yrj`Ds@pd59 z#$|G&+n!u_>$PxMDK8hYpZHKVm1@#Fdwlz=JjRd{s{uq)fFx_k_)Qi3xWL;7+%dQI%61-D zn67W;+la9<5v1vDsXd2_)7V6Tzz=%ef)PN~`bs3yq3f=ml1+73Y%6^rM(B^>aC#*k zOiR}QNI=j3dYHv`c(XEp9qw#iKFnwI#7kdpvg4 zzMdqfLn$^tbL3X@A7J}4Z`p!nH~KgFu}2;ZVHR)4jZk>;>FWfElTDo}N~SLbrzNi3 zd4!4baj@{>X&A8o;fD|p=o?*3ii}!?Ix`y5hFQov9&uRSg2Gq{!m<|dHr8F;zhYN_ z4pGgvjVhlCyzpkXDlDm?s4X8nddw!B;M-s1rSER3(am%YFw&?FsqPg!a?_w?R6-D$ zg0IAcTP+*Ha zB#V*bVF$KEDXxSYOM%cA81>0M2us48eQ2jL`O{7hdgcVDjDuXO(IR2!{dUyH6exdgu9NxWj{me4D%e#2oU3+{?G?fgh;ZbfFj6 zm{0`_cm6_ywe;Smi~HDaRBtzWah}Ei2+_zqFHLKvqAC`x@hTp(eRIRd_3p0oS^Gi{ zS%xbYKW&p^qd4Mq(Bb>V;O0=5m7TjEjGi!qd%7QYSG~6V1zs-SLD#9G3^1ho%MN{+ znvEO|Y`^UcDH_|lPK*X#FsLf?d$uGGpc=qNJMTwgAf(tw)hV0${trEYO zlFxn;fyW3vLW`rrvUNU5UdUGRtc9M}fzCIr=$tBKeF<+NTS^PR5 zx9(w;^-}FF)zX*^1&@t54Vta2+J#f+r2$EfU#KpKb{Y|?^V5OX zR~z^YHGwj@xLZy!!q3E9MH7))9VX^)#}#OwS8Xz+caY$rw5F1I%z=hCVDw`8f;|9+ zJImD)!X^75r=EQz%s@n2EX!Nzp5?>k+!wDn_EI$G88Mo<^BV0~L%7$)Fu>y%f(nXi zHUOvPcA#xrFwSgeTx{2{eJeL_`O*EM{(Tqr&e_U~3FPUk@^L~#j>O|;%Naj077xwQ zkfYx9m|2W;v7Z)9cjWs=W;zLJO+V59jy#klj1+kj<2x(vSOzUYh4;}tOikHnhCDEiJJ$qk1ZI4)uv z%+TBYLOBxh0-pZ&63@27BErg6X8wkvKDQ_wE{*vPUg%DRgax$3MSwcT9+)p|v)_7g z(D;A>qlBD7wYg$^G7=tq-T0=}FI%yKfQitm!S9&5IjmiF8zjCGJ___>qEwGL1AvoO znn!aRlbNK+>OgNTa-MTnY-An?GprzI7Du5v6Gvsju6r|+mwAzf4A)mIL`Sot_&%qU zlF$=`4pI?N{q4byLQ5<~|BvV>w;LZuL(+u#)?EJ6>o7oa_5UqkM(S%<8o$x)ujzQ> zjfTBA$bDvD+XA-skV#0t8{<%ABg7>Ov2(wFW&HBzLZvi@|6nWEehCQAd8kG>gA{d! zS(%2C({`x5s86B5LfYUIBsy^FZRU0&zly5V^mBQ*SJMdiY*1q zhIe1n5W1A1aSF-5@A#8*bIMBV>ijV_>?FW8nk@m+D>GV`*Surxp!mg?cIp>mk9_F# zZp+DhMS<*n%0NDL<|!d?qV<}b5c*=zkN=e^9K9eA#GB%3LA@vlQM&q@de=x7Cy{C} zebhy*3xYi+_r02a(q$8b5s7{yss5F!2k9QU*Q*lI*Js>b6TDMH`1bsbjh1+_OnpSJ zpZ*jd1=nmo(AiHb${wJ_+OWtv|IRQ8x|fVQR?*hlo#CGNx1VkEZ(Ef2mj%XSH|gAp z#^OCcQ;ZggNWwL93d)7lFq8?rRXr}?4P5@E0J(828ys5xHEwd-mDx2hOm&LjIYnFOZ zu73TuGGAQ?fU-GRJjH-Nvtc43FPpp8siKrpX7qdH3I5s)ndJi-*C12SrBXZ;;I)}> zq(0RfMb!dEGugwGpiG>h4Rr#p+{Q?vsDLvuw@>FcA`NZNjCIutLodkGivk&p>LYSB z!1*U65{8&#%xcSToRIXO`pR{Ao>>l-3KD#BW;J95(AVQfHEq9vEZ76gaFv6r(?5j; z$F@0Ofl>xj(%DuP#;nwH2LB~<9q}I+VoI!qA9?Jm24drtDUp7qi%~e#B`i)rTpUPjfz~zsA&dr`SU^+j4DD+Y^ zF5Is#QPubn)8NbULp`NKVBXrKe3NKrgrI5OD+=<>SB00*TY`e9Vaifq#E2088}pQ5 zoi4c`DrUtkw3xxim=c}lkA0O8pZLHogPl=hBe+YO4$gzuV zgvKn>=CXbwN@`wE>^cO*kqT6P=4v@|zqmQiGjH<9*GR&bblap0EWnYNC0gi_jKT~L z4(r5M6t#tQRtPq~Cu9kPV#-=M+RdYi(7|EjyA(29c8r|w5ga6pNxn0j1Z!}g^k&9S zl~y$E>Y1x=o1t$>H2{|ypTORgT`y)!2DIT`+vZ#CQWv5s5v%cWiLRBxmn;yMEJs2* zhsASPwrX^doM{1(r1}U{Oqj`eG9g_?N44}%X%X}>y2hn~#{6VkmusQ2pv|kkZ8uJ_ z1-z9_c`XF=A7K735BkE3`Cw;+TV^rAW)ts!N8S>6XBR<(ZWyNVKAYh^b~LKetn$~I zP?1XR=9@PL^u>EFg9~-I^LN^!Xo8^Y3{IFVO$Xl+= z3{3Bjl(izLyt(@vQf{ZCLr^@3p|TAa>P!g!iU;jmZtz-;vWQvFE)O{7Py*{Hfbbg# zN8?X4eCNTC@bSusq!D+%H@hOK#@#MJSX(nx+-77EpxgjqgRp6VhdX$QQbQ5W@p?!Z z#+Mcp8dXn2h%P`KqM>=!E+nTHhMFZ*g67Bbse@{=RTJllB$D{>4EOj+2~N$OOw4nQ zNbcl-VUz|y9<$qZ0_GJPbgEaB_b230ZN3Klfu9PXmi`QwrD&8%*czW681^rHcF+$^5;r8&NzI^N%0kgby#x%rR{wMGnsyPw`~ZdHnlF@J>E=}^J< z9Zv}s+Irx8OIAo@oEw{Cb)F9=y6(8&6Ez1~7c(b;@!LMjMikD6k&oO`Z=hB8tia930VtSmG zNK0v)PA5(9%Z^H_dx#~c?2uKBF7`CQiR8zmO3i3EtlC;+Tnuarm=EGyJY>WdHyNh6 za}{5JEf@sdvH$2lMC_B=8yoRQS$i>t>J`D^ITQ@HJf&5AEP*hFB85UL6~t(Ycz4ls zOQC4WBy|I_&$%;JsGs+Yg0kNq!LrPQ3(dEY>?}i8k#jQqr98#OreLyXptQz2|HL!o zW)GZ8&%NXHwH+k3lZjz%BeK3S2WaK}2q%;sxP$qq#VtMyfG&aQyj%gy^`9!k>=K+N z05k6)#ci}RH$rvl>L{^Dei0gH`N)JLxot@iLNfU;J?@(jEvk+?&rHHzmxi&}r0Z!G z#y$Fhu1oS#a5c15I%fcS_6b7lAj5F`01*v~Wq*?P$L8_8UP_SwUjxlR12Y6Y=&FnF!9*_1jCYuf|B=IO%p>?_Z%g>KZUYSpeO*=NYO!%qPbq z@6_g^yLdINjh7mEwm(!u0qkOoSF}jFl=ogDOruZyde-T~@UlUaZ zp0FDS$UawT~Y#`;-#UPzsTs*~A=GDxziL+7Tn7#sE zcInx$7LP*|9|hDH)6E?>gg0&2@Gl~O7=VR7;R4mJ9Jp^CpMR3(}a3 zq+JZ#;rRu1iO2mBCy&^<{MGjXE}_7%YLO`k2eB7NMCvLda7NSIgZ%7f_NxX>f{qtT zd9ukRFpObtXbceVIacjGNm%!lQqwfaVU$#`mF?%f%C4Hx$TOXCXSN6uX&Y`&c(?id zDT(#8BpF1nDOm{wunv#JG!i1U)H^_HolZ^>&d=jkt4@N5>Vg4ax?*CTip^F74a5)n zJSEOYg^)UqF2EE>=pY1Hl8pM*Oz^{9_8QQWoRoS}Ab{is5NR4#(T^N>a0@_EK;LpR zuno(1;m2Dh>9&G=#ZvQHI1uE9#(pg2b^}tJIfB{?B}W`y{oCRV1RU2DRCIyBwLs4U z-0TPtUlW8frGLF;jkoGDFHv8V)LexG#7+Y+X-iGQjksH<*5~_gvTjrpG84cG^Ml!5 zAFm8Hs?e6Qj}}5?adsn^Tp`flyKd_~7Ll2dSxR`vrmsuzkhx^l4`e-cbZFgd(YWB5 zDK2v#s4i;-#nZ(I?I*4w!BQE#k>@}GjI?nm4iks$UuexP;f$5T=s_~FHpNpsY9e$U zr;GT=qyE5j38EmsxoyTk6Fv=F;&^6tWBaK_KUke4h?XAlfuo_Ff1%{Sj-HMHcx^vZ zr}O4JD7SBDLicePWev$h@}ox*uqaKNma$MCvjI9 z0Pg|aE;)*u>LQG?^^U65SbRP@(0IwmMKE0g$=zFY+QsFiyB>M9t#FQ)+n}5u*pTR@ zN7nad-92Dm@P75W$^Y_*^}D7<7HWo(_2*ZzkrdcfYWl7|#Nz;6HpPy&-hWg)?y9)o zg6@{Qi0u=l4zpyIH6%_IwmyFox)L7_A7A~qiQ2%5kD1hjG2R&|o7IjHT6=dz`_2sh8 z&QJCKZwzNsOUod3GY^b~c8(nyExPr`_`ZpjP~xZ1`gxdAnA%+2r*7bg(@qG&!;Js% z)Q4627dSj7_l|>22%gtNqo+P}CDHPGCZBg0Y(cV$Au)%BziJS~8^ll>sC$A3w>@CE zut!QhDt=p)Nd5tgB8ofDs9fxS6Con-38f!Mx8$x~B~E_7G)C){O4>C*^!u2y^|oIi zmDyX}AP;p)MEoOW;x=r}^!v?NTM+}``wF{id|f$c=8NFv=Ong>nD#Jy zBEh~}OUvgW`|r0&rQ1KP;?+d7vlP-15ugx1fw_`1`sk*XcMKNxsiENXP-D29kBP;* zW{0TcNOE<54oIt%I$KcQbdLU_jHg$Yd9AqV^~RDlM8~J`e8z|+S!Z4*z}9UXK*3!! z_TkR-LeSsQPif4#vb#6`MrHI9y!V7}{K03hX>GKvo@J}LPM$0XyKm6O^Iol+)U|e9 zi0;mby%V$~L|mr3!4%tK7`F(=P?A|z1yPPMds$&clZ1seOGe#zhdw5(=UebQqW)K& z28x1RM&5qQ&}h{s-3gI92E76uRfM^98TLr*pGI$2D?31Q2x^~H{3AAhMl{1Sh6jt_ z`-#Ytq66i)o_lt=UFR}W$a}K(8pxyv2k!1Y8DITkfb)zQvtLw?E+^`F;OO!Pq7YWw zKzJRlxmFMH9eUH|xcp~@801jhhT|oA(@%PX`7gZnKHcD1L*Qrvy^!m zz*h$#C#p>Jyj9DMVSTpUg=$){_iTV~1j`gaJp`>@o=+_uQVWnvez}+mkETTxxa}El zCwZ-@4-bDT?OR(Bl^&|O-WFa%tH0n)26&y^Ghm6(Bp6%Q1sa@{MH*+I$r> zXNCx+cLEerMjTD+3&=@#wmnF#!-&=3v)>~*^;<0H4!xDp8W&|_fH@@LzG!M!{M^UX z+?ViLMA>g}y}<=3MK+D9ELP3Aw&B61r-1VH&F_v|1X1zOUJOb_8B8nFns~`5b3*$ANCclS}6UJF;v% zF<0S^x|4T5dcUq4jbAM_<}a0LI`@?(gO!EwU}Shu+g6xQ>3hI7IOA+lF_t`@B}%Unw#)D*)lJ=c6;5V7Vy- zdhc3})|-(mSM1#v5gc^(XBr8<%6Oeeu-b_F5HB?aY^fIkJ@|OqT0>~(*u&!n3B1A? z&OrAqqMxA}KMK)f^0AY&)~!sEbn*({!7}?(zrEb4IKew{ zV)V|2XSg$1ExuB@AMfu(dHp@-OCm#!Nc>131p*fmL_a?8QIsmdxJ>`w3aNAfD7Vgi zvtMDomFxplGIg(%7sqd5F4GLj<1h!8Mw9YSeJPX$i+t8~SFd?YZ>a#bWxalO*~~8k zhd?F+jG(@gipXr5<)CA~cM^$$;2Z_)5E|zBQ5}3DicSlw!Nr$T|i>A zK+?WJrQ0^MFd^H*J3+b+M0<&p-Afb@Ap%-N;*8I#Q&n=aTT^;~GQRQS*J0TtTHC_8 zOcqD80|pz76S*{Fl2{?d1@I38CeW!o?<>iOHi(qAlAq@GJZE}NuPsm0@M}9NH)#Z1 zUKWUyCeaF83Apr05T6M9@Ak2ONq$%g`otur=z%E1{{PSRs9hUUxY8hZ%=zCQP`}T1 z@ekHCJiAmIFP(2z&*`?b*By3)>ReG?_4iztSKa9w^9B`sH}#9u1>_zBtY`6{V1^bk zv290Vmuz0=C@67>HL(Ysqx^aQablR&us^%&9>0?EUnRT8>U;A^YRIQn@aaEqIBPZJ z&m%QS>fMr=3pGuzE4FKo9l4-BMdy0b8u3?M@8`woBnLli>Ko01W%oHH zOj$$j(k*qeVU`|Z$k6J}_U%gWs`((;29Lfmfnl%ke}a_BFN1sR5&D=c4UqKGaSZ7awfv3~UF&9_W&o zGo7x1*_q$$$&|I`+FFx~h7+oVmCZd5vqc>ZLrDM}M z-ip=8#k0Wai98R;U$>IEes_1St9R=*@1~L$40|y8l_}=#>l?YwJwl)-*<{rs!FVhp zXM>PbYDB{jr>V^AZ^kYl@V7TMK{O23i{_!==!(P-$ftTNc4nXP^-U~6aR%(P zR#>cG@Drf(i$Fk!e}Smhtu@o*du6Za5m-Ga7oP*6s5kQquO-)C_%T{5RXJ9wOM%=E z#jC9O2<)Iuk^aJ!AEw_Kc03p@Zn@j<8*|C04huBGVrQst8M2fn`P~QgcAM}W621+o2zo8RE`Po+hW1)TMyR7um7d8U069cLX zPO*23HfbW5L;Rr{vE0_E%Xl%EzS;D6T*JF<>uwfgjNf%?0EhvhmzP2cUm=1>Cq^8l zu-lxIKCR!1%VO8mNc9K(^P3wD_T6_GIQ3T&+jt)RhN2shLio|t7)34YHB&4`^W?1` z*X_B*045)qzRcfwjgMz|URw7ZAv;K=r}vPIZy_RQvUePHll`Y#gINDRp6%YYEF z40@}-SFIKfhVPbIg9oGz`(aejfAEhPOH~# zDl8q&omGN(R4VKmB9*sZtTZX}4!4NOr2qihi;!Ma%q6dLo53LR$KbNl>j+K=fq~|l zC(W!66!?rl47d2&Hxh)@CB^vT5z^UBeWBZafIRLx?rE8;tQRj25XRto0C}Jgp@}@E zSmhh5;IoUfAkhYbai}sn{~z21s`nEiP%4QR>@KxlxW*MV<{)V7Au6_zB`2la3IO$+Hrbh0oD1GjcuAQeH&m ze9!<`Fp?~WY8`5@MB6vmH{&i;g%u*i0(t3=MTnNZ!6!s`PEjsn>(ym<= z-X^B3#B@WBoQPVFGYdi>yB}Gf@x*&1sD##u9Hx9jEGe={pb^A$zOh4C_ipDq zeeStIm9nGBe-CaL)-F41Dim9iUW(c?SdNO{T zL!k!A;+xbZS0bg=>{ZM8p&GbDM&5SbvDHo6N;yVF+d7MkGl6PF0ot^ZxiUAJgidLm zZV=SUt6CQ51mD5Vto-8CxePWTv$8gIpKy%Nqet@`cc;0J7^It(v5zf!zW` zbu9R|!BQ!>t!3Ht_veN}R9yg7K>`d+f5Kvb-(U4^g2-NZA_&`N-GQzP6qgBE-C2ZMl?!)}tO3-4fZjtydnd-t4wsrii$CGM zMqme^QVre;6|cjfiDLaYBQLcAyPY8b#m*6c`(V-FAwaAkQjPgmBZ->vJ{@EhM6Qk1 z%r(7I@!uC`wrfXZjUui}?>o?QMg5w)sfN`>$5wZIkP7VKCXNVRoCZ{Me9HP=X1KpA zd*Kagv|jU>qYrk<7f?P0_z9<9pvWLP98fUVa{k|i+o16(a?00fhoSc?f9(Ejl<1i{VK5T6*tmDsh}1}}mN zz09}$;JaCUZ1n*Z7)C%VBEw@>QlC0Oy64GaB>gxKR>D5(=(mtFSVjL}U0Ao1FQivB77nPy=5%CttGtE7{i1jVM$lfhG|A<^x(u1!&tAH#tg zN#Xg&Hv;eJ)X5E~&(9IQ&1>t%8P}yGR`1d!om3vRBZ`vA%A1>Km8dj`ZJ&#&J%5~! zbVcv&9)XwvO*@8IyWue+x*Y{w^Rn}WR|C60x%OjZ&%fjUS)$+5ukAF4Wx{3h!syK^ z#n~~5OjQ1pDa>R8|yGq#SkW0ATC^ZpZ zIINzMiW zo_JP-Lm}oU!B1o(oQkiukActfhd=koRTXlK?zs_q&WD8qIsJ8bGe6bq1;d)+B2|8? z^A$Jr@A4;yWdHuB)5j7m{r0OrMylg|XGHeElR;vAqr4{NID`Fa;`0m+gBY!54Ev9X zt^-~hz9b$ox4oR$&$qWsWN;Q~()L8R`lq!~SaJXvb)>_y`pvJIA60SZ$#%4(;~qEd zw!-WD@A{*0G7hGMCrvH~FFaX`we!*!fG293jJebX(-7RgcnJ~`0miu+1CLbqLVTiG zh|`J;DXNl7a8)^OqaV1hP1OI!g>E9EiCnPt2|-ehg6h#{Ng@_NY+d*Wth^k5S$yR! zmR73L>YZXpVS~}k8N(P~Wjv5xV*)ULGj5UP5sJ5B3U0q1TC~BnRE(NPOLE{+0m-FG zL-29hmCb#~v$5ELTBxz4%*#ZFDf>D&q!_X(IlMu6Gzak<%XGD8q>~L~Vj3F(m4>W6 zL5MX7tVze6&%TC~zHtn_aD#>%ROBE62(!QvT``87c(J;v=RQ!!V38R}JZEKeBMp}$ z!}wMW%|NIqf30Q<{ekGZfzDkX800s4lSriw$%UKz)d!6mW;=zt6I94ES_6I?O^nAA z;^+jha*~-+sCe*g@|WjmPH9!r1>~v$G|u)w9heqQ=2o(~7(MWy#y!+@1chts=i5I5 zLo}WAt@l3xuX-oy&51&&rbPFTb)hN{)2E+S2a?+~BS+HVnFCJ`$;U|X@#8aZv#sQ+ z?WupZ+ZJhlHK_a5+c@)Fd#8?Gq-L(YK6IJ=Um90@MjX+AsYkxqv*tGeVl8h|M8Kt& zD)LMVXy|3^s!_EIo9}?~;^GhtZKe^n`9 zn*88NXAB~!H9lvDz>52OX~-_9t-dedTZeILr5FgcksANiikTILX6p5XGqDjW(n$7+ z@vY4#S3o%V@ z*~4Tb${a9Kf=p>6ZQ${@(HOG~2<22yfv!2Z@R(G`+Oe8KP6BzJgG#6&EK8U9iM$n zXLcniLhVDd;u>Ux!OyI&7~C~C37|7BTBOk^=m&)BR~6mG#_XU7 z^e8pS&4xAhs2c1yd}+#U*E}ui<7rH8Pe?r zr|C(6=M~)uYY~#mNSBnQC)vx}Dar7fGLVvAHL0qpcM~m&E>BX^UvOSOSvq%(bl{R) z6Z^m|hAJg*s=T|oj1iTQj#Y7!OQn*5&ABIwAyGz7QNZ`aMr+=}=-KYEZDDO!sY*W1 z-^DZOKamtB`y-l)vip0gnmF$%#W^-_s~7x%_63A!eHuVHhZMa4|{!#spEOa1QE*|qcnebb;)%EJx;_>yk$9v{i$;_$50iCF_C~iL0{(GGW26x&oi2a-J zs)9JJy|G3PrBZ}}{@t60nbo)GM)&Lr7`3`bOCZ4=yqX^NvFaM@(Ul|HaX=4HaTqLA z8AV8Z<0lM)OytbPSW$d9vcATaU|@r=U{?|qWR;`y=%5A|yEn(rDGIuam7U-lk{SP; z^-beO1Y>1f%5Psuv=Mj~7h~plf*X9Io8Aq5pj67U*fL`k0BFrP;K`3)`W5xMYAvS| z|HS6q?XJRQFy}q#44n-QLeKrYxz^SdQ>L_E{`vR;_xp13f0=z8xYPsw(lQApmCRN# zt}tXK?ONHc{3o*mc}a?OL9&%ih=X?qOBuHpPpV5wviMT<5e3ZU4rg+!%CtctHn-2V zm&1ho#QB>`=rFrMa~ewP3`pW6zi1Pefv)+O5g}{|E}|7H97%{;X(3V$JUmTV@2&^2 zBaE%qL}-uI(JV}k6Eaw0WCA^2(a8tSIV~@d>8LZGXX}Z z)u538wT+0u`rdw#U*5yjFz=f$b9cKg^$!&;;HHS&QejWFzIj>$O101B*4TtKWaSYrf5{Dg0e14TA#1{-8OY>Oe zWa^CR1dEDCawx}2A=U^{(L6g&Tov$MM;aRHOdX;PVFoXfBl(*^tS?6b-UrFk5OO9Z zVw7~a{SXB2LRdEjmfp;h#!Q$Le?llQimmH+yxOnTU*3_aq3uHyj8F1jfj>wYOx(Bo z1RI^H0zrITJTBgfeiat)ud)wWSLLj06t_n7&Opv;I|rD&E5n#GC0}Z#8#VH^yVc`b zxjTIgJCM#m$-nU@Tmjp)e0DOhX%fkwMDp74R)B_n00He4Gp$i3O7&51m6VcKG)wwl znN;a-^}txtwpf6W5sP@5A;PfyMj>wtihV6y!oLaI{h~Bqt60ELE$8GHoyYJ88*zxD z`Gf(V*U5hZVm@2iOQL5pl4IWcZqg|dX6K_vfc=M4`~xyl0E(o5eCr1i=lcCzr{>)%gRQ2(l$Q5aFn* z+^h+L8b;A1V=5Tegs?5M^Ic8nYr$@5RL`4dnuBN(WSd!f-#uGbd{Kev+aB>A|GYY@ zy}Yw6|5JKJ9=3J$kFs)cc5m5p+@NrzQa^BAOBpzK3Q~z}Pwqm@oc7F0P5@s=FT{m~ z-?n6%&EGZ&qC%Qmsl@ito1YbdaI&^v$31tbDUo#?I7O64;ooLw0$)cWA1fo&{(@`dFpg@%m8W1By)N&Cdx@D?y>tu5ornZe>LFK zLf8QW)nqH>9m?W~(scAzA8b&g@F>6-Cwk~K@>9j*Je`9Db;laSmwj1=fmsWJlWdf8 zWunelx5J<`o#)xX8N4vaoqhn&U;TVq2Yd5kubBZ2WR8zSufSb3Isv(*e1wr^biz;< z!PoPq?sB*dJj<8_M_pjLpgW<@3)5o9RSeh>t7DuPi3c!XBg{JCHZEAVMc@6Y3-Vy; zW0Y@;(GyHjP5fc>UFR^s)~IY$iy*60#qT38>RRR~*H{H9f!;v$)L>E5!#N>YlL-Fz zzZF+!-qCk&=@yNA{nM!l&@>(QjFl>x+%%>if*Qp(?Z|o=6t`W#Uj` z;rSolVf0m5)2*(oET&cxLYS{r%il7nVb*DN?c3(p)3LA@h9mj|S!uEwHB;5ynecf} zP4(+Cl$lO`7v;Vw)5E(MO7CeeRc$IJguyC0Z2Kni_ZVsPLuot49MaCkddrNvwFJB8 z+?}5F(Hi4Uc~GWrNf~clQlKjb@%yc7%8=1Ph=_UXO;@QVW0d>n=2OQQ-O1zi{Le3D z{3A1JI76AR1<4{MhA2}T*POld11@yzb4RnwCG6&OELn9$+$GP6?oxB{+*Q^|c&F-L z0|vxr?c^gg=JYV$SmL|RgdkR4vBKSRSpco|6*okdN{KuaR2-QJx&xY}DS_1~c0IeF zj2duFbW0oteiTVuT_JpEg@iQfuc6SX6Fk3$tM)Su`cw@KFE@0Ai`mqzIQ2AVd5nfs zIX#B{19hyuF!jSs2&vUlZI1;4?$lnZfUM=J?RBVq5M9vW!LwS92;Vi1?{TQq)T+wTq2_CWBN?HX)FA7V+?=hr8)ST%*=6 z-{;-ZVD5v+=Kl+c?FK8ro`CCYKZz{vIQ@*c<(v)c@IN4F8882pYjsHw4aQyt^GLM5 z`HttalFmoobBEUF2O#PTm^Q+y4%U>d)pGOyiqtaI&G}%SIs2Ft-z||=SG_99gZbJY za9Iz`&@ptO!~`vt_F_o58}MOc&|Wzpk*qKMb!ntEx#+`k2_G{>#jGszAfN{rJJu=Y zBki0s-DarQeqyN;5iA4=6%2Cv+QS!8vgvA+)5o7495lMshAWWsa2{ybI8b~kPj6pH zctfK)J!AKtogVQyGuRGBM5Nf8`}*UzfeF*5QB9r2^6Ps|5u25bbX;zV?E=3k=^^<5 zXP~MShm~m?{E)l0KJ%NDU;qT14pQ4$KpV)Vn;xSE(WYnHsS-p3H7|cVt`GCqxCP%d zH}JG)1mE-0-n$@2P-gu!7&_YGT9Y!a`{`uW&+r`RZq81b$Aa9QMwEI)m~fO)$e(#H zx3GeXR5rtdZ*J}W1gcV;C4X#U?Kfwdk0Tz!x#3QNAPB>7-lM?t?HV$ZPv#kzM#q&o!^t7zab)YC~X>TxCCkv-7@a^C9q z2owEXv=VO-8i>&tK%rU3BOr*tb9uBsoz9$vVG9*g>)R9}XUdh{4F=#F*=haJGzCf) zzu)}?$mLT|*UeDjyJK`>c6%#yrYGdzqv?kt3sQ}6t~H18bBIgNf)iJ%Q3%wV8jQa6 zw%vCE1U0i9R22Bj*P7smjhHK{>@N8NE({sAcA@z2E9LH8Z= zeOT3O+_Tr)C~??5(r3Y;s(x&XZEMrKC{N#9+~3XtBN`|3{0$$ z>II4ad+z^EZ1?11i=d=$Vkh8&hRjh{r>yoM2(+!ZEB06cA`C0E#F{2>XEv-8!735* z3=ND=d#-U*h({sGLc>6U6n(rIf~fX34FuSfV{+A861}WzyLniXWXN~5IwxuD^S(c$ zwzE~XfVSDw5D{u*-SXs_Y8?Njg5*Z!sq@h@cbdk=#k_e_?bR2O&!Vm5J z|L88K!4juiHLU*Bfpwv0bshcVGrEKzNa6x9th9WC;X{siH!6Dzdp~#r$p|usM%#V) z3V_3fGU|U6+S@#z_RDgkj~~Z^n1KqV&M4GDq>=U-9Pa2TA?M-VaKDyO8FYXpxtGCr zS5wdTo!s{xUFD~E4{uD;P|=6pK;BSNK%h9dxLJsfI}rJT#B!2VLdOeLa|hOy@~dov z?2#@7a$AiFmprs-6KZz-&Kq@a^zZ&rrd%Hvx#0~G500pybdJO&(2?X8|0>TANb>%o zik7k9#B&k#q^DLT2Oco5Z(9MBh;R})Q=1tFE!D#C{7$PTwd7aho1wu!)&nbecbPHz?RDW*&qKy}n_*pLh~RI@n2 z-C}fKH0k}u@P*o0chSR8h%)XDBV|oB%5});^)hxL#~Od}-B4yyQxNHim6d?|z1_3u zm80b59K$u1iOuexWwgp-aP&<~il%ym^eg){@WxPJa?Qr)3I51e@gl6VgZY-4CLQx> z(-BBm2q@FYM^Qe#qgf+T^m(Ka6Q_K+q!RGe8KC&BE9M z1);#}N22uxP;^vB>b&G-tQIsqfjWKkFcC?ITFR64flnq1;Jm$9oY(a?Fv_dMC66^= z&ogq`2R1Z-28-dBCu-`;^?N8psJP(*S zdd||uPbyMv$f8-DO9=nX(qLHb^q=89jI?p)3qjZ-lVdgLX6aV;($kBLxnKF4UEV;l z{o@jtbaHA(p~TCG?=Gf2ndHtU)RBX6D7eT?vud)R5Vn*tcY5xEod1K3-fRFvd=5Jt~59Tg*w)qKPgUS>~i}Fgd1LqkN@idx}DsDRjt|lY5%Wtcj0r=FG?G%z^k< zWLT#QX?L2vkQryox|PE+Z~e0fZ&i@G={b{VXMNl#h)78A|5FM%58wCF|C!CJC}y~J z@z_QXLKq1hMM@9yQ6PSe6U+YrA&=y3W zjKo2flf_d?vJ)i$j!+!hyJ)-jYU4}2)LO>F--P5_M{JF(-vz}NES|9qxffthBxNB+a6OcUye&}iQMX@xzsFz}Jwzc!<+ec0q3P-2Y_%LaWH#f%6AB~n4c&WCF;#WJL zfv>4l6EPC1luqNtUo>`yYkKQ5xAj2JT-0{OqWtHXJlAb$bGERR{z@t{^ZYA6=O$OC z@0PM8DESr=bRsvlZh>fpk1A4K7p8^0X_Q-*>0;|98jd zK8`kOLh?bLKOq}`!$3$<%Ygpv;3cf}GRYKZx{y6ze?vjAm|UyJ-_bR7|DB-zZEpZR z38A@k)A>vAalFH*twm88)f@7jOwWEd>PJ0n_wFQT=lvE7)uo#^rOj79H@Wrz$X6P`boRi1!UP9_&FF#rI9b9; zo;4qC?j4G3Qj5=DLs7*8b^4$!#koPug!&5cz;i?&aCnK*rUEq>yFGJ^xkeziu0O;o z#WQ6nomZ*EZNz+V zB+%_nMgu{Nw9IrSdl3h(g83@5mH1S%u`#5o>p>w}Pu{Vp2gA2N`8oIeWB}Vco)NK< zvt6NjShE01dTL>;MyjGJsHz8B!b1aZ`wV?SJB!(P7NPZVl{zA#rvv4XSxt5n*4~6R zv=jC#Z~?V@PNTnQE32IW?Z^ zl@Wp8t8A4xN-lI+vcI^!6qM48Hg2ac`KPa{&&9?baQNsl6>~bdS)=X65~o7b_i5l< zR{iNg+&Z$f1vmBt`lDp1Vk)Hnn|1c#kP@`Xo6=x|TuqwR*bF78VpY&ur6#|l_>uWu zP)|w6iZ$XWOpDCYz}F{2BIxozJ=7~_MSSlRx{BblV?`Yek}|(bJAI5zE=(i@xt;K* zSVBpY6R*M;_ zpb}XQKv(*zbzJ_S-(}r29DvSQ#@K%VZ>rRDxc?{v$d?wiZ*i30jLFy{+N!t`L|QJ4ICS z_InD}2E8@JI;-!ROwQ0e?823vB%VJqzhMh->Kczlk&x|}!u!jVsUsg-&l&WwvOS+G z=q?a4&j1?8i|tG;lUMIj#WeZ65O@y=4~qh`^~h@?%{*VrI@rx$@v2$_=`uqlHZU4D z-!zVEGxYPCW|cdJ=~-26)*+i}_{h~LVBAS-DK4wp&Y#xkwr;Nh<0jO0d|g#aQ3fU= zBUHc-Qmj$+uZH6PtvOj0-~Jz5DFrv|?_;xcMxg)0ic_y+_;k2!mk$Yz(R5FPEc>jv z%7_Iv3XJ-;d%Z;iPt6iKKsDkB2stpF<)9eO;N9^d%OU6_aEO z*Fp^)@Knp1Pn2nmqN1|_{ zF0fBX=4f`sS5S2it3EvlY3Kq6tu52FWNL$&5r}$o1KN;=&O@%Mm(G+M*n#ySLK_VK z-h23`gj^v%OF}+6%=Rm{H>wF2B!}si>Ig%Q2&ri556O91*6g9<0j&l6h}<}(Wwv#C z4WA4pPff^{VTaWKFdYCiQ20_qXQXrE2niQhoowc++31Hrb%Ql+yf( zvi^8MF4E`^xEHuR>_M$vu^nw2;U9?UP;uV$crL*?+2~lJbmXqr_Qfun{>8s`pWTe& z%7Q@5#lQTe>j`p--JhO?JL~)q0ghFV-Z8^3?AsL72B%y1hl&sUuSGE9MGcj{Y}o0_ zd&U{xC8Qc1V>0!O_=_x6VT8|m61BGFU=G2KO=ATPWN*O^ggjV(X6rGRhpzMD&M_qp zY3wcV&M_3vQ6mky@T#GfP3*`VG z*gf0J-mOwThK<=@>(*-+>y*67Pf*-L*5W}W6-*0Fc;?xcp9&(u>K{@O0|i3!FCf}K zsOw>Jsj+;FOj*rDHKL#`TJdD7b9k)Wkh0p6p`KVLjf9auv7_qFicGTc8fpIswGh|c zgwCc{yKJrM__qi((T{PB+7Jmqj5w76fSWmdEy9U`7)77-T3+8pY+qWm zFMOz+3om0wUUBZ!blir8O*KYKk-8=QDCm6B#FMC&;IRa!g8_}qRU^pF4NAO^GaUJ0 zvX0T|j1RbZKS~0bi01d_anYJlybDW4J1}47Z};%TV_jJB1F#4X{W3BqNNsRMy&4nE zKLU?7$Bv)$P$hFTLJyy=^55mOdeU~6(H&&^SH-7pKV+kb1af+o(c2*8vWRXDdeYO8ii2lnhYqrrb%gBHW#7cc);xT+`i^f_X zyLD>kQF1%``u{y$mdSP{JVWLM7$>!#h*aqo-3C@*AYzjlVtzd*Vk>wQ6LnMHR~|Yu z0IP&ae+yAPIZm^2Y7E6;bo5nj=X1c({K#0#`oB-9K*Q8!+}YOQi4VJ%s>ZWD%Tu*` z2}J-ZhwwkN&W?ZcR7WM$wr=d}j28_Pyx>`8B&h-8u(>jdFotp`ARA81Uz6)S?tf*| zs|g11qNh<18(6DrFn)9pjcfXY^};NsCT4hkT|&xkbB0+kDbS1mG(b11qDxne{3IK* zW+PdEb$+U;W+_Tkcqq;v27oxgn6jfF-~qi-%j@FzvPqx4wCIvj^8Rx(!vkEukmT^}7`Dn-~+Rl%t@Vy-HHFOC^`Ct$whSnL8257ceKsQLC`&+Nu z6aqmH<<$(MP#z4LU>yfJJIbCZc0pbvi^_|b69J>7IA8*x2LifjYGP;nx2audF+W7< z@%~@mQt`PP)ANEqQT7LvE#Idctw}0N<*j~_+Kk}1`+42;74ym61Rpq_Y_}DPgUt zz=CrrxZHg9cR33$ors`Z@kMAbituotP?OKu8e=f;iF)aRj_f7L z7SWn9@HL`oNpev$7qPEPKW`CCHZ$U{Bl2I$$$e*4Z~{p}XcFJt&QKcDqW3qy$a5;g z>LuxbP8KbzelQHPD#Z!2$Z-b=f9o%tv54eAyEA`(HNNC24It)^=;C}}0A1zLJi}J> zL$UQapMgsutglbWr>P9G{guop2P1^tjktqm&3k-8>)Zx47==o+njidrNKKmaDz3Xx z!+Y^NZN9iA7B7l->d4(aXXijmG3(=7aHeYiZ;H5LH|YyoW|VGHG+?aXg0kWr5{ZM_ zxZl52uoWu`pnt**gl?f1;MyTG?_4|K-6}M~!;HK&Im5a$t`y(?eea9v3~VSc+HhY2 z=%j<9qVy_6l`UnMgV-On-nnB{L8zhh!!wQ1hU@kn+%yE+?`lyMps54w5)S$7yg6k` z1H!3lsM5zT$$C1K{s|Hnllb1RFn&T~j6E9mgl~*fw$<*KTpDSEjn~~98%I~O@h*48>#Q2*Y;W^d>6HFD)^tSt>)Z_RKq(RDo8M{qqOK|~hn4b~ZyU-kA52hEmd^Suq&_l0=M3Au2Br z+FtoV4%VVlKung-*B={zs>u9ER%RIJ|5S`>qT*oS|&?RjT{vZsMHb)cP~T z#q61*s_I&wC0zki=9!yzNK@^h_!%6Xt3<hp`yn(}zwPZ*GE=Bztn_B(J63ap z8^`wKJ(t^4e5QtY)^}7hx!nQVardg_2arQ8H>*WM1cQURS<}D3U;tQ8Y5y?1-x`gF z`?~sdcJtNTSgKC;^~8p1=UyCcE#V1mUmiyIz%Y3X=_|C*c#$D^kG`k6|pb*7wR}7`)*n2^YIsT=8!)@bR&YZsFKr z$~#d_#@5PknI6ww^E%_cw!%!s9Qu__v$uH;_Y4a_MYwZ)AEYMBJcQ7e1D?7-lX7&^ zRrckq#;r;Bb+B&PbI7DQ#C>)oNYrh_)M_=R;OMR^BAyF#`i;2X?o@M{C6EHK&zgDJ z`jjG}+|D5uk41%a8lPpX+5y_~7}{uJxhASzotc+tzB*$%0oY^N2H1u+a&$;gkoq~Tl*4AwO3sh+P((Kla{%~^+WtWAuOjq^LQJ<}Ym2Ezy1hsjDNXm(OniD0>XeNCMrCdkZ- z0CVvAj}vjkabxTGoG|Z@QV4W@`YTIa$C3gwzYMoYV?|IZdrA})`Ds&ni{_08(oP(P zJ^3FAIDR5`#*g-)_1sP)$yUHgVM_^BLuxV#^g21y5f{Fj6HKVw_VpXo18G=IcR5Q% z^+K6L-+z5$hnnW*g<3DeFb=cC22_A8U;g(wCm|76ULv{ET`2P0rD7TZ~R(6|%0bvj3f|qO_*Vvlb#GnQ>AXz+X93iDBN5 zxTMaRpvSr&{IlA2JUo;YI11;bA6O1#9V%oLik647_rE-e2afzOupNXW+bEZ=Cl$4j z`t7{*X}1hYuhJ)myLiL+zBf`D%@*|XXsbEwUay5ANO3Oj7j`ls0RiEX1p}J}p^Y**4>h-knRts^B3r2*{vhY@d`4}lRE36PptWPkkmLiFI6}@ll7X%~P z#4Zv>g{i_2m`^5n`~1$E78Ug_j_$C*Z|X-1xTOrdsR9?Aryqg0My<&WNsH_?ezY4e zJ%85Hh`3uOTHO-_|V>UHxCl*({{l>v2&zp5R?VX7gVe z5ojP(C#|5m8?{e5BeuA4-8>eGHVW$%B2Z{_iPra1!@!Wh_vBhI0s1=KmgwgC?2D<6 zwQ6j(=<0jL?7)=We9oK3-v_quJ>6OPZYyV|k`Lsr+zi*({62jnYJchFjLW%f`#9!~ zy~9Dt?Pyik59*lr=R~W1@SW_%!X4Z){{BRy!Jbb_ejokT0L0A=C&Z52O!M?sw z7{_lCeEoe<*|vYYBe-JH#E*x&1tv(FU!S^2CiL;SDUKSs8Z>cOSm=jcLQ}BN3boQ~ zcxA=z1tHhUN|z;XvqIy=_kt+?+mnP@uNXn>>ZKOy{ABFJ)v}CTby5;cL^uEY_;y7X zGx4-nsgJ0tIrqb7wCs5r=7R>3t-0{O1?p>_XT|!sRd}Q=G^mC?hpUo=tB$Kl&#V6b zqU<+gy^=)`ZEzRJiYL@l;WMp#J2I(icBZ3<)JafPV1IX}W0H`^ifFxRJqy3LaTq@Y zH}R@*BXFRL($4}306se^Bi8LHIqexnjToQ&0Ek=WSGh{|967uO=DhgA>esSr^l$BP zVr3t)6XWXPG??Uo^d84vt>x@q4`THKVAOT0Fu+UZaKY)HokRTQ|M=1Q>14Szn57xc z>J}y3$1bYJywB@C2t9=(XY`u_l}E0U8txn2s@ru7`+gyEN1;*IzVs%a=aU>~*NMyq z`>3B4*nzOaMswpeKsMks45pygqLc8{b+tZpjEln;zn}IEm%(n0%zXiT+|otSsqy^| z%DH+%@M%WID^!(8Z^axTzwL+pi|SNB`1N8jFQ=6Bs^lAz(^K&zJ zCb(V+kr1%(n?MOO21kn0D;pR6&+Yzje6D;EfZoawyvKwbBg*NdK1>Aa_ODXlm@Sdw zH@8tkc&)naDbLgU+f#f-{l>cgE47)>HdYv-EK)b~+UZ#db9R>ns^TS+`lVV~BTR4= z205X`p*EMok9bloa-;Cg%pn#GO;V5!CX&&8NY)M1eK+>#&vo3Zqm+%3R!NG+Ex| zt|+s>HLbkDC`I!>S=|A>y#+d}CC!xQ)jqPA>o7y(dDC)(S(#H|jLZ52dAN(4nmg7R z4c>Z#yg;Se0hJ*o7TU~HfUN7nQ00I4$zUOlZ8S(tlOH7M$orMA-^~mrKU>a^SqoLE8T|b7E_PA2VY<;kS zls0?d{e_1Du_0x+Bb0xtoLx0kKVsOMWSX$aWboQnoN6;f{e6YLbbgZBN>ihF0NZ-f z{;dtd#tV6WHpA!Uex8+y&Eh*HhWi!W`_B9`+qsMr`w;3evJ1Wz>H0tz;jg50!7Wa=9uu}xX-0|37h&uv5_i`}R^sJtTd$e|@9*jmCgfQ(upK=xz{CJ(Im1Z%d~ zQU{5;viU8Dq8~U_J=frxq7T5dh*wG-s?urqjhy7ZW|S@#hi}{55OENZ-g9*S+i6;9 zvb$dv34Wu#CwEA(00G-c;u}=OHQ-^>b-=vaflH22_vkI1UC6kYxCrGf{st}EH@cR` z0$xHew@UJb-r84|p#DCw;4Do;&A4Ye?AUUnkcP}!JunFYY+t-V0>w|(u zlo~5p{>%D3Xeo4V?)3SVfN`{ietWL@fP-uZP-Odf?xhpK`h;Y8iGBs!Mq(B53~=Ww zq0910WmGOW!EijaT`PFZl)89wzf_|=E|1_j)FR8B*S}L`a$U@v(vt5;QPQSI_sQmA#MUjGs=!S>^R6^=~Gct>$l2QP#>MoT?D*aAzCTHif*o< z=kD@tRfTGj#PERdiIfTsNxCj)y{tR1|3}5~_KSzHZp=@zg8m zwvWv)3FU}@ELeZ<`4=@Jw#_QG{%Odg54uj+^G;Dqwq|0Ikn`#w-lufDD|h`~vAi*MHk?!HN;`z6oJ1XukzKV1WSTS>B(7Z>8Fe z0B}EAfB|O{-f@W_1G->J*LE64o8{yclY__{Nh;3+yVgAL#K8Pl#3=pO8LAhQa6Uy- zUcT)lyRv@MVs_fR%LJD!(^!6Mxb=dukxfherG`(Qs8g)t!12Owh_Bwzdy#@W;Fplf z(@`ju&nqNPr8vJ4bSDVrFOsaYo>m-Oq=Cwp<`^bDJod1wM4x_@i7uuh%FqZ4%!4KkHVBw_2+&p zZh-o0tO^pki|qr+ptzfQ$uY?NFP4o2aMB9c+NC%S&^{7-YL!BFJDh>GXrJQtNTEE> z86Y>3-0samOcO980d0Ir+rd95P(T8KPub2>k_P!7fVIS*n`C<<;_C-9+YJ5y#etxY za(weVLA|s$8+=U!9sYuRk)NC%1YbWgGb_7S1u;IjE34=B>M41O=QiUeaRh0-%{gC{ zU@RICg_ABZ@(2X%6gVXs^4=RY^5ZU&pb&8Wb}-{hsC+*#8+D6f7Aj!k_>I?iWmtD@ zfGu{DZeZ;3C(~v#bW+Y{Z)lISMN+W7kSx%X`UT|?V{xSx=Bh}XH8Gw&bTPV$LsnJ} zGC%9N4z#DnD?VS(b=EU`ymKhbwWd*qkz19LTYMJ60FJ+P+D8A!8NQHFlJkNu>VD)we4JnC4|cdmO&$w&|}u0WsA!^CcG$ip3Jk`5=7T);#TsTOCaMacY>- zP*S26)i^o1lemy$)CegG(JDqfn%)-i&??m6n3(~@et?b=p;OZrDgW8S`Pw8F`Q^SU zR*z2h&(g1>j|y}_7FfCt7URcinQ?rAMmHp($``fzA;%2qVka|lq&(L9`~!_%?eGp2 z1iMKG2lnxxtfkp}#K)WF!-c$|yK(n-bV$nU+Szo9oL1oa{9|@IF-h+0Yw!E*`^(MP zElsZ6_r#)Uobi5b>(mg?B6?oqtFKr!^3gfWuM%T(&WrVHQ3w)B98S9s#-Mbk9p4kY zxFsRFibQS>9bx!<40-*7eN-MtqCb(@4f6)kTH#BBk3gCKK{FR^M#Hrl;ohQ{Nkq9y zh{exB@$c2WFlhvq?abyE&?d;6zNS9Hhc18HfAf8QxsdeT0$btKpevlf^Y4H^)B1Fs zLwCQkK1GyR@DCR0@8uxgBBVW}zGhQNNN{(9ltj$|U5;(76)uxT+{cMBKxTYs)PYX~ zw1gD?ho=EUaup_zfR7{K>bI=$IKMgKZYxGUPJrs_OP+&=FB@bDUivK;72E(zK(xR1 zE`}w#g9&9^6XoBuIJ0lZBWvV0#pk;A_uy#NGqcrENXhVC3`2sCm{DPmx8ZnnlW(r@ zC}=yVA7m%pHW7_p>Qo~-7(fUR0aY=?Af|TfCj9y-#gY71`kR0Q)OlNVQxON>ZoS+r zMum87$)@*PCGyG-&*Rq925L&VgA{N~7>+Y2*RLug5Ht_|exx3on~LU6WzAyL9@eES z0V(})*jy|x(h3gb4@<&IM>ZX6di(lC(DN6Jj!n<;&q) zl((ab&eX-&i>sF-bXR(!rPFIfH^NUArjTiZ1bzPD6_>-S>d)}82KIbudRO;MxDCzT zp%ky5PSNg>Eof{Rb+%*Dcr2hjuYo=mUutMs3weC@K{PQdp{be)ThBX|Ej+2kylywo zgjfZIO`ZG(cb%>oDCp|i(BR681Qc4i z*_4mzbbqI&T2s}*n}&wW-3R;<+zFQ12AdxCqPDDJ?oV%SQo9-d)T$I|5CW3S`ZrEd zJrI{FwfV8rCm88xiMif%jxy5^UKxrkSDmCZW``-*McbQ5DXv> zMnqxp4Vm8S2IyVH%q4o_+e9m5?OcHBTv7^4e+Iab^ZGVsQ3B)yp|g|W6EKr4D@@+N zCwQ+H;NXws?NU@PVrH4TQZvoShB;4MBjiNvFIhNKkNhpBwNUdbYoX~Kbl;T?(qlNa zcPXYDq}W>1y^UT1#d81x(^PB{C$@%IrsUN>BeWG=DR%e$&HN+4dDg_&mCRlz$Uy8O zUw)Nc`)QM4w!8SEb}B3VL`6&RzRR%tbM}!Ng9HXYK79w6?`Ow2t6}mS5Tj2J$5-H+ zfpJKU7F>E}FVZp2o((b4Bvv>9AWBr2*3cN+_0tTwH?b>S#*4-c^o@^GzeP{Yw_O8) z*%#$?sKr`z;{>A1t53PsS98j|Xo>|v`uGp|Eqw-Q+ZW9%+nQs7p^%dQ$%9z?SVJU6 zvM4s5sBB0iU~-;j9$$Co(j#D}s@cGW&w#A0`~+Di^zCg?!lKe?BIFZ9wy5wr{$=Wu zy~d)Yq#W>j9kk`Yb62Irq6so{y3>8Tx5LY=Yo4KUPEBTC_a~m+@I-bCOx7$xg^E=jR3Hz{}o=F3?e|410Z$i6y(;MA`syFvgH+cNe&98UZ61h>IVoAtLs;$ zM50l?M-G6`%R$#PEA4moDypA>+hXPQ>6Q1r9zslxk2E*`Am7f`NPhWwcL;Hl>%>-9 z%UMp)ClG$!MGk*_%isz55ijx;2wnNDlvxlY)Tr!|1VRNd?@`jmW|HojS>U994Fo4U zjScPZs#VyY$s$)YmjKL!~oSNymWAm^ZAoGK@q|v;6m=P55%ognA|toL5>(Rl;$v zX+sA9@`a<4@BaYRiAK;%H&g(!POE1{9WF+L>?MJN5Yt|%?Wy-nCSzW)taJvTgry`6kuV7(o3PNynHh@DZZe3-aYWWz6T^wq-1Ze^U?n@0*fsHTY&OS_+OcUEAwp zzm&&WNDz(;ff>#FYHOy0E4nqM*FMDwf)XF$&z&bOn&(1$>!r1CTS{h5u9rye56t*wQiDdyvh{FEF9^$ZnT7q&>F%(R?U5!7H4 z$!}SZZ{Pf>+H~>!-Z*Y4*pl39C zG9dw;D1aWX5hViRSBtDUYOc>r5NU61J|;W8e1p-c1KI!O$gtYr`Ke%UTkdnwd|QK{ zW++*ZSrbkJr$SLb?qQnFAxZD&a2!+;)p;XrXD7P3TGv9Vnt9r|-@ipRUB zo|C(u$a@=JgQbnOmk{}V|AZ4Nt%kz~y^RhL1>hN!3}$@34rZas$y}_C=mYHx zm+I%pJH37!IxVLYEXA5M;Ysenw)@&i%Rf+#k7|NrKSbF)ARHj7Q974w z`ril}L)S;&xNgJW4(C*M*n2KvQRh&cMA{&1_=dGugZnz1{_ZL8c_79wZeILb@Y&4v z(Vkf^X3S++*cN1dkNkQDCtA9uDQWml6KItt^prFR-B(N|U9H?Onu_^XL^sRhW}Ai1 z`VAKEng4#evnnm5s-d0GoYw7Cf%cOc)=dV(H60on^*OWq?TgH~VVloG)K&5S-9px* zyGtbT(1|i~(k;lH4;M52TQfT5PR`jDZ+t(~(7oquwj?()!iW>u=YNHciWYyCB9AI; zkSjLxcjD#X9d`BHep-8ZdQ4f+^6kezOjB$!xd>D=SjrHhzYtoWT}gJXo|BrB>|I&p z4q7+QP(X4)8u@&fp#5W2H%daNV-ey+r$l@KkCJ$@DS=A6*h~FKl5ab}TGHhu_#j-5 z0^U4y@^902&l+MI`BwK$tWo_>rA02>0N!|53!2O;h5vB?Y=~6MJ_hP0fA9DAnc2_Q z14V?pDrob4+DsH?lcs&Q0Fn`vSg)ItXNM_hBkx8HH=6D4gJ%!j>ym>e)sL$lBJjRv z--Z?a-(m{44l?(JcWZ7|dUeY`k*O(3uIU6W>9nAOJz*@ZD?*4`MZ&$OE0bVtf~e$^ z(=!sF0(Yoo-LQ;aQt?KG|Eyc*q!}<#)3O??LEDLBdZw7hWC>DUdPa)AfoDIUbM^#H zSt*JyzChTl6mCoy3}7d==j|h9AF8P?>H^bjZH6b=-=qA!PRH%glF1uXv!P^K+oE(VAlb2 z-CgiJg}vCYCoRupEh!RYu@9vAeJm-Hq(s7NQN(mNplgccM#x6l&7$_Jmv?OaJ0O|p z*jVm9om88_!h?g3B<9dm(5B?Pxc`HV9t{WMNc6a=OgK{IBgf8C0<9vN1P_hb1a9hZ z%xE;pJ-_@>yqDs$1F2Ulz?dz1S}#Zd!#z@U0`7YWT zHw67kdKzL1*>uYj&{o=0Q22GARIrrHta?NT@uZ!b6vR36s-%h0m%|xLRcJF zzZLNzO$3w>2T2pDtPfgr017S)0Z6N^vDScp+QJoj-7_qk{J=iTMnfz{{+9#T z@fviM>LuzD{iLW^8ZBBKRYt6Zfb)`hF`vWHFKjX-Kti8tj+Fks1(+d_%6(y;;Ieoj zrVci3A1;Wt{o%D~5?#eu^S^04&Lfp@nt?}Y*WDUi@(GfFsv1xOf*xSf#BI`A79V!d)%f#w2eqg4EJ4=9>`b-2DcP z`2hJSKM>I9R|i0no=N+l#ZruEUu5%ajXA=lMOqK~XpgHC1>+0H?CzXn(owTq5n)Bw zw-kvDgqTMGJ`&rZ7NWjA3v0|-tt$hR??|LwY%(kdRG}Gg3#zPFP3`ow6UDqH zcKhL|@{Y0z;{f>1{?2iRX_-|aSF}g9Ye+%2GvsTk1^0Rn(>Ir=lI^H_Lti~u0_UlO zB!NufKIn%{(}QkooB?Odk=+&|l$XkJb7!u~C0>!VE|uw2!eW=D4bPMdR5Kj2US>WUvGaojD-ye3haiW1B*F6! zAz<98f{|SSZ(F*d2?pc1=W5S2CGxxsp4-CL2%#!fD4+R2X_tR&!Lkol3>MB=+j8={ z12*=x4kB8~TYmBkyp>3k*djP?OuYSDHE1MATuui-ZQWe0fn*8vk%#buh^yP%$Gjqh zc<#Q-IeT^ST`6E0C}eMIA{C6MOzZPF@RlyV%F*!%n0>rj(0PI>3{xLjQr-w+xABV+Iya_GByqOpo)BJ$(Z^`&E zv0H60TswlVvsSs$&Yyf4o!@!;gwbTx(8sZAQ_#_i##*xgB^l+S6{6`;-!Og{_bgf^ zyui#cz1+@sOZR5czhkP5S`Tv4NAgQ;AD&6OcfyA0)t=*DtE*k$G}7(iK!TFjy{a5wmkMZ1uIs4vRjhL z!cW$HLw&B`5v>xqGc9caB~r+;N}4?AE3a4WXN06+?!BzU?f?5ngz(NkrI0roy*|0N z4IyYZu?rvJsKVL}@mk#3K(%YC^5!T;>^|3nt4Z~SXQDQ{(Y2C>5-#@AYGd*e?BpxLG0 z*H4>ff88#f&9uQ2Gc_9;LX+2``qeXHX{it{PQ>N{K{|!4Ou`4iJ3As4FS&Sq)t@n7 zC<3McnHZV8r`ckfoB!egC@Z|Gc)nvDnP|-STVK@WW?p*kx0W}PZ>;UukUP8qy}k6| zgus>YKWw5^5}u^F?S`A+z{JdZn}8eJha~YYQn51Uz}p_eZAf|&0tFbSi&@*UloKh! zB2uIk&@sK8EU}ca{+h%>nFjt#NX_c6QfJzhxi6J?Ce3Au&^(Qhn9ku2 zl)n#n;`_-XcBY+2U#WGKH?vnPUu8N>LNNgGD)pmv_7N?$& z_zTDAM^TGrSzfg50n%!&IE0ah50`f@QlK<_&wW!^Pc5OvX;X-b`*2`x8t7K0Y1lP( zkI?7{DpCjQeWiC&1)s>1=uAJArHLw)MyP0W`wu|TmUvKR*BNUlMM#&cy$rm6puhvd zvK341qNB|E>mTNqZFIvYAtO^^ofX3#+-;jn@P7?TCPjqTq?ffo31g8uM(1hOs=C~m z;OrK8cxO{*z4ur1l^(uth!5D&WQ}tP{(hmI^tXZpyI)8Ymi0X2br{mNKKl8&OYn@n zkxtWS4XA7Xl-dm9+ftq2r!}nY$9mDT9APoU$yA2xLj=qAS~*5TOH}xfn_l`os!A@5 z2)nSMUe6(w&mlgyMm3y2j&BJWqW6t^!M=cVB%Qd3?MFVK&kuaWG|NcwyW@NJhLExy z7L5(44DYT~kWgY2R``l&CnU%~Z}dwSm>3048ZBY>?D(n(owd!;_vifzm=3P0l218& znj-P~d?%sQ>$rJ!{uqfX->o#{5x}P=Q`jTq4TKfYS9Gm3h2+p-fuUmk=3L+9!x>hhdt>&5DOYD zD-h?rS!nb#L=ySB54?C@nKb(#`!grLZv-cA_Ke)ysGlzyw9#`nVV!-Xt;^Hz(xFtB zH4^LiY?kZXsc2DA{NpyVK@w1Xnmtc+WymE-AL<-!f|kYKX{nkkvOBhA#J>j-c+Wck zX}z2p@z%q6E%3UpZ#;Sdd5)j4l0R(`k54JrQ_shXL}8O+;?q|@cD1I+7#%Nhi|qQS zV6-?@B3#z%Mxkk_938~PbVUPpNO%!&jXh#am`ZmzTt_lFQrYJ&gVM!N%oki@8BX2F zR9tb8xO9C=FzV>ve1wiRyK(EsP_b*f4!Zt&*(e1a!B5VVHj24W#Z_^Um)F(s9yK;y!Q`C_L8E_gOS$pP ztZP$CmC*M>==4SZt8y=0Nm16R5duXH(ZO4uN#0$FE^4*K%e?< z&!M8cCXK~=VW@H@QEQ>_5Eqs&($KU@$>ElX{Jxjk5&6s=wY^bvt>ak>kGW$QAGNVaF1SsSecGv+OLRe&QKcnRlez z6EZ~2ioX4o^0dN>XhTE8iRhtRu*~mlfHJ;B4tLOse@5=&4*^0HGDobf%KFsO+imh} zQ;Qp1wXbU&+p3&XW+H9y-=dHn|JO*-bKrbWUV8+KsEmE=J3+7#g@f8@Y0f+LP>Q&? z8`Id+r#(?NNxCkFKO`nLh%Jo(_}&Oti)Rb3cD*A~3n{r1HrC^gu2#RfBDgOTGI}}c zPCWkDrG<`OytPms(Ut@Zad|AMHAObmqdbw6Jd8IID&JKXov>&4jqW`{+TeCf?jljn*vJX^?OHdDw)l&BC>&|V#8(Z;rS&mP)7&M9bX#JV>!b572Ged}xP zUZJp%B0g-abOJoopXV?QWn<;UkAS3rGd>0AG$V+OSFs{a_0g|GC(GrCA1>YGLN~Ts zoHtK5#9VIzMsPjjagiIdXPCkRT{0z;^H@V{4$A}z4qw_pKx&pZ+wtpJMAtBVM7{+m zbxl=QUxqRqia1)<@yn$pVTCdw& z)DH-6F5SA@BPxfpGtwQ<`XIS_M5gLwHA4aceZRXa3D?ImIe(A3EZyZPX%S@d z9LT?`1ND0=CYL7i;Yn~1GUvT)gARmJI^auDb0}vq<{t933`?Dhkw*EvlGY%Vrov^K z1T+Xlq*`k6Z$8Axy#8EQ2hMSAR5Ig8StBMypvKB5XTxC_MgrK?yd-Nof+EIPFH@Dy4eRIj>lwZBP+l!~`>X<|l- zq2{%{Rk*?&Hd5vVkbuxG?CvF945(@ooZZKp^?5u9 z%?2&gc6U8}+2!7veeG4Gl4Ak_z&gau^=oT<3JX5?L4x8Lq712;H3PV%tUy-?8IdFJ z4Uz~bBw}tnPTxbX_FSYy9rQz?K#y0cogi9|G3=M8WA-6vF{@|@UnBNvPyr+hNw!iF zRaT+@jWSoi*TSDHK7`=HZ1BEqZs%Zr*C6g&M%;7@&$QV5K_g>>Oe=^;2%HI6m=*z! z1OQ7KUb$97x=l?#FX@k|V=6H{G;H(BY*~rmM6sx7KPxzfiA0@_6%I5@kg(+5OQU+6-FvXXbD=keB}~r?%_ETFkHOAqBt*pQXP^LeUO* zx2AaR;g29YKmVC`oa`U$P6r9FH9@{7Oo0J?#F}K?=DB7qnN>){U>{ssQ6nMom2MYq z_5T+l{Q8Wqk5jIhxR_#VKzp`HCen))+#*@psMI9-ZW41((Gb63xG|l`i3TQ+c}q7j z_*`2*C-x>mWb27R#obMc(d;Rd@{z)JYZdYOv2h*3o^enqrYJ40dJUFNucc|*nj$nS zGoZTUoT$e3QYt=^eFd~LuYJ3q{-E20Pxy87h#7gsm{SKO0i!+mex)>+~eMwUQ_=)Zx|G5*z(I%`wP{q)JOwxQ38dRD7|@(O-A56w7Y; znY&`1Fny6PGydEU70uc7oSN*GR&JlGYUr(GgNqeM*;X$tLHxD4f-&7Rh8WK?9tW6P z4|eay>)y~;O@d-1wKSAQ*aR=t7WIGyN;G8cW;Ng~4qMB~(A}I9^^j<+($^(xOVv|? z({efb9EH$%&?O~F)biPY{A$LfdcmQEZUSmse6D9iiAbt$QuU*#ZH1rEKd$s#5gw~m zw>2dnr|#c?TTDFl`&(9MW$Bxba127TD3)i}-mqq$2xeR!qE7@eZIECPD^CzKipms! zVl}fus|daHttV&T__TIj*6&3KKDp=%(P9wVQ10BRCQ;;se5Gi<=2=hab({Vb5S1*u zMV^CumY@bblGEwb+KEEA9wiV+RHC|FQPw#(dSe_e_c4$+1e9|J{=u{)%@~zOE{+utG0y1@ZgA)*RlD0LhyRN~yMQmHu_+<2hB(P%^G15cnI5gwfMVP%!Qx}|WbAXc zWg~7%JD~`eU-`$t`Msd3B{>F}*8jtAS@3bdiB{(DYgTn3(pE(DE2Eh&TXhm zN97&w;4^itg3OYhj=r6^WV7W71bwS@CX_wiw^DYzWOn}1z4uVyc|#?ZwRjtP13y(x z5!^3hsK2fT*LPAfXaW{3e7+)2_y0O()O`obvPsXv7@!^vy~9u|wk})#j$*Zr*+(fm z!&UUu_hMMjAPS;=fpVnoSvOca1H)=Fjaz^nAvVlTK@{IUWA=8S)2=+}*p2}bYf$Pt zL_Jw;0VSGvwespR=3mQKC}G2X+^?OriRGt5w0<7NFf=m;{7N7lBnIV4G7`c91BYN| zRuD=v)>cuC?ml@8>+x&Jl)H>pna{(AYU?j z!T;xDJlrun(D!71+6F(0113Mv4%G|tZF%bf=X$fz%UqEl-lYLA?4wz4&TTwd?lR%iC5elPH7a2x=spWt$tal(HX3{nLL6 zA>ZS1-DxGhkU9B(D4ltkUP-N)RQD<}UMk;_Rsu+M)>%o|b1Aeu<-`TYFz*dV%}VqJ zw?HmEg}n3%6|Qz%gGse%)4=#8c_Pok)a$R=SOB$ik!6JjZp4BrQ7|>}&LWG2_*nu> zgl8Z+R)paEGA{XW47a8`dVr+PMqELrXD4Kw;G+{b5kblY-PZB1j?-XN(!St0T+jT3 z&RxsEcxo?fho)aXj_PNZ>!VtCcdTp!z$AnM|J@ykIK+)1Bi7-ZTTWRxdyDx$5z%F? zm^l;H$b4)iY9eB&Jb6=e8dN2@eB&YWZZ|+EpB?L58T_9}8-fw7>rl_BHpp%doAVA= z|7LbiHckNVK&4>9&m$X-O>^1HJt68zuH1hyKcJe`5)0^S`@5adfrr^Ta@*1!&D#w- z9T;8gq}B8ED)Si$Zl|H^`@1VOSzYmDO-Q~*nvTeCz6cJXyX?j%I1#u<_PhAEg}E7d zLJcw3kymMs1Z_J^EN~R<0jfrQ=+3_i5`EU~gp2FIIX%xWt*&`1qJd%XwflP$J;$`L zv$USm=tZYT?zRmjN&oYJBjS&1S{lzJ-Y<$Oq!4M`MeSR9oG5a)9VE^$6Uulp_(8V= z0`8Fm$M*Z$peO(fg}V6a^NGemH7Bj?MhDXG_GRFdk9|D#v$KK%T6n5u#^&3$VLG9msn*VG9BBr+APS*;BBdqU$l%0SUDOQJm5dJyL5b?qrifu z2r|c6Th}8AJxkzc5-%Oc;|;}aKsP=+eY3W^6~OpenjO&hG5`oNDmfmg z#gfrg)kLc2lUgKSVlU+eF3~zALF4v>Gc$G6JQFYQ?aga)f#$_LVOv{2OpTJsyBonO zvYl}x_qd4nr&U zx0Q!2f8BOShpv~qx7-rW+?G9V>TpqxqcA-Jiv)EL#^1Sr)|3i#TAv#F0lHd5`2NP! zEmme-q!5>g|=z*lTaaK1QRTjqy|pgJ%2jciNFb)(8N0ko?ZL z19xw92QYx#TlqFsCwNB|vt6{j$<1q?YGUnTp$#1=YF9f=f_nEWA0vlHrxP{M=DwW! zM-r?t`|nxq*<9jyj5R4%r(gmbZU7X&dEIMJO9jo2_Z85phH#Cv@h;EurMK#*RspPoaI6bZL`?-{%(P09SkA%l zrv%XWgpoEYo+i zrko*GlNH7V|1O8hidc4Or`M@DNNMzQ-445&OV`R@cHk29yzHV^p^t)mV^lEk3$|BC zbWIV;2*Y;Jsm!ay`pQLJs>;^3b7eNjY5;cu$zQDkk10k;Y?X#3wflB5Dp59&6*n-Q z5SM@z3}8}LH70Si!Uyypi)fuvRa@-=7i@;A}2UfXU+XoKCQ zSLVG{Co9jBixF*Jw*l9)JNd=-k-ikD<7WP!JIgZfQfx<>RlP&|3j&pT!V3*wV*nEE zZtvlLy{^hO6>+?DJ{62>P~2lIaeWRT1UWKIL1LUIlC*iOk|S=_78X+PlHct+i+BJU z*S}@{!xW9r*`#{p9!BUoBy@^#t3=$D$*Fx?7TcOS_L&yi@QIiGeu7 z*qGQ;@7%>H8OJ~g=s$9{t%HR+XK=d70$^Mj9GF7iCCb82!UNOt@-)f3LpE+;kO5fB z1^;Kgkf(mTz;npTW!-`D=n?O|#7tQMzZBnv+dVu*rb&>oUc#Z9j-)9h@>T$$)>Cdx znUgkpbUv3#LhVSE1HRxJSG@L$M}rWBWbC3Wqto#FQPAsmXvNd|2rvF-5x##`IJ)lL zj2P{w(bp2Fimw}YuB(Gd$wyTvK+vUHdj;KpL>XkVEJ9Az2KVu~D%rw`|1xv!HGO=4 zN+P-D-ZZ$~&N|$;Gqk{WuU+wQ^@)ALwi)mafDT+-f$Xd7eU2@IF)o^I;g4nT^3(GN z33nNtWZh||9=x%xfv79ExCcKPJIfO6M~&tYFq6_NX3qZ3B!;&69?F4FlK5jyE_%7I zSBJexJ1{r~y|DNHM*wj7thyeA%cOFv6^H__0;$hgU0KTN$$Wlbl2xy0_gdr@JA3)E zU#^~MHRwnEMoqD89ZDTP4^hMo+AgvRu2%I2eaj}{bT6wXLBfZ)Ih@Ek!!dT$D6?i~ z=7nXm;n!jVE6(&0tg8GfO3we(FfpIW_vF(T*!`|Su7>G8qMqY_sxP3T}kMo zTdqK@-2b;u)m1Nbty7D#u`*xAcR-cGa?$aJ{Br%zfgLt_iP?4#Tws^N=%lL!`Q)*=C%%!qm(H?x4!$)qO%1=>%dYNf@F&9} z9QYgQ!@`$ZQYFqh+xf>6V@LRl7pC6)k?3qBaAmBjS|*vIa}T;FE$Lf*As|_P61n1! zc57&2^b>7CFujHz>l0O$DSWk+E9U!TSA;&TjLR@#2y)ACOJxvxCvdS`rZ?}!-4^bdItVvH-P&g7w;B9I*tw&lA+Fwa zwuw0c2a03aVA}$Gki<-mZNw&JOs(Yk4!xRLqM&<@7CDWjegiK^L=5IsHpTzwQ5!D@ zvLMMnBOl;m3tCZY^b2Zr!9Xp?Nofb51bwzFlNJ)|flU+wHq&I%rDP%R4m)WsyJK}YR zP#QfsTZFduRs4z=6xEfmRca~K&{diB;#w#Snj|!*GbQxtl?=vwOk&A@I8oEh@Jaxu zQcNNw1m^}(x3kJn@qrbqPz43LVl1>)moIy*Qm6vM4#*%lpBy!zu$jiKo7tyrv@7P` zF}a>(2^D#$*lbKaitbo4exe`{HN=*RIww+EHZDIz7~m7mHMVijc`0_UG+BBlIbXG(dRC&F zCm)JG$@)2(v%J<;tNGsx6gTG6?qw{h4lYph}ruzy_Fm81rtY|~Gn zh$5VqMsN2V<%_aD>LXiMp7?3R#FqVUlEx7ZK=T-L=BoxL;boHAMZ<;I=^auzktc*x zw;TknejAR3(Sy(B*Q}L996vfmns1c0p1sz`oBGp$5Bq}I3D{RLhIVD~IjQ6ib|QuI z0#&IBJTHwrXp;5B)orXENXp_T-$PRB;v4>zV&1j?I1D#t|32WvOw=Fv7sUH_26AiL z%ydNaJW#_H{p0+#X8xOx>=}t(sLrjl#k{8?_A)mGy#&Zm6$uC$>axh`E6RGSErZ8018{m`Kmxy~M+Y2$y2 zY2QjE)(hmwSJ;ru>=SIe>d8SqeZW95dyI_o2G!2@D72&%o$?ioMyWDc(xG68>+u_> zNLGxy9|Pd?1LDGL1A4O2ng9R;pGxqw0+^7?oiQbcbxhw^AnNSDD*r&PgYjN|^d; zhg0)=0My^No=2;&FNQ$|yvj`#%J!x0H zk9iqd*?Bg?$Js|r8o|aW&#VHnm6r4!TVG!UjiwVHABKwDKXy#mRybc1rLTHivX?^Sbh%f!%V1Rze)S z3Fw50_YqB`%;pUxrGLMw?BZXsTBb*zafjxMUrcahXTc_VF2041l0|}wA6JxPD(4qd z6}4U(Dcmht%el)3K06|x6i82RoUb*p2i@ai&soW#%3X83%r0TD^rvx$n#|vamv!wv z(Ja3P>oAAmf$5vQIi2;~3<5bUdRxm&T+h!&KjMr?{0Ezi`;aIJQMRPg86LC^szKbefmWZky?v#!7N*a`hpv*_ND&4#TAtEve*Z&J>#n&BZKDnn>Cab{ zhmDl+WUPgxhsL?&2_?|Os75FcX0+Q>vcc%p(##?1SKEjh=}Z?{NwCTxR3e5qpf5D* zo%scB9}6w@s+Tn#!@DhBmG`mMOvGHi#O!(re|LFpQJGV}fjjVPdR}u{a0Ui}eQMeS zcu*LYj4t=OX6kQKT0s4I@JdHW9F7hr3VUvPg|Iy}@`(8&zm~4Nj$rbeVK9Q;(=d+=UZYtgRDO`bd7(m1g9r%ZwT(?_r2a849P6u_#2VSXg(9JB#G zz^mwv@87c4MApLRoxhQ$lUs~2Xo5_d=ZT-o9JrL8(zc-!snIcr^%}DdqbE=f*FK>= zU${E<1{wlBtpTpH3!mdPX@2r>rN4A?ZH*K55;=i&=Cm9TrSR?&{)2#g-etJK)UE zGjB_Q%2n(qz03J?ZQZ5|*iN?`HqwVA`k%*!W4{rcLA{%Rd< z78f8XZtetCNlx>BwlkH$UK6+8cN69~Y&576+c|qp43ZuV4ZK%X{j#kQii@=;-HQep z<~|}SY6N<2vwgZrTcOgf05gUs3h}ZcYS%9t(wPG8H0TsA1fx>}K&y~rs88cx1Y@a1md*C2s9mj7NzUBKSQ$)NKrfR)jZMlWnfl60hT9w@G?G6d%>O^ z$|+jsCCH7Twv^IbO7jx`%vG@83bUC{Pt+@wRo2-&=48VIb0ok^g>U0s={WfRsJ0905Lr$7K|%=I-6=;&z|vZPs`LC5-@;c@e&12(di z_n4TM@JHc0eg_fCw^lEL2k2=B8njZ24ugV^#({2|rFLop!I}iH1Fpn7j_fVEyd9=L zrMh!|@qoBZR(1{+1=z+I=m4E*u{y(x5c)VLYH3(0Po|Ywp>jX_A~3Pr;W zNde(72y-)Y=IcN<8mZR4Q>$*_MTSFk#BC410~!4jUHe7R$6-L}hHYR&@8P_gpfaKV zgkue}YmvFClC8Y8ke%$Uo{jXjeJ?Rg*)8*1)w|Upt(dJ$c-7sh<{W68w(Wp~)AApx z{!>Kt&1)VRr=MOEpD^KRBo;V;4aAKwOj+3*+0d;U*>L%mRT(`}z5SlybP$9(cVA}+ zvzbETFiL|fKD)0-aan4uauoVX1}sSrYD$WW z3K&t0z7~R@KH)7PaN3tcb(7Z3HeC<}x6IbB8^BIN&7-Yn7eo(}lDQhI>bj&y@cGbcYT0z=QaLx-;?G zOtXTMlt)<}479>0?(>3(%)!Qd=kE@``q4MRn8Qn}`TzSe`q-^8p1&swC$b9LywYM? z+Y=q#Z(^qJ1&H)%o>c?1h%?9N&ey%9-k{Ma05rrQNJTKYT39!q(+H+)iTv%$$)Y|8(~f<7g#C2qmcQ_*M~S_SC&3mey>svK`B%Bn>^W+W*ysyR zi>?j&7YJOGx6vp&nJF%pprJwF1d@22)dKmK>bBEUN9P6w0rz9alJ1K}{&<>}Zw47M zcWSO}gNvd*@C&D}giO^q=V${#+?=;%4m~yR-lcHwhWxglf?gSr1>a}aVUcak`kKDf zPW{fAX?HNY-8w%rR7$s9*Kh@>I9?sBYeSiInl2SXfEADN?KOc^5v9{(ZRWe*R!cz~ zQ#Pd3;~WLgV1U$I(t2GCv~U%BhD*rovMlYso2`{pa)3}Aj&5#1VuvGbyfaOdt@<+L z5!+nU>A@bfD8CqQkarus^FQ5g2KYGr6_WZdvu_*6az+iwDiQ}_CM~^71=>fXWw~Rj zCrqLxF${W7z`3NH0LNLKTuC=E0jGw1X~l6di(eXiUh%%bo(F*o?j6>r^!dJRuILwL z!!@Ueqx^p1ECuE%*t)x|9OF&wkP#eA!E#dDLHI~HT>6N+hZgZKMPHX-l}Ek_pv2Sl zC-Vsj#$uMdUISWjKl9H-hW4;r*IZ(ch4<;m^d;Eb!WEE~h0!B@iI>ry!h)uAr1Q|t z^5Q*_&zARKm-U0j)V!mcR5{K`&-FglQJzi+;2JOZa(CrPSu|SHr4N&|7T?z#$jj!p zc7rZAL}|CcGC3jUC>_oj^<@Q98dy=W#pC=$l+8&MKx-953S{Hq3&_shOq&g%OdMQp z4r7gxRkoc_+FgW4Oz7FLEyR?J9B1mx?@`7MY8);4~!wFJP81;p#l`Y8f1JBWOWAROv9U$iYo zN#yws)us~GGbjEC^^OO>@R(8q`8fBEqshNckDl5Lrzl&TuDK~~h0&N^!CF}laO zVycU>PJY6eMSVjWmD@QH;JWa9sVzLhE(ze}v*#bPc(>aDPpk8TmWUMZ;di}cS)Dq8 ztZ5js7sL-8-oVfXUmped8LSGKH{4xYsYUhrFfjHIq^rR;BeImzygk>!7&3xCYB;Zo z^l!Pp%muMrcR5+Ry`XY0Yy_Eye`F}oDCuD|Ws8^tN3d^+qe#<6$)3SW6dZOoP2x}R zeP~?i$=VMRPjv7EokI|#_&oiAS{X@c@`@&HvLtBy_e;HmxLF8&Pxs{{;A7AIcy_&* z+CnBd;G}y9GBEW0L$QPjQE^D>g>n^vNdHur#@qzl*>2%apvo~#6IF~Ls$FU zXQUp$G2x~`eK632vK44R>M;$+<+^g^Xtc8*CqEa|2w-Vn!U78`+K-gQ$n9s7+wU`F zo~yAhf0_wRrP?I#^Y&8<2-MIKO2!0UxYl}ipU4bg{~j7FeM8eYh9OZQ?y4Xd$vi&U zJjq}^-#_*VVvI6b#e_)nJ^GN{C1YOPa(MVxd9`;FZ4 zdf90sqvIldKlF{=^76rbg|kY}EyAdMugXm*8Rg(57NT!3fSVv0E}9dI6Mr@=yFDeD1?@zO4b5;bcT}Z_7vMG`M6YBt_dacc zR0Bmbb)w})q0GmTCYtgRUozCG$-QN&TMl?zkpJUT4|zW;C$qnk$2dSKfB4PUTM{06 zEixtN0^oOjZc!m;6nZBeL%P%E(Iyhb_N?Az;6TUIWN5;vQv%5GO z)*f0-6we*{X2N~2IRm!>B#md2ma41}zR6j^drt-Z|1+d!azTq{&D?d}M}OWZ`CxK{ z#MvQ-*Z}zRcQM?lO5PCJ6XQBp@5D4MGLKA;=4x=&&9kedIVAY4sUv5w6dp?CdHOmq zx^-JilodNYc;x3|H zAOHsQjnaf&?pJ2uSC_d^Tzi#+nZ8Ytp&?{KKprcqPyuChQ>DHa&MXx)x7Hpgdy3YT z2l8kjUkM;Mv}8fyyIbEI#c#4Fnfo=bE6bVP+_K zOkMZ;)pfoPS~6mk!M1{bN0GnbTmaT{@2U4Qr=rpq1&wixIkEKj00LDJ(QcfHrw>SB zcAU_Zhm!dylLoo&)7_=KMmPDZ2G?kmx5a){_;l9K3}sbZzAcN)92VtpI}>Ax$(;|)LAS0Z9{*@oJ>aLPf01Bo0Uw;0}>+K z=|OjjLSN3wN23&HCgCNv<9YXD%=g@BAyVN?{)RYWjwk^1kiV}^ad18Qsh-z|KSMf1 zNbXPN{{6OHiY8W-+(^06|a zsliVR&R?xHCG@s27R0e5nL4*uK&y+__~hKOlvR6~u5g+fN`iX9cLsm4GssdoqT#sN zSH6kJ(&Zzhe=+pP{FnSJTTB&Cu^x_bWBt(PTfUC5?2G$2~rCDO~0 z-sFENWhT&Qm9_NF9SSxd{}O4l0QR28?-+>tSlZ*R;2N$(pc{0bO4xqHEbUvJOOZL) z@|4~Rmj^h6SQLmY))xtpDz+urJ*6hkRkGfSHn{mWHMI=Hz~c(6rQWzOIU27~lELMR zzw<0)IxrZQFQ_L6XAWpEVneb7c#8Q;-amhAXacY^yg(diCsu27upN9)V49fdgp!71 zax>f$8zW_j(vi(=B&v$!edne_oaS9A8a}J52uItS!j2+R4ti~reY+%9OCK` zu0D2M9KSm3ROu;Uz$>mMkl)K~9{Lar^iCsx(%469Le729K zm#KXoxY=)+DEVKoPikN+*$g?I%2Qa5(3DAbN>E)aydw8KfWWk>yH zA@AD0TEuPhC3if+Mgs-i&$AxDN-%kJ$OzgG6&|Js;Tw+}cAZ-beK!7FESia=90YI8 z{woA9k`<*_q7a4;LkRkWfzm9*MkbyOV$mBD1~#D0|IDn<#SGYvd8t!m8q?IfP@|2s zeox`OOuH=E>sB6%wlRbi_z{{@2K?OQYx7b{trhcosoW-xDsS@e9{EzSnPSGppda4+YB+tQAd z#n4QNWgzm@W`_10JY}zFE=IiB1t4f=K*5?jx2uMc(D_Lv;d#>QnC$fMm8x5^8K{OzeLw8H;amz1#2JxGNy3rR42G&z4!N_aQ_>4m2P1?wojya$(M}yx;u((hLtzDrYaG>ut_q&f zF)FN+V-NBlF{~`deb!E^osFjwV?yE!!Q6!OH<5kl1-}avT22$^UmW_D*bveN=Lo5> zS^1N4U%EE*yeAk-;?b4ScPzaEe4a58GVkY@9(+Xb7yTL(daNU8nzbYN73?Tl3Pv}m z+p9Eri^U4>v191C7}Bf>YTxtNIzm;o4Q5POfA@IloU`@7j$$ZJHJe=bI6#H9A3+b+ zj=a6M+;YNM$Otlv7_xkoxpNR$Mj=ujR|mg4tl8>(pnMF#eR#^xncPZheL?qI2(G;` z&ye~?joW9f>2eM})cE&j&LPm(H|k{M+?{cTuK6XB42B#bWnF6*o!WuQ1%hY(z)6V* zUo-}$-=)+==}t}$N7D9Tsh+%cvM^|(bz26V6C^Y3N3&5uV@UI&#gyc2`Yw}R6q?S9 ztQPx#_RLA6Ww_tshuE<8ff7DEc;mCeP@iM*g5O?57Y9wHtv1+lV*+qRL5WJo8*lFQ z2E?f;-L=puni*D=^5(@>9>%|WY^;sNOo==Xx&mE?DG*@a&BaQKgs@+V!9ZR=Xp8`C zjX4}ul?B_&M}3uJ=NAHi@&BC<$g8HH`@kLpJC7&Js#ZpKbnoMv@VL60;Z`VTq}@kZta*0Y5NATDHlIsHX(TSBiUI>frMu2u}1pRj=?2EL8 zZQ#+{?Bv3DqTc>A&Wbm0>LBgip{dFCi7lM?+RXRj%IIvczX(_6q_h9H2!4Wy3=Zqmzhl?v;;w|&any?0xewC>5iI}IkX-4;iL;APA|QmySR zOgqCtICah6AFXOMBRX2dDW%jQM$8DaqFbZYms==3zd08)eJJQ=-8je(k1AXYL+7E-1#xPMFT>;3v;2^wr?ye-F~fnpN@N+7RGZ)!G@8mgsDN_!rCV zK2uKEvxTcJxY5ooPr=1it-KaSiZ~^xPPyF30wj+bF7a=wQu~yv8@8^HW~-&)lJh;b?64h%u-^63S}942PbP%?9^cY&QxFOOq7Z56k2NLndzsWf{O_E5 znBwa;D~wN>o4iDBX++*Z-t@c@rj8L*53DuO{g816Bd66(t*}qk{(uwn_#K>fg6*Hl z*Q_kyMHfSo_;qw%?^c>_-_ngZq-M=LV;7LpSPE0d|%gwAC)r2$J z=f>Dr?S-N+CbluaFYpfWCE(Kx8eQtQ)fC#u58ZNIPz-u-uWJ2ixFdOUu2u%#{7Q*N zIW%VJRh@}Gds>_Q;rm6praW+XLG+eDV`txfCU<~+=BX*|oY=O8tD%Fr`soWgRGYUO zMwojSniIT|yH8Vq}ydKR6(MRH$shiEa4A{Bc z7krneKrKM*+s?Kf!PO2HLl1*k3-F3pUFGUu|4PJw%*rc)@oNUo~Z+(|v;0@Q41FZ5I?qmx!eL8JX!^J!j7yW+&^zq2w z{RalXoKSSmLVj|ndA$SaQ>uQEY<)N5Bwvbpl9;QeRAiH1ihTOC==6WQG?Nwqu9uBO zoY>9g#Fa8CUXm1|{;&b;2F(j**JQ8|G_v(l5k?EL9qXvw$Q(^Z7IQKqIyui))ixj= z=L*8VTs5w)eAWm*26v0+7>&inB46MV3%**=406Ta8v4<)__Z}*DQdI; z)Z-1DDimF%$#_KIddfB!lwlt!lbWGo#~W=P-Hgzr50J&iT#h2-Gd`;_=fN^3BUa6z zN(mvp{=KajOtmkUdtSn{mv7F%ssSe-cw2ht5X0yUv{%WuY4iv1X@uttw!KLN9HPlH z{}30;T8D>UsMOkMTHt{N{>QEwF-vWw)!5mZ7`5z3pkp^zs#az57@g^RAah#)-#2hO z^3Gh+t?h^k;Lkb3&oe!>7t;!kn4O_m%AZHZMO6Qhlfn#c= z3Q6e655>>1H+t48s?EzAX-1CVqMY&kB}cX#RO)_a%L+gVnr(J?oZy3ZgP#03{ozbW zk*2)2fZ8cppKNaY~5_ZNcD?8C&}E{v}}5HG-b$ z#*<=s`0(*)BZ8Mqf?liLm_d`{TPl&Y*mx<`q(gFj!MO#NGcmP_Tf!P&0SQRklUj)s$SN()AC9HJv8mwy zMH3hi{X@Hyk-b+IB7`aga7T@ceYKAV3k}?ozsIZ-&X~NS;e)8V1_u-v{uMIrQ5dqP zVtGlas1U;AgZc_{_MEN;x4?8jQqn5DTKtZmm>dvaOg-ZiJ? zYT0T@fhwSUz=|^l&RBC28^$s9#o+W16EQTVoic);Ip8UytG16Dh>fG;soF>!cp_E+ zo=9!pEK$(pkE!Q4FELP~r)(CzwMeCYtJ$02Qf9)5az#R0<2&45f7^w690c# zliabYyh;HRSJ}g`JY1rE zY}Xz#kg({t>vQyem;~;rmEkwOEY}xk3kF+m$xGFWE@1-JH+I0cV{cU1Adz_U zPx(ccDX5mO|XD7$|zb4k_=nSmb2{B_~^@G7HLa8J%66(eAFlTXcb z+@uQtvR{aKdvzub7wWC)&prfRBo6>_jzoKgz&QU8%*Atfez6by_x(9xCuPbEMj!8v z=Yc-xeP1j<;gjv}JPWQRf#d%)9Ru8Q$oiowoWXV#xR0@`o-2rAb^exX0m`JaXj_yG z)}3Tip@Ag4Jio->RoW6bY-nb0E;*v0E@vRtO_MRiG0)M4yxf5h+ydCf-6U1SjQs2~ z0PQaBswniySyC)?N^A%LTG~<6c{i{Lx%ml-UfJun1qT^+&wLTsQ;{Q2GgVkBZfkdO zIA5nqgm+p0lTZjiuoUaMYd3%*_l_1boAaLxznSsW&PxG?y%0uG=LI9_?F0+*)Oo!g z1_5YmH)St~7y3;Al-Vi4%Vp1DrEL!6!z)eGi}`Ct@e`vgBDoMP7^NhhS8l6Zf3{*)Da-M)La>EQHEH_4ZMfRbM$>T z5=2l1;w9OE=45q#Q4URAVx0Fg{RzrUmOQV?Q&MgxZH^8YP48}mFHiElkO^8E%t;A> zRLMkD#BQ|~f;qQ*Ff}+JtfeHMHhMMJJAcR3zLlpC-B99o=FL)glP`O_cF%+;c&c_U za%CEVnKCMT)^T2B=7&}D(LOhpu6YnJGv$^_7n8f@{MM)AmaUU|6jMD(7<5@`^do7o zTi%BY9MGb=Edi0)lg}9EV#%8niS%-=$m&%jJT6FG_@P?bN-#_UQS9DVvK{oh z$3$p&o>ZIz87mMZq~!3Ka>+1{#!gKm*HjIGAp>D|bNtbML!lv3PhQl8x*sFQm(4`p z)wYT6Od=?~jypz0U<6OFQ2&;tVv}%{^sjJJ{Aa4Y{f_D*(xEdyp7}FGw5dIP;QkFy z=!Cz3E2>35G^`@_m|cYpR*L|N>y4h?lE$690~%6ZW3NTQR=}YLB5K2Z_D*+arY<;q zu@4I%c_^smzj?JDnIBAD;H2`S1(sT+?L4m8yT zW4((a^3s9pe$mpq|9r~)Hp!pC&{eZz*+d-&(xLv-$;rpl zIO&CEUPUft$;jU{J$Q{M65lzH3r(Ud#jeQ1*pK`oHR9Zz9d}=qWbfKUc>~Wa$6u=7}ihq2AA4jK0 zQg}caYA^rLO-i%n!jS_>#gFvsAe+#p2U%g5lGTsF=w4#(@5=`vl2*7{8jcU7wkOXd z{<5f9vdE(3o#d4|)E*g5uh`rqw

L42HrPrB4H!$;&kHv_0o{QvwD@v@<*C9~F{T_1S#A>=TP&Z3Fj}-II>#0}6 z8bR#|Qr;7{-*KC_S7c5~>Jn9F%-o8IQsYTWG%>P+b;VAxBoyt+`~`Y^Kd%YYFbVAsa( zDn6NvOUlpgGRk_95viislr|H=1>q{PWmMnFa(WtnGNvj~Bh(dE-0qjD1FFkYQ|y1L3O_+hgawz2)h zT~8Gqi}T*k-$lw%I(T~?`izGu6t5?mE?9F%E{wTSEm4K+Xoz4 zcA0reoY=CxW;+@D54gtQ7}xDsOy-#X=0vXv9&4uGk!HB6689`8OM+2TI&_Jt*arD? z;^;#amJ6Z;r_IBhNar{#nU#X2(-JG}JPVSQ)6Om^a;AYO^Kjr6QD-l~mkj;fGx&aq z+kv7vinsL%OvFw#+N7`tM7sznP7vbd|Fedb36t+$e<)9GDxF0r>Y1C55ufZ%=iRLk zHnuZODw|8#;J^)%H#?@7yx$%>*Wy)2EIzCq$U-~0ZC8Dms=`l}b1-^VAu3>QN^|-F z7uXl9eAUn|qS3PCuhh^V?X^bT_dhwVreZb{Safv@!1Gsi4bg`Nf}`@^IfhrO@6Vvn zrL2$tLP)^uw$*G2LJ2bdqG|R$X`W-bepaazZkqa|h7E@$v`hA+OeMGju&0fePB}8# zFSq&-0QDrp*8N(Af7XoK@qf^{WQ3F6tV`Xd8a1Wp4ZGbfIa*8J)rETmq|N!)Uep^; zSyoJX->MVFiQzEN@KZTDCzq#4`Quv+t(eon^ulVQ*%H@6-;n#|L`inFHoBA#s8X6t z#=_e)je=j8fYxINF;mvb?}}iNvK|Yyns!BX7o)l$L2aWcZ;A=^ZjkXS9mN-!F7^(B z8QPwW#8_Ba0OV_$&F~=)TO2vbMQu8y#Vguyg4bAy)#EeT0-^SR`3#QeH!Y}*_VVBo`QM!uiD7d-><-a-(?$!N3s zMr~I4LRIS+eG07tBv!57s7%sn=r}k4Q!bE$xqK|7s$FI?3>=~}U$CcxU))pGPo6O+ zM-5197}dzAJ7_c-?!=fUMM^-^&-mj|pQU3*4`k}#DfBU%n8^}D=k5|6iHljsCV73M zq7z{$h;SO3Y7N(NT0d^SY04f7`0y99hkb`g;9T?s;+`PTZfaU~vUIiluPnx-_A^7gVrWnFtryGHZ-33ok>3(>Db=n^Ks^ zxR;ot{bLT8iU2u*?uxAnD9BS#i zxcDN$NvpE(6BBaQ$Q1KuvI8W0*c;f3#pIN{2z$`C1%79F@5r>_HSsI*RWg+A zU!+Q}p74VDX9P%43&_c|LjJKbXkp7R18!W2dHx|==uY>nIY0e5*Ps=q!6Km}C^p?d zxaWD_jIl}eMycuVdy;m0%B)XNe>8mVU4?D(CH?B1pbE8!U?2}9vMOeOP!YP)IAT9T zy^W`;(C2OZ-7Bu*H|?O5`K&l8vNeAFf#YS@=Q@h$8{aNe_3QQjVIBfc>5@ z6(zxDdGE#CmHjb}_6xhA82F+X}^$xn!sF4qLM zE%AsUB|&1t2g~aA0oPNO!q%^z-fNB#%_0E}$lbw|(yy4ttSTXXf~$6d7a(A1(R8!O zlN1sC)w{fqPj(AfZLf^ghzIr>pF}e4MCfkX&SFA@odG$R5GNo)N*F7A^6kT$h$#o5 zKUzP{0^S-E>4|zhRxektSgG3a;__A==NL3nKM{};vIe;+-(RV=g~b?!NBHQ}w_CTH z+-$F^o5dzX3!C@o0GJ&AoCCH6xH_lPqv}os-{5SjVSQMDyO%UNHo!7gHN)Ve_fozISKHd@a-w}IHecrTxG<&xHSR%ycVxIu7sYXQ0DqMF zpsnplN2ol?+ufv!e6KL}$?Y#-VGE-} zGJ^VV`onS;mF*FIyGQu*yBrNIYYz8@_Z0$`p>Qb41)sP?DwTTk86Y%*aOW`TJi%Ql zA=C}(ujX8Z%xUHX(LfcltD#PC(_&j+a$1&~BKRTUYtC63@7($V@{a||>=`O|+%IHN zk@n#S_yuBo5rQCon!S#(AmhUiaz%%VE-a-aBfN%C%N=9$Hw6hi#Hw>QLLP^$L!^NV zXS{2)ff}ll4v_P~SSBWczFy-PY(m>!{98U9GU1r%tReWn5z%<9#W zj46!a1p(Qmt7&g(tFpPI6J&J`bvQ8gTF<(Y2}O3P4iu5{K+pKm1SA`_)AGCVlpz`G zKK{eA-~$daLXe3-gSowK9Y@T_ww_Z)%$bDo`F!g#x;MN5rf^F4Qh+8D6ug$W?PMtE zlSZLy?q5B+FaQJYVOTqc$6b{&*Y%NLt%-k_;VVD7=t$i$rlpP^Fqr!BM01(MYE8c^ z)nE`<-zhw#_K-ezbuB=x!F^CdWWo=}faP&3CQ6lJ;!h z-23=zZ)D0RME1PzlECKc=pbTA<9XfBXkXC=27e^q@Z#;kkfKM<&x6nn$)XtGcjTVJ z&A5=^bhWrTk0E_Cr;+9CAO zO8XMLBd``ct`XFj90=OHxG0yeV8OJ$NfU>9w>qWS^6F@Q>p7Ad5zPl70?0e8^COF; zbHB&gYx}|2s0)E1vHUu70;HT@@bvjrfP%vnup^E6vxc(bCpvL7GhcI3F$(Ttq?CiJ z+a>u#Qj({EN1U~+=~@sdb9ersjE9+qOO1hh$2zAib_z5BLVr+$@Bskb9it#diPT7K zR2nvI-!Pz0wVH9V8;$aijhfE{Q1hmRCt`BL`{1WT;!l?5(2baM%;F8*KC8j`P1xMB z<-=*`#+}w?A`gXhR|8|ERuOd(`xg$6R?U^u0Jt{6B^1Ob{E%CAba0DG9oaQP$DhZz zvlM{YI{7LP0>tI1cu=3~i57B)8?E^&s~&Rx1`Zy0)_+Ygc#YV6X0)nr-s0PhnHMIv zn&UuSz+I^Ch*I^j0UUp!HK@RrTu;;Kq-6bwDF#L5G`)iMVW~nW(<%_?_`gRiIztse zhe#t-K4ea!D3p)6>mE{1y4jHx?ti|`PF{siNKX|vO`!?nH@=hQL=TiCi5S#=u@+!H z=u)RYX_eOrGK<%!tjQsjad#45!&@)6lwScmL+HrT0p3r8|H@vvbUmq|NK8F&V|p}q zS6OD{UI6ZXkJlnuBrMM!%rx^rBl`ZIGg{4{ zd?bi6Xp(Uxrl#t)ajAN#R~j050a3eF5qnShJ>!e2K0gKM@S&ecxxCi2Bu0RJBu;N- zHnbXT2Z(oDiOKwmIH7 z0)sod+tC~zhQM=yV_z9T1jIcJV}U;)FqV9$Mo#VAQQ8vmJCMXnJim}@aHUtbM~}r} zp@kP6QI>M41UK`OS}Ob1LuGm5tkfl{uYG>{rA1vf=Cib%N~AU(v$rNDZGOA`W52g% zv;?BaDve?v2CL9S${>6MEVDQC9KSW0+q%J{gb#y^RX6ylO2JP%2qQll@L{v-qij#Q z%Dr5F#)gk=)mOhvWgvWJU1VG(QNTkQ#>Uh~eWjGSAI>zAU4)=&PW~UysqXwX`pxM) zXZ9Mi=t9)@a%HCSPWMd>Zj8x?gCBzouvHj53i5QHU@25Z&Yqh(P({Efva6(eXU>mF z0vJUq@t`S0&}Aw-qDG$Rh#A|=?fHHzlE1uB9&@5?C$Z6~_B;@YfoJVwZ8dXWpIob) zuB4b@Mm8=qK6VXsD(!8kER&am+Z`sVFX!eh0e!@M9m&i%9AtaFsC;YTOeU&mOtaiS zPs_+`nMhpG{mcKdsFmcfo?w{Tzho~_LI0bd~! znK}3*KU*{ktW=^DrF)15F4tq0hS8Rh?^dDHX6)TWQr||RJWi=VVH3o6l%7rPst)fp zlvxu8&r+L{9))gC<^9BEGnSf84q}TTbSkMvypxwkUsH+DZajq`SyR2B=HXC`Lo zi?UgZ5`dx#`#InH@{3#9@jE3HyknX zSX}b=tVLP|6=bMg^}~!!fMvV{gEeOYMM>H;<4k_Y!lJm^fGyNW7JTPRbyUKg`0<^E zfuV=SXOJ+A?~u9;YvD@aZ)g`X+$qYGp5;$c`BKa6!2z4!&^L5_D8r2VC?&Ta)g*ed zB%?FYVtAe>54cuxzwR}`-zD#53cTOq`1{cq?iGT6!^(^h^IAQ5FgdHj8RceBD#?&rN=zt$aOZNo>ND}@f`0?(t5Hpct}fA0}= zHwvRs(l-mS`xv@Okgy1>*B69mqF>pZQhkYZ%T_qnzQ%sU%22@1me?-a>W;47SDqi0 z;A6C>CcN*v8b6}RgJEhvID0cNF~f+8=W&mZD}p9ut_2CFk8nrS_zx20y#$K83c9XO zbZkSKuSw`~H8b(1wdj%cu&eP4<34AhU7s zoe6~|Ka}J4oeGo=hBNHCL}@6F_*Yx%7fP|+73bBOcs6=EVkHV5uJ5dY)&~)MV_}$g z!I90^B4JJ0&x&;vvD0%DpRG3UR{3koKeAx_Mj@+nY5hY1ScxS|OKo+SJv1(sK5P_I zAd6%8S|n1sMDZ3x`fRKIyY!+6Yk~9IUV=W^$y?WuTWgV01Po7Z*8tBxnp&~yj3N)3 zigk{I9V;iJ`+sKgg@F1H!4C%*WiWSDxniUc65J^=XY}hbWLXIcrXoRCb+=K8vPT94 zny^PDM0vCp7M04Jl+6F#NYv$ESVL!{A=>5{I3Fskv`^;5daVceShtNvMZUqysvXu@ z(Pzk1;WYT&|NZO$__Bx+z|^ONYPdh}03=N<|MA)7@d#0rG^x+-vC0Q%UPX ze(NqYv#3FcCFp_?8f!WXDe?{fO0jms^)|;Wo&%LFtZeZ&M?*6GF%@9?c-rjt?)RdX z&Y9)DqDHy2^D(e>c(t#Vu%*b)TTZsD^R-E7=RY!PUAPO<^|fD^BR}!+ixb=Y4 zHPq(LyA6-Il`x1CY;LG)>H$5aI?{pDJU4!Bdu&4Aiht{p~#n)+jyQ zz^|75@2Wx??QIaohO}Pu3s2GNPbsssNe8S$9I_6h52n&h-Um8MVRiATm^?^u_S290ai1H&xO8I-WZ7O>XiZsa#WI*Kfiy6 zS6KeASrSkZo$yI_ttgfY#W?zT>|-i0zAp$(4gu`NoFakybpUuDDy36D(e8`5az-u0 ze4d~QBzk70=ig{j2OpyXdQ;hlD@1E^Jx*NDD+eZOjA*{vH2vdIV2K_Y*PdAbe$o8y zgaS3%*^*zUG-Qp3&Sv@(NPfzs?j9J1(eIbsTnT}JA98NmfhWFtG6`MH=#)CBK%?_j9>0F@MRK)rZ0-QYT* zfL;O0SATvJL=6KXRVQAcz{2_x#`D{;7f|S-9H5cgXf{KS)0s$PY?sgvubgRGI(^zf+X`#cR!XZMBO_}>*McBR13jAsYaBz0{%aC%&=yw< z=P`HyFA|^$Zk};#-xc_ti^XCPv3`x&zP7BUx|FO0Mw zL;bdp`~Eq?LVNsd6Q+zpvvT)y;}rQlN5qi|JzP0xfF+QkL4 z)C^kLFfNiTBJ7F&8>``X=1RPKP-8t?*Krylne>jh?vOL)^r)Mg0v{j&I68rxrD3ff z;+Z(xyw=xdueE73sGJP;R6zRo7J}}K66}83sZiqz`3NK0W~Uw5jH~N}59T1LN)_lB zH>uFR@7Dp&U_#qfsztzGL;bZ$I-k**bE(nJvY65_;0l)QL%{!t$F`V&{KCY2t*j)W0zEAX5cUwW z1daprs479!fcSg)p8t}||BJWB0tYrV#e)D1aan*mhyTClTWOXBu^TAZ37ov6BB$O8 zPluX$GU54JHwF^KciPzr>rNH8)+6a{zl_+E?WLEE)KPXY}?1y{I+PO_7~_WY9eg!P>F zDvVvNt-UKFSbA5vp7H3aPWYdz>K?0P)^1e*OiH(v&1M7nTW@teO7U@9r40li%A>@cr;s9ii}tQ=m$EKKFtK|m1syGSsC{5 zEIaYZ^I`Sd zq2z*5<#c53Re65c0-J`|YdkX>9$JdPBtY+-`5_)NGCFqdI8ir}bRTNs9Yo6nXzVk{7k>^Po0LNP zf*nXS!zxi>$#xC^r-4k|8(jRCh@}KATNKEA5&IU=8s;EYS{hW?bR-z^>$S~kJn|Z` zYE%-=N|<+af*DzaFXm{P0Q*tAP8ua7f(hamC11fFP7AVV2Cz9An&q0j!M8_S(qW-e z;O|&rp{6pT@_}A~OKHn8#OU+PCv_1FAk9+y)E2vi*eMPY_!Zxc6aNN7>AuK`1Mm{drnTf&PBATb9x*&<&1^K}=33zdoFuyt=8=oB_Q`7QhO z?xJM(nTXK3JVys3T@w}>u(XZa{s8?zYZCaxd$X3MA#ii;2E9>*we71rpb4)+g&_4! z9Nq{b7&-(@sI61)5%r*&Ww&xX^O8>@@)>meff~7RSy9zyEOKZEP(LJ1qVo)15RJUM z`@;u!B|pRDKngW>2WvnMC6+LK4;r0E`4Vru1{zBRJ=9?APM0(A8j?0zpBYxyvo?}@6`BN*$+wT!^hA8}j@IC0wD zMgaqgnt6iPm?SleaaP_7D1?=A^+8g+MSl2Z#_3;Ao0i6)3Io)3dsAY2l40#V@kX8! z_TIfmzZ5WBU!K4cR5g&PDyN#5yQNM|%of%=oiK|7p~p|q>dgZTeTEsu`m%R44Ct8j zBO4l>i?vusX3^ifHZ0rJP%A=0x{iLIJ*m(USOYOfc7L!_bxyTdrfRg4_;GUFRi^$k z56oZS97b7qXHobSKgs}rnvFD%>%nR9VMY!kI4)Dlx>tj}s2Hka;~UQyRq>ZJuZ%1323 zvD?w`rgj<4rs=-QjCNkEd;YC3=fBR+vORSmLUsg})HSsH;wIscV23y=W5S#f04@j8 zY5Tz|A)mfrFf2Aco2G)im{htx6XeMH@(@*8%JIvnadLmr{3T!@R-Go;2JSb`BuSQP(? z8k3?v4hV9=535*^e=0r88i6lo!Y=%tG6c+mS+<{9WgSaO-yZLSE=R(+asrYU`h^Jc zCB_mkThVoWx6l{kI}5z=?qSOtc5i431ALNV_S(28H1&rxw~JH;cICU)7S*J5je0i& z8vW!^j@m*ll0Fr{s9r|xyIh#f2oQ*fzo~EvSF9&y@{q%I?>1(NP_>Y{4sOs?K61v#iOc&zvpB&`@hoJ%33F*tR{P;E5I_f^0S#X|8X}=cV+I*oWXCnCPAi722Wort}5bhfD7f-kK8(Vmmz2i?`%#LqYhwPm2!RAMH3cP z6@ZBFcRoKYR!UO4IT?H4wLiz|_vy$|G6RbzTUNoUf?yN?H{lad*n?+oL;P%BKtQmw zy4|HkQ{Akw73B zJ-G7@Ej=*rx4_DECKO;kiS5LS0XwVxtKCbUTWRJD3jzqC0Mr} zKz^jDzg(S@d|$q{wov3YlCK15*X^=Z3P`5u{2(?M=1)qLQxw0}8%P(1kFT48GIBZP ze7{F>9`EtA7IzT5yb!rh%OMBZ)}!x6I(7s-BIa)c0qt{ZZrSxmc*+Ha9meHsD94s- zzUb8+LwqFlJv}lMUWCH*a)}2%e>*1rijB#Vw{Y z!PLDA-m$^Gz0lONMucbFzYIr|%6O=UD80t&eHC9R$`xf%qhf%!!eE`Rw~a=~gOuYq zpbMGk^|Jk1-R6q+kv^1TPp_J@wt80QkL-Qc8|6!Q7y7+)5_%`~z@2swnkxG?ZD=(n z#SYXOTehuWAR~7}q2oor(0dI1y=i=mAWY6Io5Uj^0G~heHD3Ic(gnK%<5?I0?h23q zqDpZ$A8ED%7gIq|6BBqwZ52rQ3Zf>cv{U2WC^VRb5G;rc^#` z0j%fz{9KSZ9@|jB14J#KHIcj3FrUGVuA8ExIg~wKi)wL(Bth@|-6arGPTr&9xQqhh ziM=~+T7^1ptiWx7BJsz60fq~0T-1L=5A@ZwMovEJvvu>!&cXd+*VB=5UPy3%?Gy@) zH#3AZYXP9%^h}kMMZr_(QHVIiKs=bo;DUf$W>_}WK63XD{{yl8D)1?i>aAlgsAC2Q zU#I+`d1PEmBPz%j2QT5vco&AMjo{L$QV${MFB8a_gLq%qI1iO%ZB@s(Nj40xCz{nv}C@WX8q^t z*``%C*)sVH7jXjTK5<*-&feFLiyZc~_|@L;d$BdYp;8n0oE!0MBt|E|uwS^KVPVVG zGN6BBn&4?NCjA_D7m#0iQB%Y}g@i2QL*M zO*r7xrh3FDS9-cFft8B6j4M2)`wMGjERX`Fx^q`nW*l4h)&=fbn>}$Zx*)MT)oeJ) zyFg(zo9!D8B=!eXKyLgp4A(bnm&sZa7&aqh>xFaP#g;~_A$>NUmaBq>|73YElgWa= zuhV3ispt?z*fqkq7Q=g19|4Md>fVeqZ#!`*(UBfRqM>0lX5F?(4rx_#Nd-EkmCp69 zvEohJ%-}ROm@JHhb-LsIXXMq)nMk3@fGL74-p|tw23rg(?r1|`uHAqsq8pVx)}2>u3z_vDP4_AO0yimU zrSXI(pnevix#(^7UUMdi4#e|o+kNzDYcZlQe8#NFQVqL&Zw~GCY09 zwZ$xG;KbxSrz$f*3%Z-?8EL44wKGtN@E24;t%+zwcqFFb4SFmIMNky-WLo0bsu*4^ zI{1kO)A59e;kjLlR+Sop_}}cK3r4gz)gT=$$TBU<5BZ!OCVWAz z2j)7nsADgb=u6nIyF?VCO%}I0!5{Vb6@r}Hs+7o-I0fR&okuCfJMX)xW_<|X1VEs3 zg3;9RPQ0dK%sk5+yZv^0(Et<%$LUd9*{JAK_|BR>`lOf)0WB@vE0=T=i7)-&-t+41R(MLTNENSlFo zs=uF-hy8J+FW3yB7XSA%u*u~XtB$S}s}w}wF?YRKJx#Kw&jIC^%QJxU$Fr)+yWg{% z0t+dMR9l)!UH;YJ!Ok&AXCD<}TQqkZcA!OxRQ#jb8%CftfpkLX_Y{bwyeEnZ?FpHNs{I=2A2s zKsFgGh)GFkYs5_NrC(-~zK)k=a%qQaST=x{dL8nYlRhmDm!5`}? z1Fh2FV5j2nPF=)^f1=Q?8hR;~<3G!0yI@)-1TIn%D#^8@aPqBuXm`+P>0Fq)v4r=e z7|!@{;lH3dbFWcyP0|#v2JYzZa$0-~Qu-#XG+9t8O;Z8y)-6~sg~4X?JoaZ<-KXa8 z1WLDpO!6BgCo-_4+r5aXFk6OvCAYB8ly6EB9m92#EbNEN(!70QPL&&HvLg}mWUBF4 zB>S?6x%=R2~^_jsSuZaIaORTeOwkSd=%UwW0h?g z*vs5&EvtJig4XQ#2o)IWJR*ZPUP<;uza=c!td~hm}o2C@W-xh!njRmK~sPntZ&poxrJxBbwiH+X@ zCMT4TJ7o*WB!iM7vkI7%d#dz|Sbp6TcwqsAmQ1{BB)l^-f;Dl4OL#PVLMtW1{q)?n zDV4l18VYSuzj90_2xD@ldKcolzF4%E<{SNA_Qny&%NdfCJLOQp>$K#4v>yJO9=3iQ zg5%3C=`OdFa9=7PG+yqgx9({4?sYSPJ)YksV)+WL047e`sN^Lb3q#{xFp&)45Db_G>;ge=JixC9_S1j&EHtySx=}>9lyLg;e$HdA-h+_>D zVmz4;{g3Y>hgF?R%fK(ZnDZ1E<&gpHFSDQ6O-d@C(};}JQQA@Vdt>LU!SoUV$hWxn ze^qe8@cupM(P6ut;0|0|D<;}Izcyx!oi3XU{qP?8t9?a%72J;%82?0d=APseoT-zp zX|*ysF&+`CZwL*&%j*e6x!92uUuTXP!~|#_Y(5bZqi=y{bz#9k3}Dfx_#JQc*PjY^ zm+hh<%;~_#1IbkSIih;ChQpm$4J2kgNzknw?4V_Ms-Bu05A_!I|5Q)WDJNHz#<#4U zci0`7E6+lPgE0T56Ln%r%1P}L^o$=oCylHIKX{2#VvnRGr@;@OHlA&T(oj05-i|<7 zVSWVEiX%QM>@^akSr23~O>C}>1fuAp)wLs{86DV9J!7y8${ZD`r$O-$e&)WNQd@SW z{nq39{v@Tx?b0tJ(aq9n#Ypj2b8ZgwCxh6=9Sma{5_FeDXKGa1y|m*Y%;}c4Rv^aD zNwxSWAIqQKfob@QRIbWRik0p5+96N%Ex-(Sx&fDQQt-Ui_Y5F_{Cp|8Ra2s!`;A1~ zHv~kbW;4aUQfE9WOAbzr4NgtjDs8P?^)?tKyjm-WP>I)%KZ54Q%haO-zI+Dyrzl$C zk^9MpxJW=!@|>8bRjPmGa}!b;Ccw&>MUz>c<<*R*A$6u9QKHJp`*>$Ak7E1vKF3H^DHi@*rYi#ILFlT)yFP^z6qe;JtrF!k z#Mp37hno3`NKyo>_GTBG7M(X@_&tY)uvelgH|rnIXUq4jAuAbJgqe zUC{Vx|C*!h9!)Pa#1^D#ut6kas@0ag$h{NoeczQF>x_uYo6e?cZ3UA!MPj|Wiq|2T_!z5aD0 zU2$^LWJnSQ&2VNIF^&^qo2xNc4psCoB2%!uAjVr;6mjlI=9yS}@m((8E5I+!mZ{3ym_78hyJXpTS5AjWzk?e`|hIdu@K`qkIEK0kcW&$sWw zV(6K<0XMBQZMtkM#L_rNyY^6cow5@TK4YKXsGODYi&~%>)I3M9i>$hh+=TMgW#Rz- zLZ*=NP3T$6iJnMSra_P&BJ)K3nkTbxF|(@F3l?iS;>?_c5FOPfnRoNSyK+?OL0{kC zrDK53{c=}+w@a)OwHZj9mqG115J=xLbIo(iti!fYbU=s3jizR`)&}v4{7Wdo&9)*2 z0*nz8T#1H$H&se{5*UP*0D~E;Y91Vuf(z-VBP<+oJ#6V#ZZ&%gM4aW6AW4@#z;lVi7L=@ZVhz6S!rEZ1|#|&BDZi9<&UmTz}Rl>N3p2ttBQ*5!nJYkML z&r|egZV}4JA3nU-_%C;qin&(MZ22KHOwiE;otY|XXT&$Xf1Wg;$%`V)d85dqaA#@E*m!wx9LALZ#2MYmE+u092 zLL8N7dkSVc4V-tZaqIQacQ3a)MKBOHn`OS{r84G(mZExK#LnFh8tYU6&}SBe55|pV z>6~RZ9|v8_fPw@q266xJPhS0q^<+%#@zw1I5Tm!V+k2MHo%30P)#S$Br7Fy&;hcWD z@Ed6$_i22Wk-;e!u)gjAan%||t+kW9Tyf*9r(dii`95DX}|Gd7k_oS-V z0ds_}*4?gb1-;1;a7w}3MZ-%Qme|E*w5lF=Kpkc{EC33lhE3Dt4*gDE@b=r?wg1LS+4?gxuL?OxUeU$ z&sc8LQMrJN>*jdXQo}%61F@cEze(RGKscvF`7=sFQ5WNXtlI(=x)X@Xah)VlPw4@& zOK(nFdU&X9+UzrYd(+i`BA^9G!}4+-k!N)y2A{{lhCvLm>SK z&Gso%jK6S{QwL+JR6$Mj5iDLUF@m@v?=L8&zCq}VBZgO|8hW(owI1IMCn3kkl5NyeXeakqu<%+}AHEjcGvCZqu*eiYl4$VuH*GjP&@=M+av{iXY0OG}mR zi~WucfTc~{o6iizbWqw}ivbH<*`yA&IeW;qB>flOcjA56u5#{^nLHRh?$>5fMrWH` zyL-ERxSHZ!eZ1|Z-5+}E{rb& ziCcb4f5|Y)Y{~RaHT%sO26v5O0Y5{ydWt4`pmLK`FSx_>!rKfxdka zf;CdT&x-ZrCMX}QE%C>Mbp=fj28g3R2-Gp=T`NoaUbu8i1L1g$bv(OMHpHPL?~&P| zG@U8!3FV>hW7Q=DeGf_Hyvqc_1 zt!&mxjh4rB*<%?%zDq4{jdA|M0-r8H7EE-&e!Cip`+ezxq>>bf0wQqI6 zP#X=I3YuEVdV`@smlS1=$Xy_YxYCKpEl9^2?(xy}=8|?PG(S|v8EXnK8c1VfzLZ%x z-EPY7;;r`+So|9XSDF$F(G(noQ)dt6{th^6YyAB<7#YLlLlPGAIgE*ovL(83Qw+Y# zmquxlkxS(-&8^8HzM9z0cY?Gcnt~M{TwMH3N}2fLvBu4um88Gjlvf}8@a{UCGH31| zC9Hr<`RLW-h?$T*L_{;r8>5*5-Ja&QTGQ4;A~XoM*@ABx&^-!qa7qP#dEFNG@1K1f z%GFtgdAncrN*smKC_&5UpZJ^~VJC`X@O%>15f6c#G_Q0_)mo1@mRc*8_OM^cA_79r7xpo8vOi z!;nuukh$6=hNpq%)tR0-ky3^{W=_&|G(>Rs!n~ep=qd`up;nic8cj6bSLe*~(WU9iq#0P4&&gq73LJV(c?GKfYtJ zYFuhO)XJA2UXUM6`KOHU#l&)I?`Gj1K7l&BiMvfISd)Vba!Z(x=&KKvH6U9o=H+UOGsa!{YGzxkLL*p&CGfGZmmAFyw zC#wETPLZcj8z~<-=c$dq9^pv{GhceK0dN-F99GXKN|#w!c0<|eB;=GRyLepu_8Cf_ zZ`y(8{IO4&FqU+X)8_|O(SQR0q2}|(k>xi*f;3|=2m23am=@8y8nkQ^o+KlInhg6}B9DOf#>56Mlk3 zRk>OlrZi*j;;tdijlQXlD8(ArnW==bWH6r=-C*78y#TKX_|D!ue9Mee;&kB zZ`45XW+r>Z!j9a4-e|j*a&ch5u6pOOwCpMalTAY_Mh!0X9{0z8rQ@0osrx8M?}dJ5 zqT`@b<{oATW8QxzQZ>CBeOIbaLV~!K>~+*~LzRdVuxS&#+I0N&Wi6P~Rx9sw2PWz1zvnmb}Zi=t` zk}9PUREY~$=zqRU>9M{wF(cP-&UWSvfxbA?>%U{hpK7K;4w%}XXH<2_M zXf~6pdP`CtTvV6QEheV&D~+%FmdVtm8o9D_g8B0l_x&acJrYgH@DgJVgbeOb=W`Z~ z*sg|T-r#N24{=0T*^fBTrarrrK**E!lr;GiMOb;PlqpGaJK@3`^7bxukL1mk4tc_r z$@Ykx_tsTRMmtqua?rDK&{Q?Jv8F|vKg ztGiJ01zLnW3XducBja710o1=dCtMx-{bq}=Mdl5nry5OLlkx%Z;bV{EjlWy**N=Oy z(rxO3ySTGMD@) z757y>Nx~t{_-P^7o$q!|)f{t$+$unsvfrzguBAvgD1qnSsQ3n*GiofTyGA`kW2Xr~ z55$@vkfyyH>^EL?rQ(5xc8b&I9{RlinE)}s6!Wm+Lgi3^b_morDtjUuUBRgeT;$76 z_&DZ&E(8h!7R%QUX5<1KDs>x%W6LUb+;4#DIh*276yCrFGJ`15u0kViI7nu>+XT2m z-ikQTNAhhw%Z`LJh6v9e%_%fH%p@(KkXXh*!@uY-o~&SpjuE@?+6V$j&QKM@IkeU8 z@c0i@+*oMaEqWqQ+;|6n!+f!ZIW#+c8+0d4zkA24f&GmPFWl_C+lg*t93t6B$aiU| z!~!lBAqp#00y@!ysqyn}@;XS^{&cg(+LouExD9EDs{c~rp;TZxC0b{g1M3BlSt)ke zEAp3gaS$89OkAk!r#)}oDG~~#gqEb31giE;@1R+4QLYh*W3cQsrL_~cvv1WE))#E_ z7lzpFP1~moZ!pR_=e9L9AX3m)=Dzn%r2U3nG$9%%TW0Bm&k?^wKp6J`|*_y=ES zrMJgW0^_A{R@yW-6o7|mni9;CXBHmOK6yeB2+Ya@zLwF8-(c9Z@`OvAjAKoS7-c4tiQ(rDyc6;35t;N;XhN@zwj(20UW3C&zmFGtn?rH%2x z81L|AEOSX;^CV-x8NQW&aPK*I2G!xanWPrVzHT^oI^TWeH=-H7%p`!SDGg+7?GH;wR;0 z;v0&Rz_#1wxay=&rctZ4YCWO2 zzS>z}vIeVWAS}`mzP=cYuDWmTU_$RGzvF+A9b*!+AD;nlu_3X+b{xf+FkE(3rKk?< zodrPT1{f$dNIn&!+6@AM@yISx{(pBz47oF^c8b~VAtGY^J()DUgX>wMYln7q66?7T7hAz|x_s&&(Csc^%Q z%8I$p`UxQ+PJBE;-{nSdP=74%i1zc@j3SL?B)9|DsvebRP!3hQ4*w#vput-2Bx&`J zkJUamMC=~W(^2)By;sCKX;|KH!CFTCUcwxcY^xG)gkEprG&x0vqV!X)Fw%G7IZfe+ z-4<}Ze$wN*)&&!f>0Yv9Q69ZL;3Ae%sdIMIpxAZ(CJ!n=J?+bK z;=d5s?+}GtyFsOP37BZ&@7eUL3^4jg07;-gK2%%N;w1l94Uh6I-b!dg%j&(D;({wcF1%PjSgg=$?oBBCLmIY&p<3F`ox*8cKke zipZvABD_%4X@9Df^myMqLTG6kw@;;rYy7;nyXPrU=x6wWZKc_8Z^+$5Y#;bNdQ5l>t50bJrAV+es(2z(R4 z$|SF(F&MOdtIO;4J%oeSDzUxvI*>-rJGhB<2%La3`1F4`A<4{oQSBL0+cz0c-xMc_STsL36( zfO^tTTFnjf8}~>@6Dom}e%x9a$#7-nta&SaPT_O6p#v5#W0Ey#_E%%pIvlN9=-qe> zu7fM(fA_?MTDa_8m;L30H^1}2v`NL@ehrY1kIJNBWc)E7c^4$|US$L2-~^%?CJOa3 zh)Vm{H@UpPWo*RP<5qpapE2MoEiUhd+L+01nejeA`(`39;bT#+3UVO#RIJM4x4FWp zU)r)E9jf5It0S49jeP?04w^M(z^@RePc+z+Ej|8t8wzneBM5_n%BqNY!@^eqIXyj^ z!y?!TYu@|GSC@lPqbFdI1=M4 z)#DaU74HlBLBN$({d4WKRiXAAo%50l#&KUm#IM4KLoRQq#Yp7EO8V)hU4u&kC%z^Q zPAyQ@c>O?VMeu6$)5yESFyRGP;nAQLZy|uF2qPHi=Wwt5I~udweun<;9zF&f2GC*u zwdIoU6m9XkT4#S*!!)kfng*cg-!xCh-iJbL7A?~iokaAR2dIwywm$rY5z|2|AWK| zFNTqONOGY<@_S|8Bg5`4ZWo(`&$8Q|-h306$TZ8WY1Q^N@>ecLE*5LEJbtzJ5f)aB zW8Q%04@6!s8Sgt%nVck%XOd$S7%JT1O(@HVf}LhQVFyyZd`x!eBN#{?`gU8;ORuR(rbz^ zGM$J+?l9^9J_~LV)~VaP)P&@s|A_9onET@N_PX0ap38+h*MN))z_LJAQQ6l$quTpH z7dt;BK?IA{r!O522?&Sud&Ib7eZz+OOQb$%hx(H3YmRzaA~`L%k5vSi;sPL>3bp?~n+U-&S%YZMEKkc5RJ-*ME$|h<=j1p{_z!b0W@y4=G9i z8c|s-1f6VJKji$t@fhRLV7>42?tcWOFMB-n^l*~9^y2#auKWnauLnGO0CL$0QUM#` zMY;*~09ht=tx84QCAJU9^ww`g&y~FsHJfSA7dd)?c2e1D<1IJw>4K(U zMBaZ#0R_Z!keo*0VWDKd&MQVHGJ=z}hqfmdO;d}rVx?>6ttrs#a7L3F!q;$gKuV_l z^el@D8MLW^{mKu$gE7-YEgNnzp`ASYzNVKZ<`y40iJPaGRDID>EateG2sVq+yzutT z~f@lhm*qPDk3OG1}_(3U(R-gcFhM23;2}-Af+oPEO-i41-&yVeo(U{sbcye z)=f>KcX1BdVcv}9)?~YT_Yzj74{m)YocCzKU9mtR^NSeEUi#y7*i>ZJP!uVptWs1M zjW`kY9)Jf;djG;Lx4jJ3=ih83LjCYLaKGd|R~&kK!^_{6+#e%|V$3CD@r+cTPXwR7 z19*c`@hDlq9AL+7J`ke zY@Vv&2VonW&DWP_$$4&K#%_mFIsp0Vpl)`PYoq({%Pe#NZ=OQhJY}Gac+nnmPBAJg zX!?$S?pDADuT8w#Ak+9BP>U~GU14lF$wen!-%T4iaXzpeYz9cfoQcMEo~577lIh|G zBLuHVW3py?*K{)grm5`93GoKR*2NMi)qVMGpa$yj)|DIO5kkjo?!gwFG5)Y7>w6%= z1XSFCPrym>_MT++HLiUi<~*958w6WlbqFrwPG+e*NH&n;#ia(L zk}(d5kGun+?G9cye7}1btOX{sO8jGQ|H`6CaA6<{VpjR$IJxtyNr0&+or79_fd$Rc zeA3z)$N^ z;@q8i)@${wUPo@@-BkU~n1oJsl7E z#}}Su^X!CYERy$=pABZ+;SLyEp9hg40V2`aO0VUiOatgWSk$j$Frl~Y3YS|xA*>b> zLdFA6fk&}T7aOs9TJbWXpa3HHmpJXRyoa<*t`eiw-Zz|U(`HhVB8_VF6z%y@^AZcN z7vW{5=|}A+?;@R1o@kf6)+z%~%n-WPODHuHZENy;Z}A-=dkbG{N0=?~6_6)5$?|-y zVJv~X1cPk|G!l5meH{!5lW;k{LRm!`J%dKC>aB!<+z!7$(Nce_9}n7KXJTj0N<(6- znKEvr3JgqVo6EMhm&E<7XRnBT;wlkX#U#6R%9*tFXnD`%9KdNHmc}mpY9hYt6Vg{X z!1o0ZRGR8%sKc-!-`)ksS0M%^QtVF~zP>$5t41reIq>0m0!QCUw5z=4uJP*p&iMUa z>2ME52+iBvx6H{|oa^ka^@c;j8EWoVT#WR;Y?4F@pEI_2`+We@>BJmD1rTOLJ*X;e%O(^Iy+k$A6-hNyCeMjp{^h0OXOk{?6B$!Ek~o&m^)6-@}>{dDc4 z`+_kNAw6OM2S&nrLe&omb)YseuOMbS243?qXh-$=Va&)YKbpQG7aGA7CT{?iaD=@7 zVG#=vPDEc~FlMRo+`&-IOGIfN@F$$(_7pHdBdR)X7b|+rNcI^^8)%n z9V$;EyQV@}#fy!f%is3yjEVN6oxDKNwfO3LL&S-_Usaq>OtSJC;jRfSgvmx`*M{N@ z>VBmC6eja6SZ%+l*Y9D2A+zE#UNpAQ$?DNeI2b^D9C`2ud4-z-4_m*6jTJ;i7+@Pzn+|_wtUhb%0t>7S zbA`M4N9`%5DX#+}BEQ~IQMIN$wFh0w)e;9|G|1Of&@D@`lM&>xZBjTmw;i$Pza)o{ z{36eKe+1KXlY5m&rPn6(0m|n7jV7cxfgZ7Z3%?k|DaIbTaE>RWs|B(^1j*TSo4F7& zJqwY%H>2^y#)GTG8&_uSf%b65bIzCwm>^$c+_|{=!4ZA*c+T_~v8mNW&dY=`o0yH4 z1x5KqY!JRLwz4Yp#fZ>jHWh%OllYsC=QKKSTz1g|cdXUCdi!0L zwe(Zl{daC zn4rH}A5PQZ+cSS)7XQh6QBzFk($PiQ;Goh~hB{g~H@VIJToCKT#Alr$3EBnJyY~jH zQKB?^Y5(HgLI|c@U0soZu=G~K&Xc;mFg8xLjC&5GYY3e&#MfR`udeGMkxsyCnjR2^ z2SL!BPrP1)0|lmXl2NJ-WOddjmM?~@HsW-CXgp5;31#c5QYMj5$$1k|f&T+$`7?xt z+L6_|Nv}}}9Mqon&FFA&;NsR0-=PrQ@Rcx-$L?U1&3)$EJv9|CaC znVfQKH@bG1BCQxOmvBvGLg-0+WKlst@U;Q$XmNQc?@!(cx$KZfSaU~yC1|lHhY7dF z30WXG(j_C_G?mqjX6^r~SoWS|o&xLR4*KFPq5$dhd5-J11jRxmbJbzD z!T`SX(D$SHE>Y9+KQJV5=}m?OB#@T}o2^KkWVmi2Us%2=;4nG^`}vGyCMGUHLDB(s zON3V6KW{?@#l=kMS61EARqI~)Cgr$Em#=jFA7a_Cv!3NeX}5au#o&hL)!}kRl7x5@ zvg-kkOtDD;voP@uipqNx$K1W5BDyG?_sA`ADnP=K{H2}ZQ=nHbHd|!5$(WjH8?iLy zF1{gi?-vrPzMQCMt}s74IExJYyf}P~yqo&&-xUnzNVy2^LrV_X0JZvg+L!kdw`RT^ zlLzk`?1{LpA6v)DoZ$e$1X*|jVYDr|B@AWT0qmt11SCq#C0V4cCY7qr!vo8EcOu=R z9Z#nyEA;&#ul#Xk!mneeo9q{@MDVERy|Ljzn>WO15*hJ|bP|E*T4sJU5RgFS8=ln3 z4;VeFel104>jLuU%tM4GQ`cu~SNMhUa}voJg=Ll#m%5_bcPt|>dIiVJQNk-fA%Wd6 zZZ9(37MOvSQzfxFp+F_MSxtJj?iXf1P=0B8Z7sDwN6n2Ih`~3V7mz`5a$)|U0<5ma zCy7}eRL|^F0-~(31DzW>jyiy4n?IW5D24dDC{JI&tbzs?QQxUec4}RSHB>{X_h`)d zUu5o6h~aDB@rU)wyWk%h@mM3^;&1d}?50^*76DA(=GidvL zCVjC)Qd#EOiK^+Y{gaVbsqUqDXcRd^&`#tHCnDG?W0&EiYL{T~w8TB}jB%wYsCj0j z&RrMQam3YC0RdSro3{H_$u7CRL6r8s3WO4$P!g~?IrcV^<-zdBTZK8Z-Z)I27?gRY zl?a~8@27Xf`SUZq3W_hy#4U*$s%5f?hRL@U&Zp&|2PlIrT&~NPM&ErRfzFmX}*1f?9Xs- zGi`94S4nFDe`_6vO8&P}5rcr zphmoM4Sk>R`)=rhcc{#}&|1ugas3aJYkPbU39AfQ>WLK;SCMo)$_ZfemUApj5IY)+ zdckh^5{?EGdo5*#3o!L)o^#FUuW^S1K0P$!xEuBpK6tS2Ig1qZq1g73Cg1_-L~eF_ zcJhZaxp!#<@de6NVEhL8F!=?1O~CKNu^rOd_7GtoXRc87r+Zsa4BH4%((yBBV9{+G zv`E>i1LeK(3bJSUOCF@#57CQ_O(Eds37g9{$hhVxNAV~0c7l;QFdtsG?+|M(zebo) zI+0R0eh`^#5vZov*;;`C?rA>jKh0$u67xuGY0_^^5kAN=*rZU0dif)`BorBph3j@W!RP7=KcU2+f0+g?bfp@I`W$wW^lh zY3VBx)f%;K7xjY-W68S-EZBCgqagDs`BAc-ISVZ9VBM~|S&jv@;{HLC3u9Rux>|Er z*3Gb?2~Jvq18awL+HLkEn5@UNJ^cfsrsDC%m*t^~S!`gk=}ZFFu3j-xN!K*tW_N(0 zcX*RDr-RKZxVV*_;?(DmwI0%Ao%^|!|V(%Yu`asvEf>6Z7(GZ`VuvnL2b zZe##?mGJ)+oZ2RtHa(?pA``skNn;a)I8`e|iwz3Ylz5RwG&=p`;+@tcMJv;pDI%M` z*eS4mY%I}gj=MFWsOx)qUl5X6Hhe0kU^@6UO9+T>2389yKdLOpa8Qd0@iQ4t(E>E? zw`CKy2U6|(-o93`CRQF4#`tmAq7I(_DRg7Bm5q;iSomjnR9}|%sgw!(rzvXu-o917 zCyQxj+Y)KzK{du21pzp4_X=OAsK_Ec;0Z&D6wlP}LoMHaZs(#8`LLCa!i?RYx!*L; zp(_zlZLOlX;?uQmj3Z*ShO|ABq9}aG&tX+{n?`Tba?uN`j18cA<~bz%mI%=}n-Kva zYgW08eXVDPgx01wV&)KFq3~YJi`L$UznMK!&emv-x&?9n+V5;d9V>I}iG^S2j#$)> zYG4sGqH_87e}|U?d(X^L#QP-Qcly}^Nvp8NqUoHw5~L4SBj%cw;UJqg`Dhy&5g*))Run_9F z^UR0JMC|VT4@8O{Fh!tdTW{+Y>#pd=X2{FQWLC0CrTMW^P3O;glRfC?F^mpCGGQmmJf}{ zxub4D27G60FCk?Q>Cjj(IM-4onVC{P-YWmlK4J@E3br{w(%*D$&Ljx2{#N}P;fN!z zR}RNiCt#bGBaA1}iP$nXZVE0%Trfb30>|A$=jh8jsT2UCzhq-i$n>ahwt`xQCGX7;N2;cS+M!La>-7 z{_S_jz$@+ljvFeyUjH+Up-=7egm40QRs2HN4iKR8TwTd7Lc}PT5iZU%l&FWL;N3X!;*9CCpcG1Tk3mDzt5k5D?G2E5-? z6QM))oiSb50CWj%?-kRfiC(YProldl0JGKWc`iIuL-2dHG*&5ZuKGNxfxuU|*3FL+ z#Q$O24P&(51%Yz%szI~)S+DZLp$4S_d0jpgvIf5ogBGp|rLNWXKYFq}D_3ZBoi;IJ zgz`C(qgJsRIsLU>Yf5nCUE>S&CzB3 zXm9*`Gjqp+*)_vfH5K#}9X0jgTe^?RZ%;P2Q)p$Vd>>pU{CVosKW#ISAx)>TxeH~G z=k(f=`c1QY7pqE4(SC6GOeY)6G_%F)0WsEV|w<~86HCyc7a>`anP_|RB9JZlbGto zs6{8tfmpbxHBPs7RL<@0 zUh|6rV1zPME4X#Y**E)c$5EacWMAo9#u|AR)+mRhc7i6YOj73eJY4O~OqvJsCZi!T z+Vg1&2I%2_I{_%WFOd%zH-50p`$0ul4tND#cPayTKUB>B z>eHXFyMyWOE8}%LrTdFwXBsMXQ+aZlb{_q4Ha(+jJ1hJosd$3PHh^vzrdiOC>TxvU z>ChFjp19{zEKT*T9J?50a(^u2bEzn6_gL%cc!g|l@E;PxO=Y4DW9lDH*OQ!Y(bL#8GY==#{H zBL$UYulbx|FAk#D4`+mPN2q1@qOjZ9{y`CjPD32oPfm>Df=gVvG-*lGaPiKPd)zc| z(*xfjFr%RMovdr>3VqGb6pjsY8R9e@P$hfAHi?Wr!oP0}@h}h>tFscPk{+K5!glTn z`iC2)Yw(*YcGbwOCDB=f?!EFOX+WDbg^L1Ju(b=*=Jj_jgdw2t51UomIBp8?wW2|z zjT(|~&Wsql0}nSuTR(!`Hl!$e5&~}kZ=eI@|6MJE+^D5Mm+&s(9;D0`+hg%_5}*Y5 z_bFh>tPdQ1M{5k_Wj1_?vT1q7*@hmn<_82%alZb$uneLJkX5xME)QB7Qsra)C9iRg zbY;cR{u4@2u!{QJ{;fAx6wqH?U4h4-Bm`zxK#`Cg#|nV}p;`CKP*SSS6CKkIQUb+)+77f zl?vNKh7*SrB@vuPA}>g`)b=nIztzc$=*(@tS|bOd&M_{h)zvHNOJMI$C@~F;RozFo zWN>1Y;}p~)aIk3cZFOuxaSB3v=kaPMXb9Wo1aJ5&Uvi$fyl{?LhW^quotGC+Y*cVG zCZNsoZDx2ijhR0G%0%3a0OCcxt3ETC)l#wrxaTglVPP}oT`8%+BXS+&)NgS!XB zu~5E$Cef%3)MHHjUPF}%;?OT<#W;=hinEG3mo}|hb|#f~yd&EPb2ECxFpOhh0I$Pb zw1|W@lJ-xM4RE9)UV^P0{?ZXCovHS(yaf~Xms+*|Vb4qLU`DU^exV^8K0s~pC)YTh1dpEkl(i#1 z>j!^#012g){?{0#`AQl^8E4Wc6&UK8E)kXFRs3$0Suvzdg$OeV0$cWTE?8Pa#K~PW zkdedPIT~&0$MJuC&A>vGTv2*9Bke0>CI(o^Ta%TSyQ}%o!t3eq&kdKHHzOn{{5gzl zNasNJP8lX|9g$Bmt&w45q5qN3br&5i!5|R;k5OHq8r$skE4B z{n)Xo#vSIknrB)m-x&~B@sS#X911&)TL%gsf;eM+-65#HhpTuKHv(e$#NfGjrTa!& z83o820{{`Lg1j~_>|1D$Y^n77dft4Y{vC>rIVK38jCLBb#j2@dT%&QG97q0{`K(hc z&#(#6jkg_2C+{R({M6#xk+jTtNUP=L>(4fLL-vv1=rnBieJ)vvQY=N?Mr9&B)@Kyi zGYlNsGOG4f_$?kpib^tHhWv|2sI|D}-kihSXRPX#Us(+%Si!gJ-sN?E-^yM z8+}3JDkEwdu+mT4A?ltkV7HL}s)&vP@WvxwG?nn`=ZIi8Xgo!nQ1}qa#b}M#AzV>H zA48vM*xHLHA+ZLr^1-5LiZGEoM#eu160e9ZYH_UpK?UJe5htFRlq66PrMFlpj6~*5 z2AXg;vb=yKVt-R;uK$#v=_L`&Er%`>nwOAR6xlCw-YC}+yWQ^$4HC_vuGltDhu48N1bL6Ueq1Acq;Z@r zr|9@&m(75AxU2H(IkfHlcAH&gM=I9jRpj^*JRHUm5s*WLdjhaaiaouR`7Hgfl$pN` z=yXKnSbWf@5{yU(w>dii!3z8T6M1qnh>#p4p9+378psNs~RknUmRdOqx&_*B{Zdzx4D5n5pecUruV z1zL{4#-fnh5~w5QhXM^P?7H5gXXQI{A})|GF7nx^<={8cA= zX2U$oiD_mt%@<&KM?S3Iiz=>I(0#v{P2B5J?7v|w1>%7srhLsjLsWR3?-m#L6m$k?;#+;sxeGf4-vk{9)Mb-Lej(Xc= zya*zOO4kA;Y9}1d{QPSKy3}lE%1h>uWKUf2`)0zHsICp+XBJPau1+`860miDE7K{7 zao9g&DLZHm?5}JPE&ioSX|0yS7ukq6$jyzD&;aPQwnTO)6+r|F2;+c?l%HaQ9yF8H zSJWz<^@SSMwUfr9_b9A6@^pLV=ENdRIP93(E3yO_m&{*czgz#?nChKQgkMFyLg-o$o&+h|g#exMZDD+DJL2OX$ z98L5qOm1(FZC5}~9emFyjK#P-wLi?D&FrbLUGv}6&cH{~Fwaryo!6@GSRi9*ccOPy ziVVh%x%G!TZKqE{$s_d$aHQ8B`c%Ruk?tS@v^a$(5Uu6e;mr-`!gj^pxN~JbH63da z{M;=NUAlldTv4xeU_H=lYAg9(axxC0*WyK#h03R$^Pq|LWyP6{aVtN!zlya2nPydz zbQ}nc2&`pWa5i0e(eEhsDVaR^nlIQs=EHt**I+w_aPu7L=*g*)cI)dh(NUYfKG-G{ zWW^Z6I>26oQ7xUu;DSjv5WO#|6X=!hrXKsLu=B1QFQ&Xva#3l1jo*pjA9p&`CC`Fh z(-P)_07fnI$(E(gRkMe0rD)>Hb3$U+bd7cJ);ERS|T>gn`D zcL3&iGZ~iQ)|w7vQP#j2bc5pJYCxezH3P`qCkoDe&0lBJ#NYPEe25q|TvH;mMINSW zsa@3Z$kaSq&6M2C#R1IV;yY4sPLE?x)nm6{CXR$O8@tQS%ZxZUo&0&|_i&eFrn6te zr4#)}qrwNPVVz5Ncg5?5FrGZA%z_fx{aBWc+uM0r)6MZ4M+*9&KMtCPgzw<_%vISZ zt9SE4Y7*H79yM*1f*_!`j7qLhlNdSyz?){&X}eA#QTag%EX%g5^z2O z?{`b&UGyy-!?DZNv|2Ulbt2H_U z_~;I7?AJwx=rvwxH9t<#@<+D|8!BWR_nux!n(>dxdUk}_P&y*MS$efpHjgV zmxzA?DF9jA;SSZrkc+5MJS`<>Oz;+ZdB+h^t`0P(r=cT=)@6i%Ds|L7n!+xiAbNVi zJTgz>tfJu_<$yPJO56_DIdkN`AAnoqat{~GRrumd0=pJ4{&NVzeE`qhOhz}adXieI zRHX~k#7$Rd7zj{4aQ>+zRs&=jq1(GoVh{7UqYM~wH%zVS+Sm*X4O~np78LK(Z|2BN zJ!P!8ngmB>V$2G;Xz$mc78+xv%4>}F_2+P?QbX)AQIDO@tlA`PjMmHI9JXX2N>oB4 z6G1agfTFlvc^-Ok4z9tUjh+e?w`cUuh7!^yIboILOoH8}m%94O`bXjyGo`gd@&jkl z#bn6Eu|*G#TWUSI<<6gu1U~JWW?RE+1)lCgd3-1!{-_^SWS%BAXRF{eASe_HQ%d#3 zIyB4x9zk9+FWN5Y_3TzcLe4UuU|IgVYkf7*(fP?BF|-D8aLi%_Nl7a>%MoPMSAWXG+H#0$dl^Go(`wIX25Kp*%3Df~$$~yC8=z(Nd}JEaBh; zruAvr{I4VYwT~X2QMT3*>_6yG#q6r(05!$+%l4#v7BCx0Sha(3IS7xd6A_A1DpC^s zv!^)RE=k~Y)Do|LSPS!OaVKfB_ z$rwH|rMv&kAE3PTf~lONydNKgG7|CPQIj5KyQGr9#a!-k`r+X?_rX)WlBw!2D?reW z4OV3Z^igojevyQ_?fUy)>23d48&h!XRNuAr5JHA8&4LbKcNVhZe9uV|FR#)-57TB< zc~JD}2wa2bkHu2AZf|jr66q{%U!7tgTwZc;m=lLL%AOCIJz%AizuliO9nkT#$)67p z6Y9gAbi%(C z?ys&wMhQGG*0CQ2q${_4U#8H5dxzAjvjwX!t}9>!B?5f>a4IFQj3IX>p`X|iu=^o| z>U(tD)J*0@L=Y5H8+Xu|#`TQFFmetS2Szzc)Q?=>YpT6+BxX-10&*MOt7Z zhqV|N$-c&)4HlXv#o6)D*7>#eU1cYA8ju{PZ{q~Ix;Iq^i?wxSQ~VdkhI^JgeazX^ zCiTjM9#U}C?(pA43pJBhj>EBfipqy&mV&MZalc!+Kp$`u*v63Algu`1#Qk9FR=(GK}WX`JNNoB+l1 zdOshvI>To()ts5kfAFAF8bISU5YiGM1UqmrDO_Q*7sSkGl0Ne3>e3fRs5q;MOLb-K z8Zysl#3TsBbE|2(_^Q9L6=xS#M7>pf2ZJ$#pZaB4w<5CirqSb=I!&66E-B-nRg%K0 zZNKo4$!#Y#WikvtX!F+;UDmjqm{0Fs4Y})W)TA)zM>!1W%%Z|<8T&zd6Hp`qyu><<*Y5e%lJWI=_}|NZvdP-dgrT0_NFc6=fCE8uR=hwda@vQ zGE>5TUKubtW~qLumf?! zBd@CBUn;8!xOC+~rgfIT?^!(a8$O7lhci@O%l4ftEqoOd?{L1aeZx*3GeHY2z+^VkKRSoGW1#3e z5Y^p}SWBAkaghmv(+sLkWjxcH_85QnKxWbUKN>Ou1lq-(iaR2t|QE` zkRyCL8slqqyOn=;00|cRxT-R6Bx-(+r|Sk%Uq@uwLY?B<)R5sHMn6;5n`wnN7*pmk zWL-?=&EqWDD!I!3aO5Lx+qW`(dK}d@*1Z63QN}Yc;H+R>VX&&T?ztnf!l}+>PS{SJ za^9(^d*U$ro-eo4GY>PCb<7`sVZfp_JBC1Pi1)XK#8<#Rc6{3VtNv&MMt%|rkl4QW zTq-nJRZl3$cFW@UF;dV|RYn=rF2ly)Ep(}(x)C2SOgGj}%Vu~Pc@%uiX{M4hjn`Tv zLnI-&@9tEQbLT(cY7$D$!H|% z+NA$qKzhjPxE1a0ge3(tE=rb+qE_)i$mAy^OHC3hOuhgHrShbn;Y_trWCos-b#xN9 zIQM%_1I?^GHvXU+yZC#Bv)p&p7V97Fp{iItm+jg}>Lj~#l zfH)g`D?~>fi;(5ex5&tlN8v`tz9OA*uzUr`L=>nT7XWWi9J>edf3cP5F^a(iP6TH@{RSMw>Ne!eO8J!{E^O%{i-VWsBRtQYo`re~1l9W~>>mt4X>O4fh$) z*3@qR0e%{*N-~c0oG~5)yJ!ndk01_B*Jhuo+AggP0Ju}1<1({fmNpZ2HJdj-h;@y5 zhy~b3sG5Tn8bT$gXt1&UZHw82704`7F+qSytx&W7D^RDwJxm4pdkg|+C$Y9KKbmo` zGiREFy^Ny=^^t}qf1%qsyt`mh*9|wAjiBQmd$Z;v{cASCnj9YRYnsAeyXe8qC7u+p zX3UI`-^$VGu0B1Cz+K-+FbAc?nBmBlk&Z=+wk}-X?LrgHO&yVQQ;{ht*4}8I{@puK z5cNv5;8SI-!l^Uzukk~-c)Ulz^yTBccGZJr6%~9S`w$1;U$Aj~)~ja+mLz{-JLm!* zr@&goV_G|~Uhd|9@7VMd5T^e(f0gE^rb<9IxhZ%W$6teLMum2V4ERCj`JY;M?-Y^w zku&YHqvT7}UvAVuC9|kW_uUPf;Q@(0Q3b!ty(~4>nPTVA*1jW5tc1g5T4Wn9`NjZ> zWeY$zU!l5Xv{NmPdW?vl{&s(QP=YP05!dKVZ>z9Zb7k?cR5VQRvMLuozyXaT7dd6+*=}E(?w|2_r2~3TUs`2&sr>KgVWr^DmWXrO-MIEP~L# zYN}gw`J=YWmnI=fgLanxy#vYAw1rlWu=pqH|oIZWs%+Tf1xW zy)@LGm}pBO!UHw$Ug;_PENxg#`lhPe6Y!KLlLt`DQB-%(PTUg>60_YD43zHOKihS#Tl&s3Ahp-&u28Z~(b*Qzk+`SJsg{bBw3wxQ*_6oi zUmRQTh^bNxwb82ah^sOFZkYkRdj?%y!!?KBf%V|1v)LAM3YK=CXeoxEm(`*oY9dCJ z7j{e|J;JaD9X8ooENa){^@Bf!l7uvD|5i$KbE?L4prr3oj}x@pd+LLl&TPM-aF!IpeW${V z*}HG5U5DMPJ8MCbR@cG5%s$vMGn#p8ivpFlSXRM(EYaUng^gU1YoppP8CaIX3pN6L zQ>_yb`+hu2-?Vc2cXOc`oC%O5z>`tQpHnIG4@rZZRv9mqJ(~&v*$E?l()a<68HH;9 z(fk`oHUOPr8IS2e=kq{8=K+^=1;$XWfbyg{;9CikVIa!claAAQ0afvcQsaBj6Z05a zlGmhkd0@3;s4fsJl_>k7tqkk&xF|>4g=MOrlM86*SAZ;0pyJ=)pd%NUElIVVCXp?m zTULPB~kkZbo#_X;5|#mcm58!kc8xkfm3 z+R*R`T!{Qp*|`cb_a4)^4F#l=Ye@oS}rW42@X5_4!}&6l$npBKFM8sV(q22e9ra1 zS!)wib_gtQGjpVy{Yh}FkZm*GBkD&F;G>k*3Yh0gc@@wt;o}Mwr)s6bQ54bZ(SKJw zeunU(`tBZ~gxlVs@*o14`lz$CkjAu@m}o#Tcj(0H$zwco zrDD(89mGL^>j_6tBO%mYJd;7e3$#?%$5@fg1pLXRh#MD6Tna37ih^=`tWTI%ItLwM zzC5f&oF5?D7jy{B)TN%yc<8PQ7)n&tkcOigmosz6<$7S|fF14iA3JY-&EKck8$lOL zHzYE1Lz2N+fa!eA+$Y)*rF}}l1(8^}dDef@qU%&8<*U?%CohjZvyhoC;POj%Z5lWa z(T*1ZZDp;%;ZlXH8IN_5jQ-%e%&@YGN|)`z=}o)`5=c2p2*DSeOU!v3Aw9Re<*wgw zKvJ7u$+0-?FUeQ5XNjtDHsAhCqOjAOp5Y+NO7v9;)UhfJ_oE&>wi9$7VcC*9h*g%} zA8?iaN<|)C{rp9B8MY4n;&C}St%gW9xbRQJla*xu+5%0++ls5z8s%eE0EBB9gvV`KZ%aVAp31>ZDI-kv{b+gb$Xc>D1*H~A66F$B$NuSKqX90jF zgz*z~syAMglx?7%kxeftV`4FH!go_vp+hm#!Ib9 zwln3o-_Nw&V69?z7deK}D@{n+h6z4i?%;6HG}Qf6O%<_eU{xe?{T}1ThE&8(i46nn z`W4kUz}HLoS%XJ%thi--on9F6GU&oWOf0GUMK$Or4}PYWqEqG)sGlQG9Lz{v5jtID z+f{SM=O3p~K7up?l!0;p;SZ3B>63S3{ZNwcOE6{1n}(JtU+jPfFSYtvED}P( zONjC?%94PbkS`;}ZrIXAcE%phWva~sQy>)T3PU>YErwdUZxF_5;!Dah5_52#?oWD} zkK>H~R1{!#xOp41iU8Ov-3jA!?SSvMxws(!4K+3wzN#FxuAJ+CVKzMRfM@d9bkn&+xf$wcy$Lm&2E zd^Ns9tUsS}2zF?Kp8b(tWx=Juh^hF;ouAuo;&g7_N*HLdsX&<{ijBy6DT03-q>1eM z-U;79Z~c}U^>-Lf(&0|Oa!mGS1yx-dg>(s{ebYT_9cfcD;Z_`a*F@;qdY3yrc?3~P zzXIvR^-)&KZBF#-s=;ej(l=e8Oi2zp^{9|P!{U>%Zykj z^(@)PC0x}b(!=6#0DG)XmBJ|)Dn?+zmAf_!4NjwcKXos!{0T%>P!b15I*iP=qC zEUYH(C}B&W+%9r(UO8PYDu3#ye(b($zGP^3L0+(GHi|;GgS>~$Ve5;ayt;O#iS2j6 zCJl-+XxR%G6&ku?D&w_Crm*#>o}NTVYamOXn|{qu;dzy%C*$HR#CSPPpKbcjpkG;* zKDtH4pbm#Gi*wUWn`@Q1#vxD0bN;l3{PiXL`?q>1#f9eiovi zFkjNhk*(eJH!RrE#!d7Y7yGJg>4TEbt5$3>{_eC#qag0xJ`q~UrPv6VuCrG!tS1O6 z4`qDn>|coKay#k0kqfjUy{oq$!ABiJ7o4jR28sE>)BjDbfQ;+zX#pF*TH5@mPVGF? zgPg}jS2XlwSPqgM{C#^s`jd9jvU;8JK0}~lUjaW_6>>3!z5Z-TrqbmzqKniBUR0esJIv>#$l$~cCmXfWUbkc26X0IKD7Vmqm$ z<{o6oD{!R{n#`L%bt5UWOx3YT(Ws2^w)Gp#yG#}074=y73P*`_LC2vsh>;swEsVX= zTUOh)FZjr{Q@Jj&G<&sZqxBA9dlbQ>0o|g>rS}XfzdcVd&v8JX_y)iTcJK%%099Pqq$6Z>8FoO;ne8FPQ0)J=D@c28hvy?mO=bQ%cV} zu-YHjMbgfGUvesE6$Ct(d!y$#4)(UfEqKb0lv{UbfHbX;L&-p@f=&mN?;mkiR*6=9 zUr8XEVSKl=YI=cP2OK39q~|_kE4bGIF{Q`^1D(OFEu*hGX-pmR-#qwUUsBw9h4bm_ z;yzcG?YF$*;d7_1zAf;g8xu?p>#-iaiqIo?uVZ@QdWo*?M`La@aol@*Cutk106wCX zM91h|wH1<=4*Q4)j1YCF03fEjm~CLhAw`lbe8`c~c_Kwu;JcXo{`j(g^1HRn-b>k| zXdkDD6PGcJ=UiQymrB{incDN(XURf4>A0Vj>sMBPZ#G?WC!`aWvsdppv6Mgv1BJ~_ zg0+Kus18uD^?>w|VTXzIVcWLq8fBpkoN9a_q9^IJ_4;M_Qe#)*tIoOv6n^*yE~QM8K%K$tT#q4lRfDMVoz{e{W=Z|T_=cID=G;@6EZ1-SLFk9PG(T6jPaG? z4e7G)N0b0<4Joa?M!|~i-Q-eK+fpgrkrCmvHCpqu27VfswR?xnC|W0_S`GzjO9V^2 z%D{-(q23te7b@hUq%`GI?BZo9ww04NNz7d*La&jaLt#Gliy09r5W?PQ;z0oO%atjQvOX++P{|JU#&q2k0+40lV+?NCCSD&Z&sK&tN61MD`@U+Nm-Bji*s%)I|i=} z&hT?Xua1X+B#DsC>U`GbAhwmxq~lA<7NW{eQZ3*!IB31UD&A5?#yq&|&Fdoa$3d|| z{ymz=aWSVyFS&va0wXjorl}Vnt=hx^$C=H0z6z@j4cZfbGpi!RNF#1aj&oYP_-$F0 zHkaO;Kt(pxGbV2K$z6a1$0r0lxD!FGVKqK=q`h#)iC4mDAXPfSzu|}p%td(Ah0;X6 zkz|=wDNmqYaUgHG|3#W3>7@A-gNq4FSG-Ylv79-RtRgaOaaai@Vs!NLJ6~d32y%uUD;~2#)mHnKcNH$EPq1iJ{x1C9{k1n;_zS;tavS^La!q8NeGp+kOp_J zs@eSS$V{45jX5g0c4GR6Q#e`ntWb48D1Jg? zB^Fc=no|SIqGHI!%Q)M96gvqu>Did2ad|DDsI~a6caf~!U1nxq_#GT!uX#l%RDamL zW<={|oN6ryc$&Mn7!={!;&Z%vMhUZoqJ`_MH>qQXJ^=4!qy(sVAN-i&PZ zua_97>r_i7@C3*r%&Tgb?=5;eiq)iImi`y0yd(WN2a?Sx(z8pPfD<|wYpG?g9Mdta zW#c0sMotm%Qx4^}wAbef@28PWuNXN|)#Jc5)u&q%C7Ed|+5HI1rytb+j*@~Fp{i`- z{tc>2%&HOnZVD4=s!v^C4vVj~iuVoHdqkR*KDr9N+W15j$gX^gISmSssAy*m8K+dU zN4qfKEfGRfa=b~#fZVb9D#K3W4?4{GYQGqOc&($OH9+IqN6dA?OdiqQSw;7df5mod zTCBOo8VfnqkHLa0Ka`f8kl0?~&O?d!=g1wipPEBhe?`%|V60N(;{06CMsN*15}Fq# zG#Bg4w8?n!KKDq?t$01W1FQm%etoiU-O#HyOGkw=K+uoooCSq*(z%GcKIW#53g*an zU)IX6_tBGXOiT4%$%*~Jg_VMHsoZr(t?uih2WwevTCF};mPd!#YKSw)PP&lRxNjfEI(D zG<4hvAir_L{!q6v(#G9|T^|4UYk3qBP0%wG@?@kVFw=_a;l$6lw6DRt>&As5Z;m~^ zDd5~Rkh*I-MdJ)RSoIi!<+@%aQUyXG&pxh_8*Yq5u3>9>r$ouWp_}i|r%a&qd`LD~ zJV&O~!po-92DxFCUOJ)g+u{>Dz0F?ykVJ#zT6!C`F*6aMkB|bkP5f0Cat=U>_sQ$W zrFVne1*R@9rUW%sOcAg3?a?ZhVI+GN(f(}EB@{50-)trzpfQv}y z<4Ei(0&?s+K+ZFGoyw2aM(ZQ%KT-6URR=C~dB+MZr7#c9z z6xr9!LyAoknQ}!u8Sk~nIRUl3El}Hz^DNOBKwDeZE>*ZZ=HuaOQ*;VT(R+ANQ#1MW z2Y$yhOvdP6D922@ey9LWT!{Z;Yt;|nTH$Xu>&2~FdY9kf2A~j zpjIniRU4vZFLD_N=c}ac;Zbh2SzHxw2S4h|UGdnNUMsv09TO2EfM{kGKvwLS)cyiS z)QOUFl&hpjY6?Zu2plLkoZ_vLb`>0;&i>!z4lgkEC*119G9kcA2g9BM4QRC$>N~(O zn-k1h6#CTlAE0WbO|2U`BxO1LsWOV4M}1UPkl<_1I3UJZD@)o|Y^6&|gaWj#1HV(w zb)MkoTfa-kPZcowU>lXTpg+bhC09LV%34Wjv;@{En-x3^q{3q#;_AFxhg+}qwX>!H z2DO2O3*SJD_-Y|fbazU2?D6A>dYV8GJWFJTBbEH*g;WET+K*D|D zgG0r6CvL^+H0x_c(oPQ51Y3$A>?~AzeTy0f`R!1cx)qxM#@);3ZiWIN|_ zMEE**9_~ruosaRgJZ*sKSrDAhigRdXgOz>%QfcMNMO(51@RlS3S&w2aU%K651dzvK z1R#1O@;31`w6iZq`bzB4xkV|uO8`KC;vorkuXk;In!%Id){RKpd2C97#QT*6fE9t; z3wds3Xq;FM~{m*ugoDEto6OjriGFt9j|GzFX_5~5Ue$RLiC+^C?Cre8vF zv6itae*+a%y#S?SOe1|+-dW!=AX1u>uxUv{@umre#6^;u+E;`OBVL4Z2Bmna1=)gU z-xT*$T^W2dA+kS;4PB#aRQ}w*hrog1K>ef^y}hME^s6iudGUzO*{V6|esJBKcH!>N z*akWAaUJ2mN||5b9U_F5Q20Nt3K-;1`C%=Ltbm1*!DZ3*Oow zE|r57|LETx>E!x#5KX|dZ{7?v>(v{{a14NqKChYR?4j(Vm$Ib@UFv11%IEQjCKDiD zi?B9~V140ZeL5XrX+-pR&w3bHE%9)uU*)z8Nn?K%C?rO}-vH69S`dJFs;jH|G`((0xf`YQVv~; z>TV<_kRcQ?smZ56ApMRDyPv0Iy(0m`e3ZavPC1wcf-V)gSC0Gpy22aPawQy=>Z`a( z?`*W#uFf_F{uj$rI8OkkKy=Q+WN60AD??!`_MxA6)l?oAE;8iJqSF|fJW8A21D5I;?fL}&Dm_ank?aZL*urjq;dFyh_N{YrW9x*cF`@(>ars$9yZb5n=DBp$jaL2|0tW_a{WE@ z5Vk)v$q`S0CHV=)u|ny-EK!(w)cP2wO}%i{%jJt3p(WbzRdE%iW7BBYCOi9O;NF5$ zlMuN5mzT_mAN`PAk|puDO0y_rnzm31o1vFlIR{<#Dnglcp~0M!Z<#B9Ka0K?#Jc7o z*Ag`-gD5$CcEmmNqOE;YQ0(l_luHijot}nlvXfP(@ik+mu{_sD( z(SFim(gO6fY+tddyHcCXY=s44kiBy?Ari!<+~=Y`{;Y*fZziqaj02**a5;%Uptwyp87}4al_#bd z4p!^84QJ%f^1AE^gs4N7OR^80Kf<@mXa8c_x5cE^sWu+1g9nxp7!AWb?p$}D1=k1N zsYIe=ah?<7d+~ylSV%*-4bAh!mH}8wwuF0Mn(p^bRHf`f00J8W<zRn`w#(LoLn8m}$3anV`c%V+rDLBt3N!jwQ7W~452Ga7*Bwv?ulbu`fb(L{ukDNh z#+MjOzv|)3L3qq{a?kM7CT`fd{q3UHzWgb21$6O)O$X!Y{=aTgwDLINpS;Oc&+fvI zte4*XqS^eJO^39U&`f*k7F)GEv6vggd|HFaY=nUcTJ)Epl{md}IUopSehyG&M98A| z0)v+5wEI~x_t<+_yG^^l$y^E;QLr;e9$28pIu_CmNbm~@1rad{KZ7wUZucz!idXE zz2mEi_(~Y)ilZT5y^H{Jf8Y*1?=SVUz4RTdf&fpsnSvI`c-31qVx;vp_b?paIh3&X z`|?T5(!|%jvQ<3zMev$L`xYHk25BEV!&}tz!QK?-35lx;Ytu~mU_tJB$2tdJPr&8K zE>#I%Ah77t{U*iY6jxC5Si(--5Mkk3-gAu*l9SvIC9QLfJ*!$2WlSgXU9iVT9x*UF00c~&po98FDaacDPsE&Y@FmO_f&p|W z6}x4kTUN@dIf%4}hug?Hc)(Iq3X-=Bb7Bt7ZX6wswNteq3nK2Twe1xCbjO9P<*%i; z)P>~E@BRQg6&DQRv1ColgpvEKyY; zUdQ)w61i-CD5zql^kFawcgV|l0oIPL+I@^ty7wQ$ukpv(P_CMXvTwx z1k&*vX<4y>KW0^!RgDo4(0>^$Kyv&NVm`>_hXF(XZ&RMf&jTGbj>gU&D2@oTp<&N3 zQNX#dNNY2IFm(TyBv0H{k;Vemrs>DK^+F;Daq@v0=uNaOo2iq6F|4GI;E7vgW7#c8 zap$@Mlu=gMIhHmEIB5(_dwOprD&{>W)f8S2^OnvhX2i}fL;%6TRPbBgv!ntCo1&zI z(S!oDA?wf8IzSP$g3Ox{K+;k27Y(96j-Kc5&Wdnj)TPa@eS& z*LV}2kGPU>VNxiYc_v(0pG@`Nmtvf&96l`i*NXtn>DS2WKykM?dQQ>pD1X9xay~a# zjcE$dT(Us3Lb!pt*s!iu?l5TmH-+qp9ZeQyz+OO)6KomyZRy82)N(}ZXlz95;tDb= z$AVU>J7BH+FNh)r!91?^2$5fL#9S5;F~qcwb6(Rw-A(_uSFg!Mzb=QtKW3>uo#!-6 z7RR-}Mj|t*eSk0u!jI*LI2#fhuhF^4pWQ;T-d^t3Nr}xQy4#1O_-9xd`v4;SFqhKK zs%korQ*vv-$_$5j;O|PWt;Kdr6E1n&oh;l-TVl@z6WS_?d5J_4N~SBm%H=3At@K+{ zi99rAC-PhT3v6aG-sJ<&wk;}ALsz&3gw8)Cb+8ANib8NeNT8#vE!wKbf8ESkUhyBH znlQB6B7Ac!)9kv6DZtU^FA6ZDtFcuWdQy`775X~8c2&%eJ#=Qo0mBjC3QDW~gL->C zLI?-B#MXD6yOHtivSGPI8MErPr0Q#PBRA$JJS~83CaO)%R?{kb<_BMhShR~@<>xon ze^P#DFY~bD z!F|NRxz(<9?|0-^MIvyCS0>4v8;aF1;k3$kctIHz5hrB6)PE`VlwUFMOk!LGWTb?C zZG~`%8##Wa^(79IO1&s@(P#N0;v+)SIctNf`GnQZ&zy}o(6-@HO&p7TrHG?c$XH!o zTLnRuDgB|~ZUA&G2{4;cVF}_kwZaj=$Lz!cZLms%aOQ3B{jvQNXzBGb*0q)*eshy> z#S#RuY%U!J>t4~y9`fPZyqqd+M^?{Bqi_&LL?mobtNDx9II+jjn}d0b|MfNd zL!m3agZrYCJ}v-J(&7`jd5=S%7vSl^di&jY^r*!>FR`$X^eUnx&<+)6M(r%fat&MT z(B-KFB%&&T3Xca`VMtB~DZDNsd6)(&sKSr!pp6Hi)zWXf-Gr@-Ul-Jmst;_Uh`>az z;HuuG?MzlAKG8$?DmcIUwFjwa!oolO%@EO>1|#)X!X%6e&wJhRNs#zt4|n3oQV7CB93jy$cS>@ff&UGZgv%}|IuDTI~!3jUsXt~-gYD(HY6FxA?p zzvFcr9S1%Ln{*_v>5YqVb|`#YNxW&&S^I9RfOSC)E4O*{ND%_4v*+}b+O@OP7CkcS zFhpScIT_NlQK-Vn?R37w5d?Mz4<9ZqJD9TUY$B2#Fd#I3@5V|y!5mBK5ee|8Cz#}? z-H`fgxl}p6r5f}Xlt+nc7}4RKK~$VD5!wr+c;*$9TRqqxjP!v_fC+ZJ4!fJ;V_ZMR zdzaj;sWbt}m|N(=aj!L#{-?Vho_#Ro`TRc7W7%cN`gm4d^1pQX+TV-?=Cq! zw6K}eaj5$Rd~b$*FJ;3B8UDxCy4m7urKdhZ)VYqW1N$8M3_Q;$f_Ua1z}P^Z`p|23 z(r`m#1IZ5O7(xdb`sXLZQ}UBDRFyUKj#_FKM>@5NIferTIz3mBWTXBmEcM%CmC?1( z;XbQFy`o&}7V314ql_9yZS`6&eqL(Tt}h89Jq`ppuQd~C3y4CM6O|H7A`QKv;heK; zV>P)fLj#xxq1w)id$l0mW@kLo=@U%CL!Au1ZHO_)`o}n|SAz{gzQ8IK>wug3Qt!}& zkYJ}}-C+7{U`CPz0(LJO*4Tj~Ryl@5Q?7!R!3DfekH9nZE_OgQh|U$Uyx&^^8(sYqnCoD`$Oc9BwS(;c7ghbEC_vqa=Ue@`<19trbtXXjD=I22rp=Ip38XljZv z&F~P2YDO^w(JTfd)YP@Z^u)F zol~vSn`_vX9aVa#_nf#NM846Bzo8X}J%FPw`TdSmEWN1S-w&rqb2m&dJ-#){Ziu{% zhNBY@cOE6{OWX9x(!G*jwD>=8ev-H62hyAvs!J?0&55Ct6t8~S9rb+Ulm6=wwt>$lj(Am`-?{BIT$Mg~>)j;6ey^Rf3U zw=ObTRBYfwHO|S-lF11I(-ZvV>-!c}W2vYseQp~Rtg1PZVHF7g3LEV!R6+0STLbY-c6q4r{lVK@mOQfQetX;QrY=91N0~nxKojv0)2D{p< z3s5MxGZKN<#PYAoj=!|N?67TW01Ck{b86z2j3Om11x-v+UR+po2_RD@L`JZ1s11Mh zMf~NJwS-G>W4L*SFwf;Iz8de8f(@ApKHE`h0AD z7E|utO3LoRIzesj@U{qFXAeiDaNKTQ2kJIkgGEID=>!cM6XpBV$yA;m4Nz?G09~*u zQTM=^cF9j}K4`&DY!i>bdqnWsTeiLN??P>&rlos39bNoVV1&M9dI;)-1A_gN3zep6 z9s*h&P759~()dRJPe8E0Eoqata`lk~c|m&ZDkF)deHp!T{g99?QrY5FJ>tdtqV1DO z=Vy-T5Ss)$rfj^fJT0*f_5kYE57z~3n;wJ&;#lBZfYG|ixc-3-z&okbYPqW4;~Pn3 zTS^zDk11R~oLA)o*G+4l$~;$)&RLzEXlg~-GAS`pjao-IBK^7Sc1GUx(@wi<_JBFQ z{;5*zh`2(HpB?xBUR6V|dsl!3PnwvUM5+xWIuyTavxWwa05(W`PHp2b8G44#@xH|h zrV@-Iq&^PCg&=1qwIp>OZa@ z99;fh9Hxgkpg(E20(S3fwq3=~HnH}L@;uebRE&c7!%nw+7+S)cckBLmjw}o~W9qsg z&RC{R*4srwUhnyY(nTHu-Y*O5twuF0lhrS%>Z=f~y(F<@*?s$)KwVXcd~qIWq~>nv z>JsjZg3S~NCR4s1@P0}+mEK3xk7zyw!BT1A9jc2Tts8e zO-*4}!t8XE|A?&uUg#Uvy|l5m&>=ao$Op z?*(iy#wlBiXtBJ>aX+0j#ew0`o*{=lR>XO;13a7g0V0$HZc>OoIw}7c00oIm+YXp~=?b1Vc{VKF?69>_ zdMAXA%edLJDU%DvXbvjng$G!hD9#<}01`l2da4~xL+X#i!%3|WQa;_bKzqAAw?Zm) zDI}+twi!+4mb+~U3*{ifz2rsde_q9(N#f3MARQMS0tB@w>Y$pw9J|ndRWrKgtkM4N z5UvJoAhm`;6l@gt`gT~`tfm^^2PbXMc!YpV6lF>Qv~=amF?X_EyYcj7+Cdwc8J4&( zvrVW5I2n1CR;t6DC0*aLk`=JIL#!bg6R7^k;_q7KU$T~CH*TdA}HV=^Vz5Pz?>!r8kFlC({Hnf4U%N_VdRo^&}IuSorNVEdz2S z;@v3TcOKO_BS@7+=!b2;;vxm$*=vMpw&0{9DS<&_j-4FqPKD(oug9Y$%u8n<+5&f* z798y_wQbgOyRM+z)SkczysG!zu~2 zFPR#IT0W5)_t`=kgW5l2so;l_uwP?((|DSfOnzU4S+YGO&`X^P%dEFojhT&ll( z+xtbtTT4NzGUg8t4mG|?h!7D&(UqacSUiJBw$sfHoynK_KDDlUd-6;5W=D+4wfII5*lMMQ3eLIj|rJqUP&WZ1NY7Qd@Qr}}|F zRaDd2@X2`k>8jCJWPgDm16cl20vCUNh8~?1ocFJ_%5f6>Lrzb9IvEsWHwy19l0s0- zT)ZSrMJ~N9vt_#$D$VQROH|Vt`vy6Mwi}3Nv6+nQrU5%ek<`%obUS2%fYNN=#!$OE5 z!`0lvche*YfYeYN0#5pf4DL{bW@{FIU3F9cY^A`?vX z5-wFcKqJplnK3q81YH%A24nt;HE0LAy5H2VQjnRyV*)M+rFK`RUTnz2UzBX2Ec0Tg ze(y6kvkQGkYIzK*#>xFxDYlfQpJH=LpcxIS?WhYgWJU_}HU_uFL(ssC9T}|Td#Rr8 z(-*Zi_%}(FPBOy4ydMt0#;>voKl4k;Lt17QlMB$^J*@yNw#rDuU2=H7GJ3KK>T#US zD4{E5JGhg#{<4)mpQ}g;8HlVHSre^ZxJ@Q^V=vJlg5#~O#Xe9GJ=>M)l_!{^C!n~4 zBvWj^j#kEq(bW{C@W5HBH#@_tqifj&^TZN+=o|PK=J}3jL+6A?AM@>DVr_!EsfG2< zW$~)>#2q&NFkHOWv)bZ`T{_zoy}(O#n(T^yPX1;JsW_S_{fht`hXFG%5ZWBM;X6(& z>c~~@f?&}6B$)!o0QqJH<`e5kk>}6pR$^H-V9CVWBX#S_mtjB&LaU=!;~19jCNT8- zdl}#XE`&D;88jsRI|*W+c6pX1PMqX$MXA%dhLW69Ysz41&=(Eq zBzXw=jA%o4gV(gSyGaWt~@fF z1I7XBwf;`z-&ml&06dx0U?RfnPUv^XZNZrcz|mbWqP_!)i01#FHn$5hnxz?0)ZaI= zDDG0@k3pbpa}7Zi#O`U?pcVp+Lt`L4jl4(FDH$UZ7$cK~^}M|CCnl1eeSvl7!^yjN z78i}oqNFCJJGk&wkn~nM)EOvV{wdpBm>9L?PA%;^d*sVitH!E>57`d`f_fCpACMIZ z#Pz_4^TW6dhP-+c0jznZ_B=12NG;B7NG`Af(S=?C5X1?Do4H~PdRNfnCf%-`zTc53 zHS~i>sv9(}uqw>`Yli9Lrn~gb$Wg6}LKFdXSPB6A#|s7iX8aOra$^IwRW+2Prl%cw zYgNRy;+8oKkxABYyw1u}lMM9P-3#MH^&F z$oC&)Hl1PEOzbm(!LhFDkF2BR_h{mt9pK|HC#d3Z%`~~^IcqRlHEPQ4ML8C zL5agOK?1&$JjAu*%bF>Y$kgVnmtHoohT^C z&eg&%lYSLD@X|1bRTyYKp@xTobA%h`-)PEI9qc||Zj;DTs}=KOtYl_TN_!Uiy4s>6 zbc}v0a*QYca>xY0QxYajo{!aDm7ae4(FyLoQEEG7tgo0GAAbVQg}Fw(v~Gki$}wD! zL5oen=7U*C_o|FG9(iiG?=vrgc;A~N%2;crN%%G`i}tL@Yji;vD|CATdt1h+KwC^@OzRI-iP&jRpiH*H;) zqjiH3YHE`z?FZ7qYV@*Gu=a5GJ(APnLl4EPV}2xsXh!kx&e6^;ud&t%hxN%e+f)7% z_DgLhRK!_r2DMnq<(n*4ABdKkCkTE{rO~kRQ%1>rV%xO|HxUyf+Wlcoa81iw`A^g6 zo)5=)fco4yX|BW(_`Vz8Np?4ps)p0W;4K!nGEH_y^qlbE5V<>R;wCdfT_4as z;iK#R7&CBB@10>()`g^c)T$mfNbZEXFTns9OzPOE*CA#xS#C z-4uM?1n>raMi(obV6calXJOl^X zh7+4-TG&68a7@O(dcC9Uh}7+6w?{*OKc9PQF$kNJnTm4W&|cX7d&>fQz9WL5#}t zi&fZHpG+x%|HaR2!g2<#c-hk@j4-a&5Cc??=xc+2G!4(y>b4{FVj6e&j1&=(lVAq= zJQ}(Bx*^}BOhznMW@Mw)-M$b~Hq-)YIJEc);+yCMeay_HyOvc^30>J@B?T4|@;v28 z)`pPC5OZky=KdWkP3G3mKBf2!#>bf!2SW$aNN~~Fzj(>uN_s#?r})Mzq)fIO><~Yb z6ulsSV@UbN!g}sDPI@o2U*(+L>cEn;G}&H0>d4)20W*!j?2j;YRSQ7Cq=+Xd*jf6+ zhp$5B_~qipu8^DdsRI%$F`35(^nR2j`oAV*^Bx`sh{H(jD76>=wkt0IS0gV zb(0>tSZg4JuXv zB;l6av}KflZKPcYaK|iO83-{2OIsEk1zM=?K_Z8VK3(X@rcf`v#Ip+bi280~lV5~E ziW3Lt;A!>+oh|^^@O53f8p`F{<}dvv8lK;iw@7#F!YcCZXeaRqqNHV((M{wHU6j++ z`@!AkZDH$El1qT25B{1tOt{*2pZHM!LFIbt-z;dowU0PnVBQ7JEKn?dmI`Nw9PcDy zPqJitK!}#?fhh>1X8zom6Z>T%?dwTNU>|#(r94>|xxmb4nI=f!j2n_CJ`~i8|X3z8Ns)PY4Pa@rFl`LkzHmDCgAeZ6iHR zw}Du0sAAe8AEg6@j#7L1s6r8#{3gFEO6evUo9E&dacM@5fQAhcA;^FcB5fDOtG(qd zFtFJ}dAtVYH+t*kP+Kp^e#B(c$6d}tb2(Yh&9h^)OX15KREuH73o=ipV~ZLd_og_~ zA0emaKR^ZW2V?Ibv4O}DRYg1IxW5jbW}EMBJO|06I(;|r97i96AqYrcpBo8 zAcQvGV>IIr8MD z@dH9{oewmKbDJ_7l zTOvjUDfZDb&!}!2l}0Fm&58&htO)lZ!tx7o5-mY{)CEK*o=S5S%X@C=eZgub4zyKI^uDi+8ue@Pu< z9--U)TkF}KD0?j%GKP7o5X;q{vciLGnN+VtM z96)ROT*yhcBRHE0(1=XraUPU>RvV_hr{+pDypd?N{(E|3j#0KdesH+C$PzD^V%6>Y z2)nyh(YKm|SntE?aHPAi0nZgaCoS=DPm+X0Az|^@-Q#5;gEg2WQG)so^+!&-vv|o! zJIl}^|Dm`U+pdmD`k_jo`O71SSZa~Sro`#{z{=G#6iotw5W43G8($h{D4YAWUoP5x z^fDjQv_C@YD^|raZs(STz~Lf9eI7= zbO`U44asa6Q8Ac&M|ylWfQUqik~5{Z@@>dt&^?~aD1WOn4)(r8mUU%f4KfYkDU4Ws z?**<8IX&b_JcxoY|5%75BzNNW-33%4Mqo`DpjBce=uQk{TjUJg47}FAAe9;#T)>^U z^prA{e2mCOksXCjMM9SuQLPolah}OxJPGe0D@yfbbEmH8-CzPLdT2d;@qS>Wlr5x9 zR@?medCb~eWh}sApN4YI+^=1-g741~db4%djF66vbPMDXDQmO_;_NIxR`vm0!7xTs z#`4|zd)a0&iNt0YyilhF@Rk1fziWH5>Ie1}N~xnbK$Jf%p&$y+O>^y(vs5K~&q$TKQ3{aj^dlalerV z+xIzfw~>|qs!=z4Z*ue53Hz|dU!JPG&Q#L;Xo}oEx!h>!i~mXbydK(IMQxKa2jzLa<6ZGZxd3YoKQ57!! zsGQmzQ8UXujX<`R(R#B*c~vXEc-VFa?%qv%EYPvEly7ag*vg(LMG`Se0%*PHA_O)a zhD3RBD_%bPdqL5-f&vu@W^oH;gDua9(F<#YG)Rb6GMrs{n=I}_kaYRQzBCr_+I@NB zFjXk#Qw|*meW?5B5X2AzS$0xUPPKFRoogK|S$xTxjt4@A5SP%^i%1cuiHjW1704?= z@F%Z-BHwcmy`aHr9Zj6KKm)V<2Yd6&ip2VOR2$8@EZ{Auh{jzZ>Mwy@Y@wD5Zn;vx z6n1=JUaXy0b7T`DfLkN8OFobUYVr~J^7zoNrxm$O*|uU?)$DuAOrJ;$6uaehHJuDE zIf0>u3NWw~Ku{2~;^5upSP>sf*1AG;nYJbGA2$RqV%W>CN_GS+!7FoQXQR;P?66)e zQwN+KYZJBqVp(Ep?^1oGPHzg!4*4Xa27WdxLENa>G@#+@Jzf1KKD(keNdT1u+o!kV za?0ME&LOl>AG!89ILeB9NTm5}J}!k%egBCoUkq$w(A_RBE_W+hPU6L7?}bpg+-Iao zEq8T>79cFvbr*+H{hHU_|Ae%>__)3x0fWh78J(qMuvZKcikG5xcX@Ma{Lmc3 z+Der(*U{bys|;Up=&chesd&^y2rM&zQv-txtx_rvZGwpqu05AVXjkeABxGE?O!FwS zyd?7HZ4iY6haPS=KUW8So`jlq85^>gBapR;P@ie8-oBh2HzbRh#y=|`wjOsc@vCO| z72A%*ISR@xV$ea+$e_9QAAzt##>55VI-9tAg0wZsDO~hjr|KL#Zjc2?Y&3Lzv(inQ zns*_rb5JQ0k7rd)N0TN(el#6BwE<<<{ZtLqkR*C!EKYpx8Mb+JAe#&|7*B8c!YbxF zPVqR=C^q6QUj_Xa33;42bhHepgM4aj^ssnk(K57b2K4u9f~=rP9JEP)m%uY<`hgYi zl$BV+X;GF+`Y-mBbki=C^jQfbed|iZMS;IZ@B;n}Saf!bE$WqUT=r(5RgB^WGVMT? z4n_6{_9W0}js1(n8Zltas1XJLMYELP1!~bOAzgPy0Q58Tt)4NFT?6t({^rFJ-7&P3a?*R3d2uA#knq+v33S5FXI;rt4=CC`t8KAuXVqK;Sa=>{ z#)DHEqYepXDKlnK6M4WZ#%s|?r3+=KWsjuju0=NXbp{YaF_HF;kSYxWty7Plu0j9_ zFKmhToD+o}6FZ#RX!^p>;AGhvC?OkgbcP{DpZ9x_ejB2PjKL~hf87^h1L#J8>4T{Q1Q6n*jKh7!?%#1lVksEqMVtW>! z14%Q0VIksWodIi_oRpLwo*7)vbtPFwH6=wWqXe#Z-hJt|kY&puAb|kg2a$Q4Tp-Q) zD?)~J$KaGrvTOtHJ3gTuPL8@;Ayvqb4aB9QO@#(yj^n0q1<0QvNcA<9r~7aQJlw=` zr)7&r`E~xmfZ4O=g?|TyDMDd5V6`g9SGUd7;NX~aj%o`ue+)M38~oH$R}NLJ!-(0; zC^FnnF#LBo7659ChsY$(kzc}SaG~r9R%gSFMekEpitItuBV6kjQ2Qg9C(U;OseCJ^ zy(O48@>?m|D4}r%XDcFk{7wUK7h@1c_U?#9eMbyyXYBX%LmJ|JULfnm=_=xOb?u4@ ze-r3(5vE7$r^mBnM$4~3UfRMJ9xpZmWkZ*gN^a85lpt=!Un*@uX`UYCK2@!?m9Hea zALPo#=J^Pi(9FXLQg&!QiXq_1AmTi~NQDTb8R*y$diGW?8SX|ebeqjjO)haUZjFU-n6qNT?f&{!g-Vkd8ARJdA)wt~tnBpxTe>*4XocJzoBWbxXtKt`1HP1S{Hffr|JMWAaoTH8$Q6LPDovczoD$o$?#Uw+ z|F6@Rn63+-kdpqhMYp2*q}PGy{G4>ClNq6y=(yf0g=?&c@x;JI$}jlXk>DptHEoI3 z1@CZbPBM{Tv;LoX6@3S<*FG{s+P?>84E?HI3fBso%huvVwBTZ+@*5PF!W| zJvbCO4KDqrikdqNQN^C^kW8;IlSe_F0B7kGW!c^wt;u%TeBk|o+;Z9rLp~Vpk()Xo zEPK3;b0K~XW_MPb)31zQ0DaF&F7P?p_7~{}RP9;{y>3e?UX1&rUez|{of)K6D(~Ui z#M`IGFUZ%Tz4-!rJ5fc~j_=%BXN6<7u2dg(=vZZ){kdu59;YAqh>YBU_m;klvg6A5>9}bqYI=FJ z-l`!0Ce5w%eyAIWxC0$Sa_2Fifk1D;^hf0?_KhJgZsOSEd8pa56;ax9N3CN-TO|q` zfSb9tYN63F3o6H-p6?At;Ip@7y-4>)IERI?Oj~=6 zgnb+k$>L(*-*N8D!5^Xk86y^%) zu!WTDKPqzuN1+6VQ?7As&ajiwXs6*-`@|Verr~=+7ram)_*7&o@YqDA`Z8&lWi^b2 z)9o0$Mj`H-m9E?6`s5x$a7o1HvSqNTS&sMKLd}!(nyiQ_`8q^rW1Hi;yUl6kDc@AS zwEys~lYJJ}8Xr8ycb{D<8vGb=VRpfZ(FZxq9o00Vizb#O3P6b9!kz4s4V8*d8Q$el z;gab*f@eOaV58Si!NDm}5U^_#car5C$#09*P38ZhL|M+_+hj5W$AvH_@8->;O`sm6 zmB_q2H!Ssm_piP@E_1=TWqG;2Tqc+p7#r2Z;fAw#G}W076WStBHD!Xo(RduyH#ywt zeYyk!`4rHkXd3JMIUn9*Uc57j;0tBR#(N#g*-J;gm(4@;i1_Esz~{G7eLAoFIGbh{ zsn%dtU4ekS!pyaBArNS5qQ=g$oLjU?IrgZ1h)3luu)6BSD7Pp(;4X6L-qGc9B>jC< zuk?@`ATsLLUs=|+44`B!uX>Q~Zj6nG%Mv(16xS*$B(K#U#N+t<`id4GMr8fYwivPw z?JRk;Bhk%5KCyIcz>Y>{Kaps7*a1{1a2oKwbH{idN5DeWu?MM7p4D&$O7H)`RjBj! zbM|NSNa#7VulD)WnRyP;$GGN~^cv{(o8*97sBlg!~l2$6g zgWDgG3!oQM5zEo*Rq*a0UovQ>8Z=s9WP6&Fl%|clNA@xjG*K3ZwLjM?yL{Rh~BNF!;hK4#Kj}49%Bhf$8i8NRy5YOTT z?8Q$_nuusmo&}(0js=GRUY&%qf%fctvX$Kht8^#Zuo7w1m>n+U}jUoi4C3N7@2@-QH8$;C{lOGF$!41kVL}2v7K#6I=Me0uh zJ0HJ8^NN0!6UcI02P3&FfjeXRLt@{dt)pe=LY=33U#G;*~9Qye0c?vJ1NGZrsfl@*!5`Aq_nw5ZUS< z=_(9K0QnjjrLVa#a(>R9^B6bg?xbch-9emjKD@wof2}AZ*rFTx^g~`Wh-cu0#f>Oh z-(V@LBfw*0WxMrm)z5v2`WNmC4C76d3*U&;)SUKqvItYdqv$G!-j~VR4wO~ z1ueJ4<+eB3>Uc%DtPlszrTFg0yK|!3WOB3qjTlbf+KNDrMfhc@cI}d}_ZYJP)ePy4 z{lPY(e!)lg!vCbZ0-13N=}V`HILlDXM`R?$+su#w~u;rXgT@8!WS9M z#=m>X;u%KQWie9kJ{eM+!T0P#|KB!vL+FH-dp8n3>+f`FkntmU_RHtz`Jd12!d$Pa zrYt1!>&c*n*SiwYSeIs>*)$h9a5KaZhDe4i*(ON)6zrB5td>aHFV>9JF&*MZ56d#e z#5B;Kl87Qap`?L`h<+obETJY$l|`Y{x6q}d3!f)KNmXu#yGF~c{hz`ol%VYu9Ww-D z`ae}ACrNh%nP>UBjpgSUh#s;N{9G)yqwEY3miulJEc=I$VLRybYw}4WINP)PBQ^rR z7}T$}QZvemQd)z+=W@_+7I+&-*>i+72o^=}!BIj0>ZMPrYhgQ{za4NcQB6?D@@ z9&QWWUTzRm zp=UCO9^l)?_UCq2xSaV$R(BxM697Ydc5oElsg{R{i$h_J(z^UF!EV9CoiKBjr=UO0 zLqzk27D(Lxk{NnTa3bV8kkGFgFo|?a|9IX-Kq?e%=n6*iY<3-!E%JS;MiNQOsme`^ z^t*E7hV*Euby?X6+Ymog-u)w(zhB=NsELfoZTullB!Owu4?MW&a-A~=EVo*tsha5V zzR#287r#2ZeGSujr17C)s9sOaI84rF6UDw2X)lpw{tV=lGV*9efj5EqjO@&v-ACfJ zM*KxXC<(F%w!Dr@Dptb!1wI1gFEjJ``O_U7E=#plGeI&f)0Xn@sqrWtb-9=&?Csld z0}5@@BbUGrgd^mko{!qcu%p$063ABs5Vtj5r9d2xYqqwU3QciK7!bPK8rRTlNEGK+ zxp+O`yEWW1P|vT9_P&j^;nMQ`G><*#_15xy9UkhpHb9vIPPqo7f@8IFZ6E-lQ zyBQ>&zIDxp<5w||Fy2Znu8tkR&z+==WAdYKwR)=k{OXFXCx7R09hIKqY_cXir3k;X z;`vjSezi}~SvH$_(>^_b4WOF|+}(mMqu*IV%P4)R!tBEgLR|oqMpCki9{cx&6-ao0 zph^3uk*i1Wj#?}uJDCu5ZvqLic-GQ{u z&)TsPZMCv5Uq>=H&50!s&k(f`v?NX3=wQaw`@e#AjoTBCU~8=?L5EP2@jTccB4=H1 z0*f2Xx{D#+ItHli=SQ1v{SzeZTr8crM-;9^bX6mYF{y$W$-XB`^g=6Q@o1tmxD$S= zFQWR7(wJeRi9gj%u--oi$6rbRY6dNQ!B(yaW*7!*y`h-*heSAKWUaCb9``aFe~Mt; zPM_qNI+ZxOM^YXeu9H)Q$@TbgLd#he7=vLZIl+v#=XGmT(A;9f5cfa9%cw=N&-d4;oJ5k4qlfvmzGkpY zUCuK+otzZfCivA?a)K9%y`XeTD?T z3esmWs_EhkXsV-{2kYV&t4SyHyHjo^_h)mXspJ?+AK5+sp3?~FRr7rf(=e+9^OM!C zVXw=Gz@7KaJKzI7`)E?dugLtT%nLTAcTuAnQ?p^D1UjYD7r&i-Z|Oy&&*$_tF?LP1 z^s@x4_IP4*EcYBNFm^+)SHLP~R%3ThL&*hTglm?ql<4vW)Jq^K7$ z9a72wq@FF}7R}G|Cjhv}xhXm`E%xpCUN;fu<%hkFaBe`F{W`1(DdRO{kF99|`<3ec z{hceUahlf_+O!X*TpEsK&zJfqiok2dt}e-bCFuzdV0k!nw2;7Hz_2Z;1X~0aEG)vtI~!rTy~!vx<`kf* zS!b}Ccw1bg$ay4~a{d;Grjq`j+doxPrI49=hbw~z zU*N&)gw1TKB;s+K>=F7Hydy+x#jTv}sDn(RCpvHiGP{(j{xEu^y6Z>P^_wzS3ercs zI^MH$tQu}tD)X5Squai~7N1N3q>F9y0g6#=vhDs|o*&Q)ES|Gr*(hh(XpIp|Jnw|= zWC4S*Qt)&VB*|j@{Gp|5cZ{jiCyK;Qp8`h^0=C-bCu|y;!fd^*?4Wi4fzD&yh#-q< zTWlO2D@}4YNTFOxsIrb`jIQ>%9-k{U`8YxEFD%#<0&;D`g0_b?ryV1c(;(4D>b|h( z^yO5CMr(32_og|U3QPpQD~O5rjP-4%Gt&1<+kNnLIQt1ou*d+5BMPfLyBv}btHBXe zYK*~nPchio;nk^5|;!Iy&fEwwE;tw=a^~TC~|f4DeVne>s~?TLwuZ$g~lUz zx7_Ds0!)o5{hHte8}em9XZ*`0z#FC2qYseygbWy6fjvH+G^HWk$rN9xj}FeFIK~S> z%*w_*4Gg59^zijzxT{~0K+$~L6Y{SN7x!Pp{@JlR_qU?YDZBUj53ZHO7e2YZ{w|Y+ zL`WUK!@MWV7q4)WTHVwLX}?C7HOAOd=s&)Qjyg0T9WJn(dR1QtZp&9d8xi545bXRO zW!j90zV1dCaXfn|41pGWF;*Shmzv5JA!V~8%7=P4wQx^#@2O-K($vjnWMA%55XQAD zHy9q#xO@U&37KULQSL}L8K?NN&ilg&q&m?pmO57Jr~EQV;!6FP_DNR#j>x$2<5x;X z;5;Z5G9Xn5txTe8B#dY-ewfrQ-NCntfYN5*)uR^mbFci-M+{uUGEqr-aba~sXbSIe zKLwZJ?-n72(j+(M;lNs!zr|LEn5vY_?FzU6$ybFht5?LtXwl(XnD(J2L+iLj9K+eQ z3bxddNh3F6TWh7GC01@E>nSb z(Q$F1SUTpmyowyXN|dz_Ie`#Q?<(xK&riUrK98E4{^DA&LtrTnxg-WGCwKdZ&l?0-^kt|Lb7` z1_#h=6@>bto~POdsBPvWDAB9)bYTwKG&ll^5OG-}ueR&QQFNFtdw56~9p_6h*%n|T zofTY8+NqPILRcK5LTF?S6B^ZS7H1Y~74aBEjk@8PHpX>=rY_x%qXA-%lWTCUvO$>U zc2Hc*t)De({T%MgHT``Mxd^?0JiGQ;d0R1qu4}3T4s@+P+UWkR6Rb0DXED!`Ru>|lM7shPzD_Zw-?j9tHe;IQ8s~g zYxIshOj}WZglJGxn+RVL&!ne`l?q3OnexQRwi0*u5^J;a+@O_dgJmsl`ly$`tf2^n z=eCVqb3df{xDhz4AniCY-3olgyTyCouut8ScHTpBt+7AoM2MOMdlBi_w@GbF$+j^Q zG)j1_DQh_0!42?SqRmMO2NH$dL`v7$ka!dlvpy8>^yf=CVpy8AzU&mi4q$vZZ2eHX zR+%G&*^^v(mhg-!W;URbHFP#i4oW{aB(0@NiVBnS#?d!pN^`>}}juPik+id6L^QY7;HxPJlS)72UqqW@rAB2wcT|3S24PkG}-)DiAusa4H% zD!8BC_-_Oa(EaezGo~P1NQFiS3&+ol(p@9Gna^_U2(v|0bP^L3_K6q{a<9cm2!&sn zRVcxK@<&j(I8}oe;rssG8x~<>O(1zAW7atfFJAOo9z#3mY-%GwYy36cl$!FZ*VCB? z-1zjP<_zc>b~<>PLQX>blQ4X#90^q@cs;gADS+-$BMMGI_ft0b`}R z)g~S{LY*m$yeB(9J`n*kwaR}7pYKb&jjlFEs?e>Dwry?SI`YNp+#%36qB z0Ccx)|4Z{$cI5n*racsfc4?_c+U`lu>QsR?E|#nV^M)m{Z?)>PwFw~9XiG2j@1Cxp z9A2=3UL7*+$X&GI1HP4KQJEVXlnV8IAZC$`_PcNY#rLj+TxOT|&K!Hg5WUwQAaTR7 z=4>K81(6M=eoa|ehcotFp8mlPnqIB%ao$K@P9kZS0A(h5R7UVROvHN>1R}C{d^@~};W=a|ps>JUWUkeN=Q?g?GvE@vG3uk~0Yo^Zpa>*DPZ1cG9F6v?+ zf?=o6yc4+?;UGZ034vvC&|hOV8>wZIW`O&?!ILnbGRKm^!zr)Dqz!sva+gu+Ng+CW ztiVUs=u3*UB#Bh|zp2n%Ym-;ab-wGq588SVtl5^S8=hW+3PetSFEigsoGDHc@~EOt z07s|T%u8b)N9MUL6;p{BLUoJed3fwp^7LhVNn1RH)8PQOzmvk0vwG2=u?-TEn9*^g zd?o(LIO`&9kjT$WZN!mtlx8R+;300@&uzpv9p9KLBkFMHr7r&!@)?HL#(|=;jBtravjehcHxCwkE&Ds7 z$ajBlD@cU2po643-mW2-{o`K4U@$SIY;jk6dW9ngimQViN9;9=> z^x`WJF|QNeOL;dAXTsmuFRyGoezHYHC`BIpLxr09b7N|ThY)ZeU4yVWeAc?RIHV?t z&$lleb0kHB(67$|Ocsm>qP;&2x+?!|CpoiNRc&s^x{9;^hEYe!w193st1&$n_C|L@ z5+kF|O$W)qkWHaO;UUI!hEVa7J7oW$giGBa8Ci=JGLdSDjZwDSxL2I?ESm96&$2X{ z%aK9`WuW2sP6o%CseHx&b{_pI|6wYv`OWkB-GMGQ{2pD1n@SminWN?=cn6|4FNCV} zjp>~>@ns3MgxR2NbEhs3cQlU>al7W|dc^8Yb0Uxrn4Yn93|eFdoo)?sJmAbSNZhKG zw8rvZH9x}Zd*UH%rO?cNjdknfi^6`}Z)-NQ% z#yIvM*R%Ld+n^&m6Ey-{iP!m{8T?9BEv9vP-Qm7`AlXH4|L?MczgU7>G%{MNmI&J5 zJD|iSqiI;GdO^d6BwL9ul=n2Urn7rmHfnHyjohjBcCdVVlv_>F*KSy)L!`hxM!p2J zUjIrUn0PE=Oj6xrxdh!T&7>^R&Qj`Q(=_B zAjY?pqQ~GrqHG4d{$^ya!{0)=pfigvWIJTu-XB^ve-$z%K1Eje`XzF&7`a5PqjeU= z$#>P~{_VR=zRy_2yYyX`mYt}3XYcZ@$Ezr2xB5nIofeG!{tq;&KwQx@D0++?YwU(z z$y%OnWUIz;CDNHMUJd`w5WtJpnS(wsU3E$H4qeO`SL67{?uo^2cL$jA8h$mJTT#DNV~^@FJkGfLnl?Bw%u6;Fp3a43f4h(MuXq&{?K8WFXCcg4yIQt=h9e#J*4^#LYEvmSxIl$`_h6AD^Y^$NB)%3U zhHbLCs`kpi(^4**J)X6-m0d-tzN)Xh$7cQmKw9F}Kvro;g=Wz0}E858SVi(&C zmt|0BA6e;GT@0{PFvEsq2MMlR6@<{-QpBxPA`e2jl3$f(x7Cd-!&wF!uwLz7x!Zuw zR82aM|BJxE2J<@x=6e6)=C!&&zhJoTggFjgk@oQeBOr|0Y)bHeJLd)8dd*(r{~`Oc zaK9Bx+9y(&{83H5Udje5VVH)bw&j`-JLL^+*p5Bb{slA66;3kK^lbYv{k$I#$bl}* zBos+viQfTfw0ip#Eit?90M-FeesQjVJgP>C`N%9g1j#PAiC&}ruN&!vN1!=iEb{Km zP&;LB29Mf`R&`UwD=ptS&SZByU6=w%Yo`YPLt@xF`8~Sz$0w@LvW=0v#$JKU(!ZFK z>Ey+kbpzGClZ34r3mK_ij!f)r?6b|0u0rjSAKmUKZJUgf*k<$xe}fU%7u96SWRY~k zi!5X9e36W`l$1NQO`exkmld=c1?=cOp^ol?6C-cn)e}3XO{gB?-(zL0fRWfpg0ZI+ zrngvjd+Tzb?@6klc+~U%9n+cI>BaA8@K%ir_Q&jUS(e#+EbF0am@F0b#S})N*te!+ z%WKhBnx;Oy9MinNUMAg*!|{`MkPRv4{pPbMD`K9F>6ru{R%aVotP}J?+KpWdBMrVK z*aQRk}E!f^>5NRapt)ulk}-e);2?Bj?~!-B;&A=A;)dQ9@HFv@;O$c#hEx-0(p zwQ(%u)H2`sEF=d=^5_tBF?A+jFzaNvS$GopraSq_QKBq6)5f9EwtQMhLQV$<)YcAV z?y+acd_)ZKnLu(~(niZJx_gd)&R11k30Ky@&jbr*w*03xKmRe!NI782p@t~mS8v-M zEyr*v#Ez3jRlT=kU_SABg;P{63iSVFhhF#T+2RK49K8($-!d4ez}Y?e5>2=O9Rw09 zToM#mRH&}_0^0L44!?MRq^j`IDNlqOzz207HaT@zK1tB`)8Xo2A)>h^p0e`nSFUNU z5od-A0#7d=pF-U=aL83hB|%cJ6$SZbfO>u4+g$f>E2(6~!3bkU#%z*y)@yrR@G`5( z``Kq*6`6_&;R2@w*T^$&Igo}q6Kd}> zBymm5q&{T^D=}Mm$o*HhQcNpor&!X_c!WjdBCI4B{K!13TNR9?J`}6F*SR#;`wFz1 z)M;H<9PGs5s?LejFLl){%a6bhEQ6HWBe*cn%o`(H7ZWl;Oj8k<)57(x7_!Go{zn zZf5DSfD71$+&Qq04??m{(w(OC)EQQDlo?@lK0m{ZM;%|R@~IN!9ltPXEr8&Fd><;dC+B=T?6 zdeDmSfbR#jpN**sG>dkhVlf)c@V1U{pgW17wwJoJoByA;M0x`h*3;>ML=_w6Eny{Z z(l;8H73qSAe-?ogLdsdWe#r#1uFCPP-1|*GR=Tgd*;n%KO)nNc+YHhH8g9n5NKjVk z9oIsIuI*gPCMJ)`r@?v#qd^2vTK(tvm)GW$h&@EQlveuZ-YvUg>fGw98WW`(q(xvvBJtKdv^we2{f`I@S;&4T z%K{&%a~{GoXEyP)EeW?-6PLv#DFJVozL^UmPzCXq?ncNP%oXD>s1h#=(gk31X!t(3 zJIcVxzP9M5l1v*&rZro)tou1;tA#)Vw?u$UDu|>)$Bli!IJiLrfm{eFjOp!@MsmNLw^gHs6*{Fm5+ zMO+4Hn*T7mr~5ej5*qwCmf!Se2_^F!{Ec0f8%@lN#=awuocoMx{DGj97dgI?khud` zJ|rzG!3N`vQybQC6MT7MTObzaHKFh}COq09L*Zpfn&e?EYH+Buarb{y%aHBN6-M^0 z8fCm8m-K-|NUv0k*nSMAm}{&diRX>pnVJ z*F4tGYUsy_m+y2t-lNtw2176n$-25=sY2x z)GdGhui1{4JaeWv=4Ih6oP|G0sfQI%VJMsOFfjB8mv<*cnka>L*ZG#w1$iKaNHBX}d9TODA{n#bDK zT<+j{+-H`?YiC?OJS}oNPx1w*zz7GVqN=#hBX3N_0-k=`yZW`oZsCl1!AU$d}tKMTxL%0ZUXt)*FlxAe6`LQBJ`i2mzb?a|AIQ~|!Y zUL(Vn5q-JAh=VXTWn)q8GFiW=ErN~@D50#FUq}Bfd)SjG%o_mdg|$rqikB*$_vtNf z`%_Y3aRf1i3T=$5-{%CE=+kgHyBwh65^x)H%=P9t!) ztf|XgWw6+wJU^q-HE_2er>u7P^#1u#uu8vHC7+$puTBSFDh7T{yj7S`f))c#38~OF zH)`aGU-1gVF0BqYTW5Lp4#26LTu(;V()whriGHc%RzlhG9_l5%QY|sUb~DqRB7#@a z>vav|;IgF5PY|aE4rK%^2F}M57HicoxW6f1alnW&r!z+W?@1=oD%BLi0E-w?se7zx z9V2c8erF2V5(yg-vjQduA`=##m=Gc!{~JS^ltG;EW(8>GFQ~~!4T*0f7en7%R31sl zLUDXGxUX0QyuQuL5UQsML>ElE;0ul^sf-5W4Qg8ez)O-a_haVCs#ix2+8t#&VU!*rh%DZHmz|jkTMsDz#XBOZsIm+%2K`4F_NS-J|3>k13otE~i=Pr_1yJ?FJgW3$ zBTSoc0&uy|qZDM3Y8twwKgT24&d*NFGMH7QWtG&_;BZQ>%fJd3GY9gr=fQ8%4f^F6 z^7(fe2`Ot;Ybx3;LhvN0f5GSyFA&^_EnVkwA`HDp@l2lJyT~E0Mr3!f-sBnWC9dh` ztUrSmV4s}&>JZif{`NGGR17GP`DE+oKrFVZk(C{n4KnC71z{%k;v{`iQf{>@j<_oN zy3@?u*cnyuqJhlKw1*BXfCf2O24CCS;(Uvf|eV%j}9K(1e6yY<0@i&Z~9r#(x>bQ=zE4+RPPM#5h7eGD{k@CDuav=;NFlDSfSLtpH5Qtga-3a;m zV0L`#MsmvMVWTS7TWd=+X|@)bw1aJF&0pQl;gpapOzA!fDXCS~6DiH-SYy%=T$Dw4 z2G~=b@v0VX7vg zLv#zbJIjS?==OE54>`16QoSfr*&jx~JVnXaPn%4XhgwNTx3O;EG`xyS&ZemRmH_W{ zMVYo1EbR?ZVJch5tT{=RmAMxYaT>rppCzUIbD=ssgI}FkEt+~LjM2-zMJWtXn%d>!^wo?s;GWeXxo?H|mPt=WDN> z>5kfbwicPA;AWO!qFC1fVb!Q#0S+o-^%ItGWWz^K>Ibk0bk+Lov9l1deCI>S8&9Ry zcU^AisXGYzZvG8qDS)B>C!B}$p`%gPWukNLQF)&Y*j3zu52iiB+8Eur00vyUqj$A& z^3FD9QhrVZB!9VjaRVimVKJgl!@E!~g~4dDPxn*3l8S$W-b7UbY19N;zF1nMAGJ9O z-^0wgFePe^VL&51&!Fk539&9Jphqx(@5Z z_jTrS5K>#GF|EX4Nz$>^e<<;!$jLuW)WKN7+$6@3g?H%_1mRRPK&}V{bSTqgi6c06 z+2Zr+X&5r&MtSx>iafQKsb3YZ`P5B9l-5Ku$8d#c^cYuLeh{A%^ZB?y4AO!M>cF(6T1d81i--SoOz;76Uy_Vu8Wxk&z58 zlq&Yk+da{HP+BJ@QKEXc*7VC(t`u~a;~9D2H1zrwl0SC6F!3?+z5h34K9z*TD7chl ztvK&AT{X!VZ5ThHRCW+p>PUvcg12eHyb4GsV9ynY`7G<+1P~@ItXC(}qkK;8MVgMQ zeHTu6dxhit*ZXORG?VIUv22>A^M3vC%RA|7+4JCfsuD_56tO#V+}Cl`vn!jRDrdxX zZmfQ<WeCIzlu^| z%D{lAmhZ){DXi8<=uM&6rfE(eW{wmAbCC`FmO3cL#}7I)0%XV!lnXC{S~CZ)cFM%d ziTt>*ot~-lnsttd!`Gp#|`;Ujuw0WLy8CJ(kPuw&Dv*jZyG@*ke+pKG<#q zYiRd%1dn)VI*I9J9g%OI=bnC!f5%{0u-57g8T$SIwJ1Lp)qHIQu84~dnGa{^!esvh z+R+YLmv1YP74YDPHkxU*yGks{?{O6)p=yFJAy5Ga#Kjd`9b|;6s=w1A_s5CuXOFYLNu9#*^Kgc4 z6-E!`WY)d9D~)NY>o@I!b-ar?t*Mx;eiltDz%{Iw+};$_&SmQbhi_U-4D69l-40>?aC-#8>FkDyv=R$frqUoGbLecrI}q=&IaKn(#QmwwmSP6%H?(~(W)psSy9gau>Pd{Ibx^CkoZ7cP+NyG%%KH=N5K#fdl zeXGE#hH|Xw#%;9{E%MG8%{7I1ZSxce8}>&QV<}goE7_LBAfmlX2yA>H1 zy93)RcK8|GOEU7f<=ID1osNTtRD{@`X-gnwTKGW@+^7E!(OMrH1ZShDndHiW%NawB z4bYa*(HCRc6Nsa)f{yGf%2-WBsTs)-`ERn;iAbYFlZW?8@c?&Ya`hoepLUzM0e;Sb zoq>3*k-bx$z2`7Ufl6bgOr{&*$&XbHVR8HVxu%it!rmMR>{t+}sR5QMcdniWKYLpo zE#{6Mu}_67UKYinU8jCZwIDZrHXy-Bn<1(wwZ<15mJ{hjiS`OZvp{!lreb{7fwOx*vYI+Xe@^2dR!kqU$8vl)zv z!gR>b_Rj9KOJbQIRu|tk@>%r+~y0M-Gk1EZgQGCxf&JBhGG&gN)lbD(r;R5oq(iF0neBzLF zUPA;xXinwZtMT$$XN&HXD;G69<^q{A*$4_!c=)nTfd<|XZuY%4)E>~}MOuC5qtH=+ zqZY1DoTH7d{EvYJQ`%)wY}E_AV*he*IRe05FXRMwfCzNX(Pf4~Ri(ovT5@m?Sy zSXcHqY}pQAD3&!g4x+f%L2Yi5r|HQA!knir%;#ADuaPQ0^>kL2Wfsc zCPBns`z4vjzoK{5jv;`K(2WI1IUGnl*DI6VR|9uAnIn9eic{x%&{PsKa#_|167S|n ze{L%|jsTcjvkbY08p+f<4)H;NV!@hzy-S{I{wJk08r$GG&7E-oP2IR9 zf&N@aR;?QcojmPzgz?CxAM<+^j+60kHFfRkFE4j>z0^~T21@YV+?+z1@>k4!`;uMrcS<6N0)Q{jOa)@Fx6nEixy>yP+d}eY=Bf>uRbB$zea(+hTsoJ5s^1Fl zE^dYdvw)r4-ittB)^A9$@~z9PdT`kN9p1vHXE!gE&`%8!r?j}`Pp&OMj;M!$0N>S5 z=c`Dwh@oFWjKTpEam&;!8=PR%^*6TVm|0Kj=4*rgOIe<<-lT zQ7-83?DV^b^o*nsuaDqhdSN>@1!TH2tMOMaY_+Ty$z3Lbb6ej&ESh%O7h{|cONj|m zG0?O`n?pnPAl%4_9n=0^Xu)#o+A;gwHb)8M@NI798gMvhs>o&j6@pxu9*+ZHj6sNZ zMJ&TJ%a)QuD%3}}9D9Y^)e07aFKs*wRF!9fOfbjdl3&{ucyN&xt*eHEM&!WGa zNapbuGw9a0Ron6i)iEij8LO7_MY2R?z!Tpw6_}KreabmzMuP_{zn<#t1^^fT7!RF+ z4ZhN|!<2w)Q^{%=vXrc)yd<1E)7d|jyYSYc9k?sks!NZrTP$cI2A49cxM(EIfaFj3 z<_&rozv(iNDpGm@iBRA9@3Kb5J%q>%jJj2-pFi+8pxRxDcjhDSr2N)SK?~*vv~Sv} zN9n%9@0FjexEJ?6i!_5AC{_TMUElowE`1`*$V)N(Aq8M6m0@nQy76NmLOmx3KDuTR zN2pwh&I*>n4}J)x?GNL^? z$(T)Dt8(4dD>!}D&(b4?J4jrXyvRgL zUIy}`*@}V^h>vn($8E>(8%QgS8$b6`RgR@a1)Pkc6)g*d+`Jw()631L-?0anQWjs# zA6uEdwJ#c6qbSDQh-q3Hqt-ujq<#m6Tl{pii z0YzS$uSdhTpK$Li2SB5SdU+$1a!%;ve>qekE+*G4emOZlsP<8U^9h?e&M+YITblJT zLsy=>k+$)#A$7FP<5Mx*4%G7l-_tdet%-7d3ucH`e{Lf{Sic|(;$5ClI$4vvcu|b^ zI0IxtjJbx$)EuJ)xu4ZAvAXht3~Ndq!x(jmJ0ACETigSI>B~NJ7oobSvMj)W7y8*M zBK_^!4+~m$RiKvZ6AV5 z+kMEPA!8mm&F5u$?W~Jh?T1R@(^F5I8-5Um&2epwxpKh@#7GPBwQ@fO@y=3!;n;2j zb)XKP?XI_V-aFx3Js%E$UOJ>`7EzM$AS*C^^Y~=#ejGevOpArpFs%4(!zks|1vjWW z0hB+gIzZ+4`CTZ3-2Z&N=4U<^EogPhpXnlMmwxU9hcsl?^|QJlwRR{^6&MB~Ih|F2 zaksC40M`mo7S(LftY;bUKz-ooYn6<6{t?R@Hf;NlUaHJX)av+TM?@Ujwaa`GwLtK= z7YO*h;=zbrXZZugufU~OQ;VviZd|s$h}ceSk&qW%ytXV&2?n9ocYP}hzF4P7=i;Am z3ThFE$Q8#*cqIP8Zf>b;UZt!nD)M8g=kom3DPZd}Djlf3jJ1$>()C$Q5RcDu^9uhN z$j6DYw zq!TMTZH|kgVbEifGd-n+Sx@m10I)2ADdQoC^50@&E6Oj4C1;$^j-MJQv<;U6xK%khi6h&LD;p=0x zh?XGA5JoWfdf06~w0F;%Qdf-hA%)&6G-eFUuRO}(ZMZqz@}69=F&WIulp1={o@R1x zqv&CUEmRTPoJJOsudI=3_z_Ydud;lhDqgIE-OrZxmxi_ zR+OXcW7|ah22TK>=tC@n2k|w9ptwGTSSMbESVDWSwm11hAd#|aK?VQCF7XzY@BA5af_9+;zWhPb=Yb965H(~#>K+(E=o@|eNF>Y<0 zub{I|Dzb2_#Tr3Piy2Z+Jo`a0LO*iXX4*Gd$|-MqaK!O}px1+HZ`HcA0`rAtPMMqR;+KgO6g1aRRQ zqPw(xv|I%)QlAd{`_E+ft7cFPZeh#?bYSw4ETrTck?YG(2^b^kDY26d2ok1Y8~v#; zxuy%CvbOF*z?nH=%hT8e>~tL06-Yw}aw;Qso>XhY^1=OSBDx$qtlYIZ8438cC3qJ7 z062Mr%k{Rjm^fgCIc2?fQN7>wL{q@0=JVf!HEqjQuB5J2-suqd?t8-ZfYr z2kYYghcA!-}0dyaMu*0nb=i zRg7mY>+sAwP^>YsThqfj&BmH{tdF?rY=Xo~1~?CYS%+kw84J(&s%Q1nw#|aUbJh?wnNnAGfk;|=^X7WkG2<&XN|VM@W6K0_T@#%^>eU!?6v^r4EOQqo&xhAHCsH|9(~ zpYI?qxeu4hs*v-p_Wyjld`sO?<)}d|kXkjDT*cL!vdl}AO5#n|=K^U*VCK4%V&)eR zL$--+f3`o-){Y$rn%Wyu2V2_^YB@u)>jNbVE`DDspxl5=fHd7qWFj#N1pMyO)#vUb+f?J*#P?i50wUucGsMzOYYAKso80eeP>M5pl$NHy(~1Bjvv48M|$+lW8upUp}340sX1saiRE;3*q+CaV#FDj)~7R;CHzr9cR>#^mGi zdcT;TAZhHEoi3R6`-q0}5KsG<&LZ?%TM%eHAN&fR{c!xwc@{b$wd?PS??WTI8z4uH zXS2D)!3n&_kb2ZLQ2|f*-2wbos%b#+(v53Q;?Y&ucV*8nHu?97sXqm-C)ryh@_9_w z4Jmg#mOB|}K)I;_j6OwT`?@b&LA>IE4X}IO;iLuLZ~xn-kgbV~NqX)FYQoGj%E)2% zQB0hV>sYFXzmtL_2`oxRh~UZHK}G7}p6B#H%TsLN$Y5;J=$PLQxCry8p6K^mZ2X;! zqv7K->jn+CM8H?ZL^E#?eOVrS9@X^GYDU18%h(7SBqhTy%3Ap+0Rwk=E|Hz$yN#dD zo%uHsOgHhhg+TqMw1H;4gw6vLftvJF*V7v62vFMXG?)dm!2(Cqt7BMLc!v9O?u>#Z zuuNvo?8DurFmrat()S<7HOvO-Wy7zxYe{VZ>^W^By*UvB6ww#KR-)+j7Hph<$}TWS z4%o5QsL_Kk@eGn${zFA_Ix`SusoEIK9tcrRb({eD#KetZmLD4Gv0PbIu8#B9q7a}{ zL@78+!f731Hl{21`My0Hn|PMvv9imT+ql5r*4>>>PsED<-70JFo;F2%z609{_$Rj( zvmMq#;Q&KXH&l--tbF|&<)!}!&cUR5fL~3(00RXakKRW>$P96mFByW~irK>2Qvh&G ziTcBMbzHSIev@8Fa{$$LEb|gg@P^#T878T$B%sY{mUx73Ux$#;x{evph|$hLe5nBZ zl4DTyuBYhlZEF6NR~>{(3Gy6kCz|#c>*6-rxZuxWy|;INp~vgXN98f^`yTa)1!v-H zV}bOVcheY8|093t38Po*{?kT7Ki7npF*qfIwv?E8di-{$j6LfgFFV=R?yuvWvgjL(JdP(mbU>qyhCXM~p50S>Lz8lo%N+>2;NXgz^oUbeu#4FAX zMvt#F!G@e?u&~y=EI#<4O$AP@c=zuwU$cs$Q$V$f%Hcg3PNyXlP{6E3Z{GFIM*DSp zR_#AflLZi<#EwbAg^uB*nzn%p7aSBW2@<^`eadOx1yK;q6}uS8S1J*6kUZ3wIobtV z^M-$#2KJm*E(vL(3X8bh^}vypFJ}y|@eHNJ5NyU+IG-A_gJ)9;Yl}K;D?iwW_<27o zLdG~b*0LfI^!FW}D~R*}=Nc{W`>(sf$~CgIayv!28Yo+RZFusnr1m<;q7d#*`+Km^ zm4$r32RF;r3#yNA&?b~r^hSogjk`NDFd5D0%X0J68ljnW>eXj|YKYZ^Ul%vxWXL9@ zKI(O4aF9h3lN@HWQlG@kjO*wwZN;&-v~c1ZMpfnAi@IbEJf?=ZAOc_6@dfEP?c&8P z8`PxS0sC9EDI)fmr)o+^+C>!Op~|W}NMb7;57T(AgLt|s_py-ao4pWBeN8W@0OHKk z!HD|N^~84Hxr0x@!ss-tn?F;I-+$ksC~OXC`CjoZNXMq<|3epVTKV_i`zl@d$;6b` z#q+Z3O&?{qbTTX#U~)JbcI^D;Va;XM;aZ}nOb14FQwK?zyQCtKRMUK$#2cSO*rqXZ zNfXD~=Z*zO3G8OXH99M2VG}OgONono_Y*KFy&-#;wvxVg0EVt%C_WQj_eOep5;6SB z+?Tn=>9F(1CEz8`l*yDh(G8~&jW_3CTd^5PYvzouGj$rE zGznQpA8;kFfXCjprQuUL7H_3x&K$4?N!7fh$0`CNOfuZXlhqux}2F(R`Ehy-W;g!t&Z}s^rqq=%K$rn~>LoN5v5ycipux zl~KP($kSnb<(L{Uua^+r+AenMj%!q`Us07Riv49#LCII}ngIOnXERk}HRh-4a)`J50>)&m9S5qKJ z=Oe@8*{VtW^LB(~=RE_W(WQW6CpqRg1UjRMhX^0RJdg1W8mKTj2mkmZR-pV7N zsaENu$S}R{XeqD)<=_>Dbw^A;?!Itl#mzlF!OlI$pzr#x{RLo)sv3T}ak#(aQENGX ziGF1Aq_HN44^Dz|8?g!?r`aBwqXgXiT*@Sm?;%!rxx(xdkXWKMS5+RpV#&ChF=CgJ z7$%{jJTh+nB%-L+%sV#aLJ$@8Ar;5gL(tjSAAN5IeWrgaJGs?EJfHo|JF7KfCTNkf z>BLbXh^nY$MVkj-t=2~}cNkudTDH)!P8Y5x-JxiZ(Bv^3>w7D`fL_zXJc!iAtke*F z2FnVz`f;%N!s4>cTdtt}asO(J{!1S1Lj2#9MVm1C07fTq1UsSHdHat+I6iWFVEMXl zF6FZbKcor;)XG(WO%Fc=v2%DxO~4iO9IPeg(lNE~(~BHnv|A&d8U(W)MGr@b8`tA2 z4XY3((^}0L{SAdPV79Z7(NVd%C>Hm#<2P~#=N&*D34l`k)|9mp=MVB9g%tH6s zwOgcrX?!GHP++eHlO2HwBy3>W-&RWyRl!xgLjBLop-|}H0BfEQliF3i_%MpVmv15S#|4T-)Am)9 zowJ(5kICi7Rj}1t=Or)nL=ED^vmJ`;hT6GZz}xONi==0&+7hsATL5^sy@j?~9<4E6 zfW+(|9OZ~M)rJw|NLmSPQr53t4M=FpxlOyr%_yk-$8wRla5_gSuhT($E9 zc5w?3WQNzWRFKx|MCB{QSs;N6@tM8wtTqbIduqXZO?Oi}FTjwuc!& zqqd1IV#^q%ep6q*QoYi>Q}bytTXBaA6qpiDYPnYw5GZ5&eAax{`}Ds$3|K+9)O2-a z9D~GoNs)iu|2%z(7Mi&`J3@{kk3naN^_Xv@>B5sFhvwwH123@!(+2DYsaNaHQ>bko z)QXY&EE^-UwVW?L$0&(x*^$KUdo~%tyF&zKco;UXlXiQ}s)$n2BRB)e?!z4LD8f7o zN3hMW$><3Hror#d;*q`U!exnSb>3A&WtbW8o@{OTq}WBjUIC*qgSQID)1^%p5|`VB z7!&v**0MQeih_!e=;XDIzy$=Yv=PC0OJ==Z+E$P|ZH(#EmsFY7NFgYbL5jKP{EM7h zX&i2E@>sW@A1gB32hU&APgYa}GLFTew2@-{C)L9V1}qH+PiF16+WFHbVKN9?Os!o0 zfFQ%=edeQ`oXiylx9n9tpWxI5c~^@2A;tghZbr*;T8RzOi7Lcp$cx`ufLBo2Jlp%y z|FREf0&sat=WX%55wOB9&U2@RGSR5W>IPl5!x2YGZUXoUq$LJu;{Hzy zC~c>7IWp&fF1-@HCrI<%zt&y^3KhYN;Ox!1gct2lKY>rFvC;7-BK<)8XvrV(>3aqp zzRius4HiRVJVwquE`kr7U+!v&{ECX2oi`4P)34ecaMFGZh^Rr=2Qx(ALWg-tLu7se z_1j(Pv%ZMT1FNKq)U z+#y71Q!>hez)BeV4f}&`c?JE^BmF59&&iOMIt5DzuRqiDJZ4%)G1uZl||J6IlGw9Kk z(h*@S|Ml6=E!t$H)qPyt_-iPnUR21!G^JA=HJUgrYj8NN)-%|J8UQ75N+c*RS_^{L z^Qsbz`!&Do{u`a#&02-6EZ{o5dppJ%byILB7fq*Az3Zx3g0X`;N8z767d9l5v!fIG zVdVGACKn&=+bpu~EjfBPJjh8;dDSRq=40{yDYMwM4fh3Ce0`|PYxO>Hx

CgUr4j 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

>W#0-BZEM8~Z(Qbd z<{$Q;KbbJRB@8c9(1kZ~xXi+h5bPsPGE+O;jgYf&SUo|p@h1Z!JUvf2GJ(Jww8^7c zoxeKc83*CI*!!>RGBA%5fvsZykY8g(_gou%>#my1Y-T~jDf6?7&NvN^iORD}kGDh^ zV3KbcABgb1|8}{U9&+V*^WBAdq#xKVu}STVI$lo0mHP0~pqUE#h7vmo6IfvC2M#U)SMOBCcFUcLRECnk9g(YA0DkPEqI70 z(&F&^g96vK$rsY=Z=2Wt^rl+iC!8u1NVD=Xuc=(lV00k~7Hf8?ez67n+}H-Bp7I59V+B* zaR1vW5wqhAg?<3Rq+%8vUF!zExzk3#_SV(KFd!`@_KSVbixT+FuGI`%WQA^SYJWm2 zDuS|KTY;{=H2YQ6jUt*CzNNY4Oswgz(GQ;Iy=g&OKVW~*Yh!*{@tCjAvKhpeWAfp* zHgz)siV!=oNu47o7cUGw^Et{|T{%zMaPA=1rep7Vl*=1T%BFgAkGhwyS3T8(Tv|dT zTXyWzxVz#u*$45{X)xX5LgVhf5g~c2biZhb=Z9T3{i`yF>v0+nJnur;#}SJic?03vS3>*hQ zkVgD7u`J9vBB^pYgjp_(-s_uL(3g20eC2VrR@9NUTOFmx7U4kU)0^}AXns!2(QU*& z+y*usWAq4{WAmAW#>=T>LE^d^;Q&3z)7Qxw;A9|RK*!;tB;0(=}6RR5!A|A*<4A?7jG7QMU zE3Cs?QF0daME$UJdB$uVWMsio(=Y(C#i8dSqDQ7@~(;h3(WqxqEvXCMzH>>6;!n z5h8zXafA6F(Y0!jrOj1aX|ZgO#t9gq0?4sB^JZ1@6+KNJ^xPc|2s9$8LNjMyHsU3i zTt#1m=5s54*)?GuJVv ziiDN?8QLL=Tf3Y=aRA46+@K8Rs{jgFu;Or9pW#|QwM22N3#`<18?6|B&mI&G|7Hkavi4>y@SIL`a9vHt4YRFaYdJu~2$?2$@xQ z=hpbQuQN@t+!iqs{|Pc+MO&n!(|FFa28uV>ye%Ykg>n_pyQDsuM1&`KFehnpDehL- zXD?uop&iZ*DTVvVl~Tp+^kcsHybSoBsQXV@IvTaPq;t=UM-v}wg@GNjS+-Raj|TmH zkpG=z;{tqK8Q2W|>UB<7-K=L6iru`eZZ+^!=_O8kR3hqPN^#5{HLeq0gbU@;kls6} zr-|dRw*zHUVMpp4kwlhr+nXC4^0ci@WWiQCWE?-qv26XcrXB}P(~T!Ur(;QM>cEyn z^L(2w>BwDclaJ#Z?8ulCCAH!&mOijOVQTQ(NbTh(cF*F?gB;A*S3_I$UQSVtCpyoy zBcorn2$@?91eqr_a=C9l#vC*?bgd+;S}_vPp0>k^#`@}M1om=raDyI88^nw0&g1ftHhacY0?xXt<`C3jsp z_emfCCTXuS02yNyP^>IPv3Dfpfs(m^WUiJjheK7%_u@F|kh{jXZ?>?UGd}Sku7H*x zqkUO+u%SA>zSz{_D#L9ddjeJdIS&^mn6Yd{XCgGSwaV3{Wwk_K`@#v%Xn4;d#L(B; zn%_nf7U%e+O~dnr8hsi>uC#WBEl*Ltk$AU&vgulkr=U{d8adXc`ot-?+#gq$kYI9` z^q`~8?{f*9+mTi+=m~{VT;z~Q;u?(5>*6S$f^()}8|Wiw#g^@j#8wJ$(Vi6**k ziDa8BT>uL>CG|TN1918~x=e#s`QGeeI<_b{HNgt4{h9&!ImxjVH(Roal3gLt4wGS0`sPXT-A$ zh_1V|@9iHmJ35T>OC*<48#%M}(V1?g*>3EiSTutZIJC=|gv zHI|cz5UXmpznqUi|0>@c&sAORmH9LS4`FHW%fp3FuC(S*g~}nGj`alg6wnjpO_&teI0^ z14}7}^~)}=zmGwnoM+biP>p3h%|IfGx!{I2>bfKCGv`Bj)J0m$Azrxz2Gc!j>TFnB zmO>vtVGK_OPbv7P`gK>fj0(CCA2Hg1;c#5^RcBd$6ZEA1DoQNQDE_{q{dW(D=scWCBuv68fy(QumJI_i7Inl63PWpZ!jsi!V@coEh3R_FaI~=KU z+)ai@{DK#qLVoyusx8?#am+6|2GF+?iM4qWhA*foRrYd0w(&jmze{1TE2D!phW((# zpay7-4Oy=^d5kF~D3BCQHz}ekNNBw{g!|=Gh94x3h1IK43qoSm?eVv--c_2161i&7 zOwlqUBiSSfUeY;bfry@}9^DdNEDI8NMTblJy(mv~@8Ht~6PXPuQhV#7hQcO>xp zKW>SG6%7Z~-@ZKSECEC5m2I48^l0U>^W?45`eOeS54eH2D;CseA;d$g_tMO!nrcY3 z1=tmBM?=N$fz*(3w>SM-t!%2OrD_)=t*Hpq>Nnpgi3NAWHLHVy>pxK^ALfXc3lobR z){b{X4ouU`i#2fX)K-Yu_(_d8iK3^SP_O$kxnl=VB=+xkZQOrD6o}=k@0WWENnyOF zQpW^7>JkTXZenz))EENRVP0GGbrT4pBiR4;3UnUfgf=cr0@RIASXG}ji>)FY2@N?m zlZF&&N*Au~8-x38(RJ@TPs`)r%0%UPc}AZAoE{1B_8OMFn=VqtYl}7I&J3zyfYt3) zl8)_;j~nZIUGMuFW9iF2#!`2aIHQCSP06YCY%6&E11v*jOPYt%YO#2?`qF;!nCYrB zO#52;0&l?zGU8t!@+Qpx8xq+=!7TyhNd8S(&5D1Ls^am^QO<#%tuP9UL9md)7PBdP zytpx{!3fpJNrAwt`b?p<8Azzu6y4C$7>VRAqa)g=7#ra~Qnt`+G`Q!M2~I7bpw)W8 z6BIyphqe%6o1M@a(;F(bIw~k{{nm@wo}Le00bD@e$a}J`nFwsFRooE~0%mJ|=O1uO(M@%Fh;j zgFgVA5t5&1Mvt8ODoLS58i*m3xn0*J>E{>Udf_p|`pIy!K+52<(H|`J41;}VndZ|n z6&Eux7d_jBtN0JLzCe!`{C!^#NU1wXEaH|531U1lRuK%)Q_f`!HthaOYB%;=(3Io|{xU7EVsV${#s!y_i zzZ)yvDdyg9!S~ttEZ5(*=lYaN)ARds-f5Vrx4;T*XcZtd}efSS|3(&O-O!j!RXi4rESx?jII9aSD*KWf|^>z@23bje(l= zHY(aEPJCR1!j8Io*Zg7G3nXy?>u>Y_XQ(!xG+qi9ROm3a07G~&HvIjw$ByOgM-}zA z^EAwsKlDX01yV|novotL2DlK;@3Flx;k`>Vk95y0>q^nWox9JgfV^;zl0)D{Uuj`g zPa&Sk@2L$pub;)=&)Tm`Y27Rl%R4FW_*;=VEMqb!SCzN7oxR)^Oi?RR1kVftd1LoN zGsGbPm59OI4#9ec2a8uCs>pexQxRG=o)OFvbNAVsq~Bgu#*1#5#T!I}CNyg847#S3 zTsw<;^u5T=3hd(dRwdl!=ClHNS&597Vrf7xbccedxskfuuV0M%LE(4Uu8HFZvgX2u!f)4j_m2WQ*4e80~Ye!tI;WUA4^|-iGJcpx#dEF@;feV z2#OR;FxM$-qVaEVg7T{RJl;OegPdXbRKL>I(whWY!x3ZPk7xQJP?awCSn{IBII3tJ zz((;EMh2R+ArZ%C|8P5oihBKH8fa5YQ-W#^t7ZuB7)0hrNu*GV{491`EOH;4H{|F5 zGTK5&2rQH>=M`(xC#Dnyjogu8>WM{Bm7Q9!3qI3NpD)^t7a#L#|WVE-ukafrS^Z|X}vk>Wp68)-za68 zM=I%Y9NVQdFSkxejn)Onw)-$|7XXfD=iOANHC`G`S?24;tr(5XSfM-_TgoWJ@SDu* z(9xy1-*C1nJH`dk>ld_nm3XQhi>@HX9l*o$SpTgE%6^;fg#AgN;-XBgX zvBab}Y?%r%EGD}r;U{CU+yPc5$*Q)#EgAaJA&Blt+1uJ(Dj{v~#B8 z!|}YcPHg;M8*{Q`DwQwQ!b2LDu0n*^D;Pwkdapv-L-9fJGF-@$ttUJoH}XnLzk$hh zLXaZD`LI1X&E2$N>@Czup6Q*M5euvbaB7?;@fj2fX(%V4*(7G7rz0U@bNBW(VN5ez zWN7**&*h6dKF6j{V-ui4t&P{-3c;4X!JYJNY?`+UkCa2fIB&TLpVME5900Rq%Gn0nB>#xT0= zaT66(d{_@b-^Y(5<3bUm_6=!$Kb6Wh2Wj+57_J~8*5bvCrUba}vIF59@nIjkeL#odl)V z6?;ov`j{{^6o*$n4J6p{&K{tr2NTZbFy2fLVSO2a5vPw$iJO3ZIS1iK4WbG&-ZCk) ztfe~Ag>V9P3Oi>%u2NrRdfUZR3$;jU`q%IYXlsY^a1j?giAAifiYiA1C9C>inJ2BJ zQnL$CvcC_<^L??%pl{0$cvYz^>>b31TS1$ozFP!JpNv-LFNka?Q)((k2ZxV&cmc^> zfs3LZ`a8Vk_j^1!A`-#V9*GNc#>=qvj8t@F6_`G_yaR?D5(-M3$3sxFBy_KOLD^KF z_nd^jTTfHurA?@$;S%&$`jUhZ*b@~C0(F(i9b73!GBFa)TQ76&UXT|AFUWrf3B6Le z8#l0Yw9s{+!|UJpcFtL4yi~djS4L+lZ|>a<1|cC zi-GQn3(~8=wOj`R3tVQ0n=?j(YQgq4r}=al**F_R9v_mw5w~UGvNWtcvd1X46lwP# zqvpxB;mRCvaK1ilNHb){bGRetCBIGE{o*5|9h&wYqrXxY1JidpT%cVs6YCv(Y_Ggt zPr=u!-W`D%Vw<8UCb}D!NI{ZiIs}&opwQ%qpXtm^ro6G3xW-lEACUN&2Kx+RHD zki@?8oI6kwjalPgizgTqy3Z|F2<(NR%s>wfPA*+74DFj23ZuiqH@9z1*n+*nAgoOt zv1Uqo_$an7{ z$*>dc!M~2M}uVQ1)7i4pANn!k-3@W*dGSK2`|NSeTK6ZR%u(3GH=b}175lRaq32p<}Z z@tqP5rFUwL?2GJxib~1mN$~Pr=uNRb@s+{4YO3o*F#`o6JHwbP?L>N>!7O=YVs$Z& z!#;E5{idhl!m)r!8`)eWCssd@m%iQp_yJF7wi2e8` zNFQNqh4PUNa*SvyfHCS7x6Ebg-+#p~CR~d*Tm<0m9XWj(e~+p~suvY^+zMY76(sFu zVzGHf@gd@4Ilv71v1Q^e+ofR6zn_{=S$l}8--vfWBX_AJGw4P}K-jtU3i|{yQREb8 z6vapTD|WN>{$?Rq^&EJOGC~E+%_L$}n2*>PLXdQ`M?>Nb(`)iLfi=_LPU;3Do=Y4N z@GRKx2$=Igb!qKUbcSj0DdV>$&0%!2!^}x}c*)y&bt)y{cA*!(Grqt&`!f($aax_} z6_*fgX2Ka+gjD$DuRci8?RFIBXp#fP+VCOB>bb;Q8<*YWw-iO$_a4gh=2;F3y9w+M zo()Z$qa)invhlr-HH^h{gW!bsWb84#2|Qa*=ks^ z$gy!QnE(>*$~-va^g>&&rQ%|SV)mUn3mUw(YZ zx)H*9dXkddv-fcxRO?Js#V%{<@mi4HC{1S|j5OP5b{&7JpUD!!?(Bzr)}PXD1HwC0 zLV<$jG7Hi?4<2p3T1+fLY~JHz-dbR)mdA*NCYn|C_-ND$RYxf5fLYt+8vMoRXPzr0 zGb=Bks{V(HB$E*ki-azy+P3e<0br>KiWPEsYTfr%RxZRhNOUYX(qsWAGD6mSc=*-g zn}@feYnO1uW=v)yLlFlWk3a*A|NXiEnIc&s6@x!vDMy$JF&X%c5JoSY@|DEKCnXzv z9Z*Dkm3HpIZ(1V1nX}%rBQYp^>aWB*txpk;kGMyEzmOnRTDcldCAc zgLDEFZ=T;9jGI;t=J6sa{AfXz2dvved0)1}7-A`p&+WiH$Qr>Y?7TRu-1%f-KLm8` zBrb&ts3lx2J)5t73f_trVEel*HW}Pv4hddxRAA1BHvfbiu_t(wb*Qt94|xr!6QTSB zvf3b14!NJlq4o~0?cpzU!eg0i&$l;anjD_ctf56J`PVw*>HklT*#+xr+>}VebWwjh zqyfr=lR{P^vZOhT=%aHN7(})K_5ZB$uyo4?)~}2)+^=LxF9Zie9&LkwE{k-E*~!V2 z{1a;2n$VjM8?0N&tomRnj}KZu)_kEhH+iENF5Z{uKdSd!{Cta-?-I>0*5_!~CNqt6 zL;?D%6K-Mkdd~_~LGxY#yK=-4uK38&44}i9)@v4^k)y3i!+v2)Cc}8!CG;Q+9&2@$ za+hKy4jf`6!!CC78hq8=b`c0=q6-tu6DFJfF!3d!1q_Z(NpckDiB_2)sKZ+^LjDjc znM9QN1DqOGU8X$mBfq_v*}^x~3mO=!YYjE&B^&SnA^kDZ8-!Q2Z{VUXP>1l!FI3>j zaO0)Ntgc>9P;L;$gW+GW-nMu1mq7ekjri))RwM6C0su8ZU_6dqv?VAgwnAt=W7uIC3?lH1yv0d3 z3Q6;x;x}BSJp+*Tc4bpY5VPK`adu@fbDhx|oih31mu}bpi=mulhBvFZqTRUjQpyF0 z8^HrpVohkYlhCu}B3sCOaj4z-y}@59U-ji1{2-$s0%R3Rz6Y=AqB#uj0)RX15Q>B; zk7E}l%p_6XU*!%KteJ`kF@96zKc6`&Hs`+h>^ z5O8eXUs9jin@uA%g#dul&M24p@4dYkt@eW9@U7;ArbP<`PislJkSz9%13@-@?=N!V3^lBm>><&RTH=|!yTI==ALybiM_txJ-3x12 z)qn+hPRRt31-;JOxiv_NTwbH5Kp-k}3V$HM6lI@IDc$ZjHhf*ucl7ubKKNqTe5V>l zqaL5Eg8&L6@GXnoY*$^@^@R@JOtvsTi_qeyap_VIhu4MYT;*?oaspY|WGGkN#2oMHFK;b6XY=mA{ z{m%W|>*H>BzuR4BL79vQ+F`v}n$Bc_UuJD9Cq9aoZ~ylIF(V^sW;5E{^V^0`-R?fr zU#=Mh;^)`*L5HZPHmS&{`&oU^BZZpI)ECu`B$SRqXS)Ht$<7v)=Joa2_){hR5s}gR zqHOc*GbXdMm5gRG&2`sxRr>7A&Y5lg&%Klv@OZy{7A|Y!T4l5Y%mJ0keIrV4Wv)Gh z^0RjFWGAxmA5f?dIBe`<#wkoTzu>Ey=C_r zp$4B`xRkjc3m<&tG_(~AwZbW{4-p;#x+2%qmI=r~?W{Xs;E!=(MEDfGPt-G#Zqgoupb~qpWfW35qMJ_@ zj91erfsLdbiA{L1oiO@|N?sEYV;6o6`ioBK&P8861^Z{Q<$@2m6iWDL3>sx@Wh3OA z3fmQO8+uE6^rt`Wyn=x4vd4-dzco~aa(VKgkzjDTp_h-pt!D88L=iV`ml5>^Aed zAlfR&D*5doN}Qm6@&?|V&%rxqQ15==_V|jwPuqkL#m#ED;z|KzJk>lWJ@;Y&JwsVG zmd8+3`h)wQUAb-of`jQT(N&Up=YokFI#kMbM}EWHCwR?v5QFuePrkK1{cnnlLOM^) zb=92{5E_MbCwE3}ybMA{jGj2H!=bbX^eAAoiEZWtDPk`1cGl?s_{JuZKe15avyP{W z!vU3}smJ~@h7t(C-&xl(j4MaN#r{@5<8${{LsUl-F4^elY)(yS)J0{U{hJ-jzRVPU zTOTVk7#?%tM_oMtaOU9;eCh!3QX+|tq=F%(tyw*{0vP zT>|M)?5jzTl)yeq@$6^Bv>2RV;y*$(9qdbHwjORTM5o=Nl)wme*U~^9Ef6i2YdnrG zboAE_@T$Fp<`Tdr@Jf{M@xS*2=MauSDciWiMlEl)>ga18%#$PV8(i1SY(Wr;(#)j% zue~o@c;f^;r*f5n^TP!^EzG=e5}Z{|3}7I&+K#~i!ncBxEST%!ix~<$9W-i z}xf(r)WcEBo#6%-{R8)45m%~bH2Ip&JmX#$2N7cC>i-lk+NjY8bA#vX0 zTnoqHn-ARjD=(ZUW`XqODTx?aEu`5wKO%9X#)}XBGC-RSSbxu`xTyyG45$T(q-9sn zR&h4bzQOYoTHup{u?JfZ$#peqY7X-QgIIBx0Q|>LtZIM#+|}X zoPYpD;o9&J@qW`(KVHwz2SZtx{@EFJSNyYJv*HWw&@RPA78_#!)Dz{)pl>UFunWlY z<^X}s9C)ESd1)shvrxS24n1&KmmK0EV!l)j$CcPxxEeM&YhPUb&YTf&fipJg8}ien{E6!{Im2kUEZSXps%b}3RzT?c(()* zB{kcAgkj0dONwDYO89<JaY58;w zP)s)Q47N|+@w>O2If z-%lZAI4kClNo=_!C%L&bhVp?kag^cTQtGOScwG&kGfX%db9C)l_68z?Z1w)_B(ktF zH$P@}K-gt>=CS9W!=&R5#U!et(>;tZy~>~~=2jXv!p&F-wSTE@c|nX#Vna1RXLR5C zyJR4r#uj5ORDe;AkrskmDi^VCt@%`qwRNq8%zN6qrc8MQU=)vIq~m3Ud+WsJ)!})+ zZ_kUGfRNJE%YAtDqyc^O1gcHeFXDCXau$+HLjQ1xX21zgn;uoR%!T5*r zk;Xsk?Hj0yG4snwkv?Z;0qj6qOr5H(jjjD84t4PVZtMCMv5f#3GwrosEIV4J{ zXz)iprk|+%4Zp&YtnbXf-%l;}M_8hz7nOg{sYjGB_;=K00?-(@<#oAwSyeeY?FCxl zDU0kCQ{xF%Rvz4zubs>iyP#wNj}C`DQZ(b`ME0OLtJ5|4VQf@MR128Bl&w>v5VsXM zHq2T^AD0-6Kv|XcgXf={Em1jFi@Mruh!t_nc;5gG^{8)=L)T8%rCZ%%f3cA;(*h}} zjFL?`5Cgz%oTa2C24q3PQc#ou%$#8QSGg>DIvPJPs)v#EWL674gQMhV=j*m+T#xNa z!UQRTuNjj%lwJI`YveNt16c*jIyAMHn9NWlcu*z>=i({uGeUTnk_?fw`C57I%Lj$Z zA9wt8&Q1mzG<7XyLq!5D)hpa+g~iV+Tg!|eG6+)nBQ58G_+7Hj780+83C5kwW^t7O z-A@wkOZ~w2-T*?QlY>rYF1iivHBvuXR*BGOB;nmPnr71%no>n7Ym!SnZB_K<+3{-9QkV`>ZY~qPK>)oF@CWvHij)71 zxCZ%jIN=c_s21ynY@X;M^(0yJ+8qKe+C2Qf==viV!GOFWPb)`~2BDA(SjcxRUDsgFVN7c-4?W!z{i|17*)yr}D%@xOBsfI$s)Ph8s0S=T}o`5s!jI15P*CP3l8 z8LnWnC>sN$)h6~#oYWT&k_>r*yL5odiQoQo1h8}JM~8wv=P%|sGW!)wka(-+55F5S zqa>nd?D`o%9}Y^ez z_Bnv1$@A_PgPwb~mT_PADzLq8k_D^$-v5ogSrS0o!yP5{jCq}GojJ5I;N>oe_Ccfx zVANTzqd5Fi!UyFyM4Zo6!C<0a3^;?^AjNkr?>pOhn4-giLE`uw8kxfQP}&Q|;NJum zQz7n;nlb)&+j@4`b$SFp3-U)6>cFEh)LBLk>I;c^7;+R(4xbDVumHW|`G&gvwHiNa z+(ghEBcR&wZd7(|W2S>^W^v0d#1%HSerB|7Lv?ja6rW(d+beE(1U2U{YQ#*c#T7yB zcU}q^d@>t~;i!Z_e>fns2As@r5j{UsS)by;oVi85Mkb?h>z$PTSV}Hm9?GP&@8fvl zPmh)Mo4>GN7H_;sYUQC?OsB~6fERZ*QP`AaRK(x;MN{!aLy0j8hjiI0hHGE&D)|BM zsZPBEyk>b86yYq`L-w9JU|LEmq|JbHR!jH5qoZVR3zLMba6$=U`?9#W*S;<_)8fT^n5aLs8bxgpXlXU5e$uroXM=U=rI}JTVZ^#;@!F_;wrX zxf=Dod-icPzHw*m?V0K**3z!o`p~0~&@Z_2v;bDbo5|KoKAeg@PkSz|c+Y-WMxbSsTQP)O&1}bv$vC9%5IaKasa6Vb24-=_H|n za!8F!ioSOCpMAyuGv@{{AGeDid=*mlyGe;G_;qI}$tLVVRw`f3i#(W3*C-Q9QnhSP zw#F-kpb?Z{5bb6rDj*f`(z8{l7{8fgM3-cLkWKFmWKV7S-0pe&)>iakGRgtUn=K27!#C!Rg1-#80`G$#cMOE1Aed(f2TX*!z?^u)=ac+GI#Ck2-$re; zRYa*Y!AjVT+@^kVI*SGA!M#F=iyH)b*KV4SU8x1-8z%fnEsgf2MTepPu^V^v2n+Es znk?|7KBgPNSC(cA$X9DGHL(iF#5E#yvMDL9VW~{sbK^u|3QrnOpn6=_=#?JD!D^P<2z zqqX;oFHKcOFg0$TGVKoP-MA?FsiV#9~ z2@*!tf)hJwu(FThMAwPpP=q~4Kq{jj0c5LFcT_fOXoWxS3B$SF`SPo(QeJefUcXZI zq!)>xqPP8vbW#3Mo@DN}39A82^0Vrse*yt$vR~GSy*4VUB)-1W7l0nh?u-`>{4s6z zwodz%yYMwNd5%DA$a$m4GQ6hr$SGuaAL!cmth<5poiHR!2GFfg6~k3X!->)Kc0~xy zMEn5&NqV9hS`9!TCS-)0qYBsU4%p?;SrqoSh~s0JHS|<1SL|&_jtGr+=gcV+`=mt^ z485FML=Os>V6p!Oy){414J zY9!AXS?F)cm+*kSS@Y6wrvX`ZKW*An>CfqMOugvmfw+65Cd$walTf}e6SLcR^N*pV zb?_;HGn-XQQaS5xyXb?VxQiQu!nRT;74FsQgq}tXWnK{Dsn!sutj|LHHKiDW`BIKq z1vx{kgaT6*(8RVg+-|_q|A!kkHdFi-cJ?V3m6NTNA8ld}r2+`ED5g$}7}XCJy2qJW z77D_d=~k*_IJb)OW#S3V=)=&h$JS;Xybt$6tRAQ|H&t@WR@1CeGu3>)Qxn6|RrVW9 zjuwxJ0G0XSOGiH$xzO2?XndHnbBcu#C@mUM)-^~+6(L2pxvWr9JfF6_#X5-ml~OGi zA^jpOT&S}xixsY{)(1Lt)_Kv9B!Dr8KCmGgS+bc9$e9m2(yF@Jt z_I(%Fr(c{tNowvDa_CSN7BrjV?3Bco1}EbULUV;Z?9v%a)9H_Y5u-L0K%AiK+`~aF z6*F@Czz^o>oEL=(W_%JC0U!5tNGQUwNGi%yLJQim0Su6A%%nMv7BA0J!EY?`%}>#s z{-d6r|2x8GSit+-oZ4N{)QyB7)1#jDkgXCw<+rpLlc`gN&LD_<+44y8Fdg#>(8|}h z&G}I#s$ZpqhET!WFM^;~Cm?q?crhE}zcQw(%3%DZ^vC5;%}FM)-38bqcQb`g3Xh$0 zB>I~wSlp5|xTD{nf;fw;r?RBCQnr8DG5DYBF_plMQ_}i?sq0Y%q8y@s8GO?r`CO+s zBvZF{n~!6RHxf~5OMn5^+zx3lctOMlpz;uA4hAbpGi}M*iiCt-Wi;-_9-g$!1W5ad zFKX*=JfCv<++P;|UD-ltxd#c7Y-7p%dpwvQs~*RDXN&PdV~4n}|0(a&VlBz}4NvVA z;i>y3%J34&K7=c4#r(}3k|e$YtP*Qt_X_*+ak$e^1i=hLwYyP3kLa|wsQlu(W7nuZA+I$ynjYY6#eoYc`Lw8L6xmBE|l8;DvIrh3(?(i7&Sp6v3L)hdILzc%{Y{L$j&oo6(3qjq*us0+^5&rFp zN)Fd*dXBdtMTBRCGASc@ z8qR<)^o}$ESU+1C^?2&;6K?V_xa$6ENQ@*kTjKzD=6P*yOsNkU@rG)F6GQNuvnriw1*~%`Wk@W!{DzGz`y+|KPI*-MsKWdy^G_?(%XZi{NfO?*3q#$&2{u)V<^hBc8$YcZFpWlY z6^?0~Lo2$=5s6v^Tb=xhd?jh;hX$S7L}8cvwP+3h)K}#LRqo!)DRSO*pHm%)3fyy@ z=mQdGYiH94pf|uh_;9SPlCzWxTw1?Sw!_Q!l>+Qfq%ccElK13FMm<~N2RqF#6c385 z582pLDkE^HP#P$y9HYBjvAk>@#P~zld>L*|$>vB}=r&^^3R8%xYZuJ49GcL zX{D4>Fe4X-_F6?f81h9n>HQTvSZeytkRUYpZ3sB!Tr?<_`G|>>VM_aU&D@^;(b0Zd zxyzB|KW;`p{p!I%4=j#w`iq&4wZx8n-Tcqnh3zQsLmp{W4^0w`qt$OCfR15ws-x{kr>{Gq{HGn$sd9ME^L-0UT%Wca!u?K_W>ihxQpft=3dM{AcXTUP<+`im$f@`Dr4bIx82?5mCV_F-r?kO`4kVI#$u(tJuLt3hR z*!aa$vFl6$wF3(#qP{aM2CkH|^L`O+8EwsbG%N*loVG#(`tsi7$QMdos}Q}@Dx6}2 z&$XGpzH&Mz|HO7KSZmBmjh?&91wVYGxo$@bNnEnP+T%QY*@%-9pB({pJ+&Lq&W)B} zyqe2Qk{VeuHLI+Vo=60!WiY%Arxpe|gOos<&Gw|^4v;^081^_3k9Kf6LwTH5EgJz$ zE$&~=hpPfCQ;-|q;eZl`{1DSQk&A8eQ~lf*!33-4ZpHx&; z7sUUZ5JMR)y>RogtHj?tA*mUoMBmd$z)Osa--9kcJT{T2%|fqj@O&U`GCay}p@dCR z-nV=>vb*3UO7?sAGo45_FsKvxLc%z1*y8f4$hA;Hjzq~e0(@S{1~v+!yIW(Ge`?$OVh=Fg5g=Nmh=;H=|Y=j+dBe@;J(})8ARb zwSO%1v}(A8z#u+`yPTV5)wxXl6j#xh5hb2}7i!!$7dP212f2$tTOywHA$sBH5|}Vc zmp6wOE)&!m!dtN&zlMQC7X3z}E6gGE0QDb-hDq2zpNSc&SXxXnGms^EzyNSirLT9m zlW^rNa*iPCNDhMi0d_NedJ`4L+021=S73-NeNvpm0Myl9E-6<{=HfewV$_860mNPp z3jLjqOAM;WTQI{~RW87kcG)%ccB|~-=~>9%h(rJz?b0)z1E4m&8y3SCik@pqG}n*S z*}<=KHy99F(Mu{hdy~kei0BqE!~dajBs3GcN|jRCz_8Q^K#JA2bYCBSZ9Md-R&d+l z!Fr(k5F^j?HgtR|uP z>7uSNZQC4Vkd|7ymJ)mP`6q%AD7!CbhsB6Dm`MSk+>rwwzz0uAq_cz5<9b6&yGzOB z=cSEftA*+18FVXJvuS6+^%J|y&ynnh<1QaOL#A}~u*)b75O&QCM&9B|{ra3q`uiq= zm#k;OW8M_ZQGr<=!;juMBhCGV_`?WeNGIVN_&TB@P zEY{AY@%euS2nBozAa5t1kClZEd%p{>VJdfr_3e;Wqr}f3EccvL;Hd#A#1i%q-%2_3 zVKFuSdCQ`tDI=w=U%FJCC3!cZvnMz=t4}oVfMwZZtkgQat?7=*R5S};3Ys@}G1uBc zGcb!XFmRF^na+^I7$xlOgx9L>TKT%i#<%V0E;IRoUE<0>CDsE552g~Rr=qTh1|ny zU2zyIB~;p8xgNDZe9`R4-$Kd3hQluyuGY3Q#L6`^5 z3cHR6*_*-fKF-oh;35PF6lbYl;1k(}G{UX;oLwLWAF<8z8>Nn~f|rV&VTH=-=fTZF z{;{J(`)JqnK)~mHQ#{naG0yG{VC~XNs3Pv6wbQJ1A9uQJvRtqmWz8@RUf2LR)!1Jq zx84h%_OA~;RGpf#aO*m(rs~l#N;Nw~Ea;8t+|S2ft8dn&zIe35Rv>|)V$2Pocz&8Q zMm$nHujZrl?|ThQYsvl{@>JVtHl+?JlJ;TQ)tM`7P{b>U1} z#wWUpm4UQ#-a{t>O=5o(LF%sAcJ^)iqrGMgujXo<{QsQmnFE8&{KhAtAasuyr2<|2 zg?7{B6*7ak05qO7Iyjo6Bn>Ha+dKjK(%j57)sRJiRl?YnkI59VYvJ=q%%3+neeK04 zpE>TeH66Sh>TjK&(|Zqzt809RgZENZSqlnxL`FtrvYdE0RohzaeZC|)q> zt%rEMPdAv}uXh!`h)Nl)pGr{4=4Wrezc%2u!DJeF2J??vZ=P68@QRxVE;YR1fJu@> z$_%h!4(&Q?D@2Tj%Gnsmc|>w#Lu&d)q^Epc4&Hy!-rWPTZZ8H@dTEK==z>cUxO`wj zfIUXgUazzw))wE!UFSb20x2rQ;^IWO6TOF>qlIG7~Mw7UyO*oQh>R)6d4;rJAuy0&Va(&(Vn*9El7;-l^b|3;VSfVjJHoq73Z-lC&x$D7~)j z%~`>*F|oVl_^?C9C@}u5e(8oPE0zlYRtjs`&*Y;^m30NQN0j6==1vyu&88kFlr)K1 zb_tDnO`IbCukUyyabO#sR?VPf$@_?_2{5)_O~Ff|j?FBg@{Cmt-*iE14UpzDv_<6q z5+iI>h8&zdYVH;eUQ4FG+=K3b_y9QT((aPRnAdn5TQ!3v4FxJ~unZ^s z5Xn=Ce$GC@39JdI_J@>YYxMj&$6*)3ZIRV9$Zq_DDSNxkPkf?iX~ZeaAE#)}Ki%4? zkNpnYse?k&Fic}eFf2J5pq=+-zsIXAF_X`R2v>pIML~~_tyl@1BN6qd%T9brQp08? z#_qeAw5z12Va}x9=$8rQ6=H39PLNSwFq}HG<&Xty%45$tBQDPMMkhQ_E`GCS(_!sc zVOCqn?ewx$L^~TIt_GGz!)w3hAHsWA2Goc7ahY2Q(j?n&*4wE^Z^NNOa~@?#HrQ>+ z)hMUWQpiHY!g>|YcE!T!OP=83U|Y)de)L7U1FUu4K2Uv^OU8*s?^e)>St@al%U2yK zJG{w`Dxnb%=MGYDujj@%cRHF2$fS5LbBrb@3x6*4!=DKZ%pSa8N}Mc$K7dr zvjUBZ2w}CdN+Iz~lctk9z#w~Yj6F1-cU$w_AO({!cQ*hVk7fQmhv<8+Htk0FrZEt(O-?v|Jvstg$3T0DnnC4aJ?! zTDA6U$E*-7FcGWCh6pz+I%VHW-kv04xr1ssrE%K+E*|^fa59Js;jyt%W^K_+F~96X zOB%<5d&Co9W|&yLe_iD)j|xTlU3TZo_T|eOqopaZ`eE)AUv_e^RZ6V#{6ldb?$B=X zs(pu37`(9P=pIw(T}d_n%vAC44q^k16!Ipx*OgJ|$^a}->O#oi!O=u33?y{A3_2WhpcD4lb{ zk~}ypZq!T%lCF-f)Rw;I8lVWXzRu6Juh8kSxx{Xhbt#nuQE&ksnjKA$5nGMI+iUS~ zZfEDyj<6;`+VhNk{Up3LmmPjb+f;DQRB8kOjoXB^xjrr|b|-CUdhH5@Z1qNYC@+qU zg}k}X_rgFiv+v$!Iw)f#E_I&@Y62U5o@}xenCL@zakz0QZ&LaOi@U2FrxGiY8&84R zqBljH)L^p%+|6c!sq|?p^(#2{MbI~<(-)9(JCti4qB=*C z*!U<)53+%EY7)V@qW%LOrsVKFmIa?X(K$_HMZ;VaVcJ7jLNnEsx(76K4Q@@@-FvvV zA$r6f-;HW8M~ugPM8-^_)+9_KSryC_1i2qA^tSc0tjW5RvaKw58ouJ0i*B)5F)wdM z(KE;28)S{;9s^&YSdMiK!K%$rN@{&Z-+G69kSp{l{tE(QoUre~kSmXrITdY`UGxVa z2Sd@_O`-HJ7(CY6IXO6XN)CJMUs41EfW!A0)0$PsTxV3p4{ZTR3Qbtqb@*Jp8m#_u zGeY*z(i|CTWQ%(;Hv#8%h&)pwoRjFyt{m36awee1ZC%!SHI}0F8RlnZD{{V3d@46jrQxQEc{1@A{*-S$r=2~C6!bv{Yt(e z>Rw(K?UF+RP2-rX7ii9yg4}k&naITyHKT;DSu-fQ+#y}~mZ;i=nOn=-9_JvQ-#7Sr z#!!S{(aNaVE6rbnBQmQfyM2%x3*}H14mpznN*EN50o>3ruqNOt2L?$?--+0a&dx=F zDY9;b=V{V?!@~8H%)QgAPL~gl-CB{9?oCHuMpClO4>SvQ4$PHO zw$Fddzo=I)uzqYEFt4PRqGEj)z4l9au6OPX^8>t5b^xp3NN#q1v~+9B)TQ=Gz-E`t zEfzVo?&0NxNCi}L>H+tHFL*jusbQ4z>0@lZL=$>k=LM5|4AE##A#ouvK%Ddpo4&6~5u>Kqp`sijDi6 zm3S8}ok-m{7TK`B=uT6)nEzK4^$*%fQx%=c$l2k-{EHLj!w?!;*FDO;tIU$efJtY< z@oSr*B#MZ`%>+Wv5tZu$Jr;WAYo*$AajOgMl8^WcDwT9~sTJ95a+d!$#Ft}kq>bu!Yx2L)1O`XWH7oqRn@oom8=^V z6CEi`*|8J?;JFL2XdMtNpSC7%VuY2VH)^WW1el0&@f-j$XM?dEi#UAcCE~k+3=KkB zu`JpqJg4;Q;ZsB%f4&+HLdfY7XICGP(JVt%&2l6tDJhCTiGI(I(P`@;ln?_lt}I;f z#xwSOFsO)+0m-D#EpoCpuSg<+p`Q?7Ok)9Wie$om=^$z>R8CW8>gT2DCL6P`AhhJB z&9r={WeYA7Tn_R(Oj9O+H@nA-n=j;H-F_N>HB;_F+1YD>{n(T~(|M%|{i0#8XH9Fy z?g&wMuF(<_;2m|$bKaYe#A9x)A!oS+SSV=ahm7Og?eg<%GoM)&_2tIP(CoPNwFFi$ zi1I%QHQkKik z6h_T0gE)Augmd@Na9&Jv34j3z`RXdsS|A^I}}y!#!K71SHsz5c=EdS_x+Chy8a z8iuIoJbTNH;KU^a*9)MH zbQey6pRg`Dd#|4Jli~1D5)@$qbuHk0SFuI@KzwS*d3>yrAio;j4`~@U!09r{iy9=l z=Xhjlv5bLrd|5N6r8zmaEm3549uCUq+0;PDqBDLY`HC) zg!YHwzBYySEuo8B#+_9!ib+zaT^2+Et#a0AtFl&Ag;EEhthr9+oyW4a2Frf`vSU1$ z=`GVzmIN3_f;J0La8D@gr=W=+|MWLUei>)kI7??c&`e3?RzMhGj=q;CiJfBcup9sG zOSEOT#+dSAz7e86SON$*(io_ z7`vh?*5dkIUCaGL;G{gMT7m3YGYUyXplV8BL7L$DI1x(3JYBKNsWy(Kro>LSQCBM_ zs2{u0fWq`)re^&vxn0pr2OV3fZZeMJ5)x+5N4a7GsH?h6f8zyi;ZfCwCd13qseKb6p5J1w6QT~hU z4^HXNRkXKf()esc&Q>;s%_|o1{CzdebeQK8MPC@EHw5KG6oSHCSzSqgIS6hkJRCRgoMavW+o&Ujl;LB-< zZmYg^W>QAd(*hY&dNou&3$ju_I!qeOWUy^5*(0Bu{&C0-QLJ|D=Tg2?6iv!By@6)M zU5jc9=3J1Xdg20gDrbEj5D^P$h29XV;_A*}cDVK%wP9$?{^ca`t3bPc0V&`h&ydr0 zFsO)3_i8=~bo|5|sQ>%AhJ}y8h)AvvSY-8<$p#!XZFAU|`7TQ`;qFD!lA6?&e+>Hs zIJso`q1H!hR3rEbYOvdL0)ub*%H7cu-)zmIh#jjS)2PCRV%v7+Ti?>5Aqd-zc^5f_peSg;fk4r^T2?M!UL^yf^ogM`2gsw8bug@DS7g$hdiOOCKHTCE1m@EJKVpE*0NY2V2__Sk_Jw z!7vW?G1e87E9FFRUh;Y0Ig+?tgI8~bntv?3PE$OTa)@{^R?PM!YAbZES8{Rc)O~G= z5Idxn)<9OZEZODU5DBh4py-XxA3Hh|=F>-luD(LMiYts^;Wv1Of;n0~<|*z5fiiYp zS#?BWi9=grX$5RmKhf%#ggULh-u&P;At!ni1;0lYi2XSN9LfY$p>KqkGTO(fl62ai z@95a496q-7V3}t;2yxxh9J_VKIYQKhA+Lhr-{|LqrUO9e`X7~7Rz;|~aI26NAZKb%nGulRNg^SCN_(~kCjD1$i0o?_frc;wWyqMIVEU@l z1a@jtj{~|~Li70!Wdata_#9`7R1*whiwilkAdY)1M#yJ1R|Y*M5>!J%DfvrlJm?&X zzw*VqRlrDFrT*~3jhZe%+St>48bHYr3ZIS_Lvy08W3FI>2&Hi2fm3QOQM1wLj!w_ab2<@c zx0N^s$havWsqOhg+TT{*r|-hPr(PGI=|}MOHUiH5L%aXDM&m$I}B((dhEkOH3!$e0VnkH5MYcVM*VRkVKX@M zW`TIz=^DHES>Rab?jR=VfNV2c5weLHR+E3{>DYUJkj5FR1&)aT!jEJ?B-L<~Ze6E0 z8C1mVLtv1-q$#EOsdNSpeb39KNjKhd@f+N2J^WFS>1^tezj3e5~m*zuMWI&oFE12n6#40qTJ~w#rrzHIcU;PjjnuV z)9rPll8WhdBkmg}^9)26vr`k?`Os8dXdxtWLpW+_VSl$A#ej{G25y+VUEzdSy~_Dw zt*&?WZQ-UK^dsmE!3 zL#S%dr$8ym1j(S6cFBIagZlVm@P3xSuqlSOQpu`s=b9;f0pykLHUFq)hU;N{C{GAH zG+GQv?&(GlRNBafE7&-Bv6E`-poo9QqC#k3EjIu~U7E~&3zsOYwV)g0P+$pyJZ{gC z0vP#??nQq-NeZem)2#zC?Tot4&69fv+ycV;qtU6Op4(;^eK6JUHO!W0fQbMXCX4^tUHtSm40Ta&ON1obo7XRKfaDI4H*0POYN@I_4_=ruv+&lmHgN0z-RiCxPhc6xMC_u?=KL5GCMv!Hj(+eGp=yaczW z=5pyf(6wD=VAJ8I^*$Q856u#fEJ*t^^?7tV>J$ot8QDCpTvy2$9|`F!R}619njded zbA@>XMTLng*aEa5zqMnwuM%ZFoFAEut6hi%?iLH{7&~`wVbsC9Ii2#{Wv0ck!kV!+ z_9#tqipd!tVzX7JA3w{?!j>XX!A|=CH9*S0N8uU_*POb|p}I30VzXrlOM^HWd>zX~ zb+myJtmwZiqodK{9uT)gjOg->IVw$1Z9X4^)`fUdXnYlk-FeTg0J=kGJCf|emB z4MnKyZy5}M!@%Mf`-z61$LFXj^7wWIE>L8)N{E3nzO3_I6Cp0Or4s%xOy5q&$2e4zQ$*z*kc8^Ae zcj{(c?dC3O49#D~f5z)>ff0QO$wwBvdT+jjpq)=pu4gIg8OL8r;4)wz>A;Oo<`+Zdc7X4kvYL#w08D>nYcK8LgpGRw}~qpaj<5mnl*!*EwR1()F4y@ z>`b!}DAHC?$JJRo60hM^^ZS=Hfu~~`bJ9*3w!((xG39{KAx^TFdMZQrBVtC)mCba6EUvrEZ_q$IL{;w=a2r_!qVmb z(u#f6B5~IyQsQ*V6RIz zv2&Mh?%t=*e*yg6MVj`GJ)J6fvBL4!o8rl$V`HOa81CUz!rJ#-9)+{*E0BROcp7_&BL19dCAPKM6M^!?@2cLQ6qhwwP&ZHDN>Cx}h~D`>aJr zb%PrYQ4mrot))RP#70hpa?q&thAttc4}>^Z=wd2zkEiltlnHKHvrSd~<&P)Dcg2qr`XsC;i1IlKw!Wf=XWQp2WU9wy3Rqw*Zf z7@e=x0*y+8FYv{*rq?VxhEi8xGFD>sL%bWQ`C$?&dEuh_oL;-499k4*!6iQcsHll( zQNUSS4GJr5_^Ab{HX3@xW!7eYalN84AgM@KYm2N!2-Z??YYc5E6{`O=v}XxicXwWe z4=jz<3cdc<(t8Wq-=`@PD4nn$^*ycoPsn)L6X#&fYV_Jpi0iTgw&~Yx`3p%H8&g3j zBzkD>tB09pl?EfugBJj8nem(o5~ih7MF(egR3n-!uES8|7En2GmT5+E?#>HFAVBF#Hgwb(&>gNhdNwP{A>!bgXR6F z`Prw}I4<<-y(30Y0f(KSUH1O?rwz5?6dWl%RcrNJII=q#K=ge@rHV8ZJ){WvqAYgO zt){_~t{zH!l8;hkb|%hkURVpM{_#HOWkPNE;H4ao5q|N4wLi@Md$=ZeGNQ#Sn_!`> zZaFNMZ(}hcXfhRmE-$j=)olr7EYDH_Z5~6iXhu&y=b!$Ar&rGJq5r5d*PS(?hcWjqZxH717 z$4il3qg70{-ddzi7AyRP6|ts+vuxO@ZXNorBl_ds+8Ns6M)ZbsqV_@)YzlsX`GcA( zs`j5KQr24z?Et@hINfmp6s8kRP}1?j#28zYW`-+p7c>ZJ&WSOG!nHBQ$g|;I30HD} zT(VvwXFgTQA_am7h8_c1!vz^p){&F@HOJ&p&w=BDBv%;epX6m#aib#n^(9dkz}OC{ zp|)Yin6nSF+J;IHg0tA>G2<3%p|t9l#W^;L61Eb@n=Sjwuw&+KhR`RAdqX4jzw&PX&(Urz z?H|9CQ%9qMm3o$QDw=+Dz zNxM4Os^=ux(E@;H3k-|I1|>T_$}D@~I|wk5|?KWM10*SUnm z_(BH%YZUB#wa_#=wdRAX>UABT7cVO{s(!)`vK6he@;!p|m&I1I7`)IBbx z9WQP&_Hd7|Od>n}{QKc8@NS4BAwSO-eD_axme4;FV*a5DmoX^Jsyun>;oJgg=~(^) ztfx{kFam?92Ny=tDf^T?&&jyvafcMRi@eXEL5)DVlAC8bF`qVLl`==2Zz1-2HI->$m4^+w)lgXskKZng$wAiiE)y2e`_xPnF4bFJjzk+)QDK~5|7*sMYG}{XTL+xo9Gt)t3k-Cx$g8+ z1y_Y8qsj}E1L$~`7C7Arx9MH-0Eyt94P6R(#l|VU!mqyjX!Ry?SgU@9^Jy~B?08A1 zwT{ZUzp-kfOY=8+sPH}~!4B9#b_%VHEv}_2=8pl{#{JvW4qxQyQ@tf&aXlbm=GFAw zS*k7p{fQK!)oZZ6H4rjaG4RK?U9aqmz95IJsP;nA8e;s?2PpsVt_x zj`~Mo80Ck!+^t~!#?5lGKQOE<4d{mvNAmZX0W1{J4i?`hC&4BO=t_K{4(DY9l^Dey z01yzog)F7ru^S&<}(wXU0UX00GmK`30OltO?M)Lcs^qgGoJEtu3|Box32Oc z`ee6UVi5z)D-!zHS~;hkoa@6~23@|h6?~x1rwT=dcp81s9tq7diaehRSOe2ot<%* zJj!z^CwtML8Bs%8WBv*S_42s_pF|F2drd6$`2h73tuCHEe8na%AGfy&b{SlgHi}eH zR1k>uOlYm7SDiP>xZpYH@;`}dhd%>~LM!;|rK|4hf{!2Ji5i$z1sX zH_+Z}yJkZ&C=#l%-Kx=_(7TO9QKAx>8Th_r3%~6bn?vJzK+G}huX;e+MFxJEpH|a} zCAzZHz0B(#1iU{8^}=*X*UA}9-z%<=qC1TmG7R{I`#5fY8|Qyg;ZSk9Jp`F!=weCx zJ_cuH6l?g_d50$QUX&B?0J5uExCYB^z|L&Br_2q@9b1z(?C|hldgj%3h)l^;U1r4-^7AYUPLof5hccm-RU2T;Lg7#& zUQHLV1!j5SE;wlwgFPo<^ifD##f@g64(;C`Wuz&=lC7Y&Y?$I zWWCO{CQ5&EY(T_VU(uJqLJ!bEIoze*iK1IqvDPII7TXIEo9(Q3V9lr`wG1^zW8~O` z5g!oV$~Or+d6(wupb5-~ z!hBlN8lGGd1C*aQnz)@x(EOm7ph%C<4u2X4I*Rx?SUWwNU*d?a>7IX>bV8Qz(>Xaq z;QBgu({_7^C?Y>rp7G^X)9M+`K-$w^-lAqSxJwKgqfo-30%|uz%&cvO-+y6vAfNkC ze)R1q3L=HA8X2KM_XwFZtz_Jk&Jd0jXfiFUcIt5MIHP_Y7$8VsnKpGz}3ebm%Ueih;@me2GfE7LR^l6ZI(AqP(>Qv7V!+jlj z2^-y{zY}@&{vEi<>+HB4iA!4W6ZZ%2UqCx;ZJZgtSkzNv!u<6XS3_CJ8QEo&ki|#; z9A*(>)}zcf@B-9I9HQM=Q0&1*++q=IJB~1}`RN{#>V3V|peTSokoDxUB7tA8L9}92 ztJ*K9W`sgM;0)L5f{`bDHi0LbfPQbTH!N`1U{u0T8E|EGvh`{XdW0pa8|Za%UG+th z3-Q?R*B&9NAf3Lq(RzR9C_HB2v~$}6`kzz?`}2yIAQiLWOYU1zRF%M@g*~wM*JBcy z2Q93iEcHWH=;jR=zgeQxFiTDKFKY@s^Dz2ZP&AW*6|0()9vkm~xh3a2W_6O>MXur5 z4YpMCvezA?%;n!C%+ph3q3fxEPIk82ytPw-$Ylc%nt&d1Z%UJy%z=Vku`bR>(wKz^ zxU})sfOLbCAP6F!<8dQ*%@H}l9AJmHA!z`y-uk$}uV&AC6@*`w~XZNq&UIw_={8Tc9!$7}~{vUKWbKBL4-f#^L=RLLj zPoAP6=N-g~JL0cBZkunoy}VxAJzbiA;-c*+sWC8?{ncq(1)RZe7>4(Tao~`#IQ-cX zNr*(c&!J{6B)klkeHyOF*h7=`)m4CDy7lOB1{80blC5|^WU}E~l_YC^I3H9(BbsMrpg7pI&QQEei`)-;Zn1mO#rv&Xr7L`z9ld-wvdYZOQDObKmV zU~jOFl7Peiu}*L(WFjnyhwPTYc4D=vW^uKGzbi7h-~=u~g8^O&wLJ`BD1ycmLEp`O zsLQFiIsGQtq)wY4BL=(>A!avHraK>aK?v!B83c9(S4^8=WRR336`)KC3hn zlA*&g<@cpqfpwT`%bho^V2h(JyeyuRBXNXY9D!VB|2eoD3c-fiY8R~v0`o_Pm%ju% z6$h~zp=lTzTO1bs7i9WSYjw&$FivF!37GSDt}lE?g&IfRG1&y^-EbRf11QN8M@7Ft zAY0c{9l&fnlt4*dw%Dw-OBT=G*?LG-37bpdKOM0I?TYvhZ}}rz73t}B)eg`dH*DQ* zEp-pfivY`^+rE`w>0$C=eTuFdg^R(`{nRvIORXvcruTU&ukGHweh;h@xUwD8v7L-)pFvZX`m;qKvu#Kxo-EmoI0Bjx_htNh%}ZCbJi zzApf_TSl_3T*Qbo6^XoYx>Y%rd%3OP91+1YlBtk!k4X57$Tb!WCMB9nu2yRIO!QjE zvYzjfaeLax@&NY6Zl+#vY!rHsj=NIdBneCba#0QTO>|^&V~)Qt?VT$4OScRZic`QZ z@E=O?TD9$*jZ173FV|c>$%#r@=^}uOdmfH0D-#-$_Ps`28$YkS*cvWk0s}&^!iC2 zwDzEJpgS{GS1l}?w-G~KrEvw49r13zHzQ~coY6CKx4v9f)Ff>)I9boP1F#bm6~5{m zzCgyoOp@pYDe&XI16^4+K}&g$9kS)(h^FgP(islolcb^_F7S2CSKP+6N8^6>TlSvvS841*im*`ny^MEN7U&#e?kGwB7eg%~DN&itXsuNvdMx=w03 zpB*I!a5miiEwnsTmBaHJk`?c(LeTkqXVlf7eBU}Lo%EN=R6t`4jzDV5?wB zGTXIgyH~`#8Sme%g}V5$CO`tb@+xDwt;x?C;p?s{Iw-BV5X7~*XSXh)RPNFNP4H*J)y8 zV9f;t;P$omTQI99`b^Sdd@j~sFTjc_9zD{S@OZqDTNIK3lD`^fV{?OhA^JBSNw_e) z8h9)fln8MSqIMBL0B~gL%ZFj*$&6%C*?X=0EBVHYPzh?j=GAhX8|3YtWUqT;4UXpi z4dP3JCyM39+eGccF1Ns-h1*y`~PQde$&8G`>Js!XX%zOTWS$VnPeAZmJ3}l z#KIV+k{{&)<3}>+(NWR-KS-WHg*9C=EE$X92lcTT^EDnX8~G% zs<7Xr$5n(Z8qfsR4zm#uc5hyM(8;i`zsP4delNB|{)+7i;oPiLYX0w8Wcsqtlg5CK z{R%V2N=s9|>OG7;b;7?9W>kE5hAiz3@zq<)Ri)ekE16AVyadfn>4xFwRZIx$^V$cz zJEw`3-B-UiP_>DkCg}n$bqPey3>z+U4S9%;4#}Ppo30QA0bstO1oDY2*CrX(#f9_% zp%E76`Q95xB+|O|y*eb#e0is!K@A1~$oj6?j3cQdWeaHXScZcASl>X-R=S=%GQICKP2`r=HYOCt2c?|(I z(h#^Ulvj!G#&paL*5ET1u)KWS)RCmPB4kBL^Gmfi#%;VAwL$tw6f}|O`M?LBle<5; z!-;V~zmF?{vX;3n|BgR5WhW=6FPSNJ$0SSCybbPEo!#peoVHK z0Lk+WzU^g5`_*<5nbmguHl(@Sp}W}OfW&IPzI`(WBng-5wC@U$AuHi0#+p;<@1ric z8pwcsls5PyIHHKzunO-1(Jqt{5ZjAYwEd!K~2Ycx1ofxQw<~;Z>j=xN+ z6FIf%M|2(-WCP3u+IIg{2)|p}q`Q+(8krBHZ=C1-_RJZ6*?u2Q z+#oCVQf&cY)HAGa!JpSrX9r@PAds`l=M%{bceo7#Go*ovPz)T*L?XW=#!2W!>p79f zB{>j&v^VV7nDD&{ScqlSH34kWmR`z~{dHt#g$`O$)97B>9I#$b3DYc>^7(%nXhUHu1*IvKiUqxqT)jhEMbV&CnoQgvFF*j@X7_T|m zCFAQkB=Z5+bk?m>GA8j_ZVBZFYrLD2rp(a-qJ~TG1lCNnM}nYE83iSmN}wq601NQd zqdFJD26nnWqanjy(w7ZKmS)IEz_nZI9o}b*0&_p-^9dXtriGlVj%W`8OGM={x$6$d z)9D~*i;B>+CK{s=EH>{B#|-MLk56uDt*PjM&zrzO4oDg3#4{^T50BJ@)oL(OHtd;L z=RYFF!A<4E@RxjpW!L8GborsrGt1o@n4O05GdJ?(I#yZonn@O}Y~btxeuh=kHgg7o z&JYiHukn#IQ$+&<*K}hO&tX2O_X$rqf&oPv3Vn;Qb0tdt@|W2B|J6H78!D*LE^o|? zgEf;5V08nDO(+TE!obn&BvY~;&M9oZ$(L(1p#2Ch@v(6?2;?KaxFHPRAP5E}`s%|+ zBh-e5pY-6LL?`M~V}IQhm41Zu;+`x_9OXIQw!@AAe3D z7}e1m5R#MeH<G3vXHf8gu?Ua{Ah1vLx~?5#?Ez9S~s*yS6_ zuY%|+t$I^x5;ZJ&+!#e=y#h?_sN*UnK6OD7rCbp;Cob)`PYhS#r=V&7GX05*M|QdL zBjbY&t*VX-&E2Pt_sGB{&MSfO(;a7K+?6 z#7$&^Sw4KmUFd&Qr?m4&uIh;A7c()DoY2c#11V28Mfx`YS8_DSp!B;{imD}9M6n*H z-2e=Dqp)SηUR|g^7Cwm`82`e4>jh?O`)2XQY{1kDeT2%1MyBw^K8A0bX+;7Ab zYaBXQ8|RAGpl@Xg`Mx)eDu)>=!c!y0 zFs}fc&2mhzct^`ojgH6^IM+YY4MUV~AJxjfJV|gfR6r3B#i7gW~ zdvpEwM%6N}jT*#f>0(7AC~~gTxf*O|)Fu)rGCYnXz4QOhP{beF)Zehrox!@O#m|8v1 zO1cuaKO-ve`XM3j;oKWV=E~c*L9p8P(R8e%?;W3YZmeZ1W!v5_GT7%MCTXo}Ib)*- zl%mfmZ!#xQwNkFu2opo^zt4Fpp~8M6L5>^1!Oxh^%%8Aba=_6rNR<3$e&^VuvSRI= z>OLHG*t*51h#OWFO%qFGY4cRPF2~sVQg(Dq4(iwnP1+o;iD{~-1oKBaR8DURJ-aI9 zJdzZn;N_Uw@XqQc-;tv{zNy_{tC#B362eg9Wcp71j>AR`yciT;1TU ziHm;#`cF>I$Ada#0T(2G?NmjU7TGKAp%!EcDTYW>0M}C{${q@{E8Xd!nQEHa$Xq=; zS{VC@paQQJ8tKIu^kZ@o#r&;*dQR}1o?$3?>4NQ&Y zUG8z@?lMy4mx{II3W4bbafsVz8^<WJR9|_nQRTn%Bi$UsWql#IXY;mT+s?-%8MlF z_PP2}N9C4HN?K)oOooLm-gWpKZ*{hVLpPrwJxHZ7_q>*f)@DT71Q4@qU8V2|uUUeP zJShmxHkN&jKL-)B86pKd&_G0$cpO(;J7Kv8S(g%k`I{6sR#-MA-eXa9iiA`q3&P3m{}uT z!p(tuJ^>My#d8;p#h8qIB}a(-qJ|Tgcw80ip_SGl>TS5N*S+rZ+>?D0aO|_AHOjd0 z;NP1VIKNX@Q6oCy30c^Ddp;wvTBM81vAT?dLX+o|Inmga>MIpw9#+XVgZpF_Fno2^P-ti?doiC-Y z$kcSUb=ARi=-LV}dUeo-3!s4AM=|rk&~u2$=`I%ZNXKDnIH48mDR8Z7#}9&S;_j)! z|8#zma?BKg)#yF}{e;U7JjuRIEI{F)*aMe_NpKV0cB1YF!fFSk{$K7&Y5y#7MwUdZ zZdLs-gr0fgS_Pt}5#Mv>;_up&dBC$0PKOhur2kwH>#2h5s;V#dH0)PtVJ0TEhvx$vpSzcrAZ(2ZB z2&A||v;Ere!gAjX*C?SQC;g{995#>1m#4MhHg-f>@<(|_&{;<2ma7iYVE~^(19Wn7 zub=R}e947Rn{axQ8v4_+N{wOr;O0X{O$<5MIpW0OniWMF`fwvsJ$4e^bx3wOu0Dsd z40KHYo#Q@X72$qOAfU4^b_11ylk7$-Fl7+z3;Aiq&NaA5NUYi90CJjyaV2*?+Vd$O zwJ?#DYl+-M_jv%6S6Eixzq8_&1UKI?ymQ<@zt=Yzm)El!i0*N1?QRPnqQ8j%GQp$3 zQY{4~S>3U3|G#K6+>mK$X&3NeccKo%MYFVOh!wrDwSc>b9`SRq_dhucwde^aMF%Jz zY{%<7cnWccug;C2I;a$8BYbh?RvyWYr8bvAIm?AL+5T`~v7GByDg>~Shz^!!Bi0pU zmhrl5B`Y}7Uk0Z^WBK&x9)DTOD4-b8W zD-7}=OKz6XB55S36Bn~>V-$99_ur$z$@i2|;mPf?1?q<5 zeFuD9o^AWZ=_Q!id>7}eCx*4)PaaO{ z>Yhmzs_%(H#H@-UWOf6Bea~EFPVXc{%e;9$&i!*@xnWOvZ0DiMA`<|0^mGfSt*_^Y zN+~7^%0(=fB2%C6wVh48xsqX;w+>b0A2~9WeEF8!HY{Bu)xq44-ubMDAcTV;37>R- zk}nmbInW7f)yfp|n|l~=f#(TcNV!%tjCEUXfYJj}H2e>OT|?N(Y(=U@j^$TT?6{C^ zOtdxwy9mN`~Rd9*dAK4fh@7O*%!O6VT4b$cvS zZd-L#iz`LTGG5arMtS@je3J|)5i)H6EY(ebf;mH(tt3o?zh z%VlnL@!a5LuT!)n?4L8x)*lD*aQDIt*GR&SWe$N}4rRgmZx3)eF#pmN%{Hp`FZUvL zEA%DDHOevkhLGks#z(&Vc{p7fj8Kf5Gwjxn#HzZ<^SE!3@xBTI$4kqsZeu{6J4D$OfhO z9_7uZt$+J0_Eo~LOSGtV-ZykNdb;?tONHMYnv_7lLm#!6T-e$4hTism8D_&RV(K88 z6_r$&%^xioX7D(=r9)B7<~=}LLCRzba8t4HRKu=BUzM)q21UuN0`CJQyif6XrBM>y z?3PDW^*tn_1}0d%0guTGF%DJ7RV>K$zZ(?_j3(a>(OY(-@zIAfis7=1GgB{>`IFVn z1%2dlfZ1_#CQ_(|DIWL~%3(?VLzX!kVS7T8Tn*-;STr9UuOdw?ct}B_m&Q-_woP8h znV_|`qy*!aLD}Oc5H}0Mpm7mZvCA6w%V|JvkK^Tp5E|H5iV8gU;C2-5{K~f1z_C{R zkj-yW+1_xQhs;g~vDIV;e`ikPT5wnAt{uc|_ygp~^5I3Zf`J!_G zZacJW@FLX(mc$`UpI@ih|26@+{-r=BicR}(0g2&ur=<>`Qh`xWOCEu`oq#7Pm!|?Mx z&w2e0kYVGb^c(;y08t~Q_lif(&p058RP$TM$cj`oo{-~Ws*r4sC#-%KmHRX_*+27 z+>9jjI-CpjpQ~JeeTwZ++Vk5Ker<}p@H*98?nRrFw%biNb6e3K&WZ!1^{+1TG~>Xh zQbbiw8~N$ynIEm&;{{SVJ_5U(3G$4?#tQx?qsU-Xlo(GqYg%-Q*G;g+WNXalcI5}D zizt;w=8UtDeyIaiguH)SsADwu?4m>di0M#dJo&}(?q{!W{!LyS6{7BF@dushZ!USV z8r)UXx})w#ddjz%9|~}LgP5;T4I%uOkWRAd@=ya0&A|vjxcu)RLwiAj)m3tuIjGbG zfbt)$#kxAQzT;?f7K=*%u{F^OT?SnTx`bMB3_lx&F2hn#{*dDgQ@N(v$}BH}`#RR3 zIjSN78K*|UcxZXWt(E;<}G&YM-HA3sq`&%#Yg8Fb|NGTGNcv{y(xTfQ92T%4J zL7cVaN63ft)d@6ez-JNMmvBJ5Dr6VTB`PQ3NIk%k&1)z*);vkYx71mnj)old(+$@Q zbJdM+Fxf__<{j)zSb7j3V;ZWBgeJg{$)beKDsjfPcaT&&+oyO&B_M?EjLz`GY*m8*)DSAW5QFW|-qH{P4z1_A)UG2-LrnEOv5gjU*4+J?2yP73lphwrzUwlQsFpx1 z!H8m#5bP+C^YK%^)VM;`BfO~)8%}ON4sPy3Ap|!71j1@A%>~UDUY#& z<>rK{`9_wSQ%MXx=91J~>9^TIm%d!sP)V*j~}}-Q6JfyV!3&ADE+UY<-n5jQn*(!zmw8TJojCJZTF0pRbMC@5rmtu%I#gr4vXdrYi z`R2%V)t+_|XPBoY2L;jkzz~z5^-P87B_*MF$K6!&r2mqtz3EKdT)BH8W+uCoctO_L z$aPM$mm3SU&0e!R0__StaOyJCimi&UPbrB_WM+p-BB9)D3Z)YjPHRT5o&h_cl^i}e zokvQ>Hcyb#x1`8xJ=r|6KRpO~jBl6hxTlFw$bmSg#?#*lJYMJrX1w!_uJBJD?ml|< zqb$d3JJVD0Y_r+0k(FTX-;PoD5c?Uq@2!BXH)L|lHeRp)r;IGP_%3_>&MBVvMq zAC%6X1S3!>VIkg$>}OkE4j76jmG0WW6qnm3);~3-0ZNDP_(>3ivK|einNLb18txYR zC~@R34R|gwbgeKiZfJX^im8y@sCOYAMbwe~knDHPP(`MAijbHHGAUnSQF!GkQb;g0 zHIR52MGo$kFA{R$ZQ8ew4+sm!xv^+!$rC|B){`UJPM}5D6wrE`c>3!ihH&Zf1+p?O zFF|+lf_IHPmjG3C+;_F3RYeaE>?Rn9ELSmF6T*((GM#O!2uQoa{U$G?TK>~kz>$vH z7&{%S%EUOQsc`Haa~!kdSTxB&?rv<;quxtvyLVchx?)6dKe%5bo|b!|5cIukUZO~s zMhucR0Ec-p6Y7Zo+IP9~Hh2B8jVuN+Mt;VMUzEFWv^=&nV|z6?@9+++H<6WTrPypB z;?8GD5t-b!yj@A$)ouABrS9-d{TYC>s`G3po9UCUPg*#ZtHOCAEdrcYj)7a8F0LkP zKs^^CF8+s+_6jPmQv2Rl@iz2DNT^7FF}l+FVfZN*^onsk43z)8Bmi)sIJ$g>ttho3 zrX9IGa?6Sh{ZZUnjeh%6>q9SRvUnW1lG|X%T@OFnI#8AcY?#%XHPPkXijTdq-;6m8 zJ$xr7?<8VkleZ}dcaAh|Z8ehU&;N1^YZK<)g&|p)G0ZviR>-g3GTEQ)OpXtitwj+W zRM$&moG*c7e;Q}Qz~U1;4$(Byvu^?TASXAEEjJlv4IK%Zx#6A1b%+JpI1`zH<-rrD zG+_W$TI){##dk39*E5LdI=x_=MOi=asp9J+XcbQ&ciE#jCUj3`pI$w}oIfxxPn}yS z_>(yelz?ir0Wa1eo#-HjN$1o-z?@8^#u&!b#8!&ZZ-^rjhPw+oCp$Tw&$}ChLHA7e zj>v1Zp*~29-_Hl%h@gje@cFvGkVazG<@VTemHUfNdp@5U-QE9BgIiZl3R5ev;^ZrejZ5p_%V$mwKSuY}#k^sv5bw$F3%r{;SR%L0^_?VGko2n7w4;q) z4Od<3l7d{-F4V%YS2*`F#aLg7UV^|EFJ!Qe$h}7jYL&?fkZI#7j=OP+nmIu^YC{|+ zr0~>@#_tF@Xvlbz`mMi|acs9_&EEqjKvy|r$^)w*Kyu$M3a&AQauG9NTXcW+B|(pb zk*MS1c^^PYUAVaaDX$gzq@hi!a&&^cm}lzzDRCljkNmjPv8@5EX{;gCiFJkeDzgqs zMC^6pJ>DcAgiz7KN>6o>Xt(=qf4;TcN*T^F3Y7@?Q=orh!=nsE>G+GzFD@) zvU&u6n@0`WC%1omScEON^&#BBkGSnH_s()cRogg-j8bRfrf}vj0oerdnIE|pubEpt z9qnIeC&@n)G=HRv3D~L-dn+^(;XQvx?H_yN5O3s zYVO~dEWU`%(R1X9DC!|zlh?18VOevbss~Dt<2qJPB5U{%Fvv=zdXmm<{a_rit7z4y zA@U;72WT+y10oZ>h+#s>@<)jB(Wg^=pplPCq-bwoc>U%C$xMu0WT8Fib%6hZBy5BG43!BX8aB*QYg2xC$wmrd?q$yvT31%k)p-q}SSC;L!i$>-w9a3?NO94f~ zk<79284hCm<%0G`T!3cod0O7*`*=oVz8P{MN|m>J5jg9vzzP#2seJ5rpv)p%7pVv9 z&#Bc*WNQIuNe;|^AWfmoZ-$w>xPQvGM2YNDXj4H{2B5$X$F>h9H_wvC1<1%t1}LTC z6?|)a10%)^psalX8DXi#jCwtBAV888$q0Nk+>0I#383qlgS2invgH74&u#{6m_+0@ zHuz>rJp_l()+f7$xJ*gMBxTsaC)XHHt~~wBs%W-(27QZDPe-8m5|Xys^K+t%vZK&R z5ufam{WPc5#*rSI>6nYAtjUR}Ob3C`67-JA<%UwT)ZCcPaM zgPuZw8$gE^oLgG&>bG}Vm7C2VgCezPfJyvC8%phDE*qTO87f$BIA9#Ay-^Np>BQew z*_hnm*JX#OU7I8(_@N_4yDVnEm>W;*0%*G8$?4vGC*ytB%G9J&hF0amBLF2OUjiw) zkTak`zcu0neki90mk18gu1B3JhNB7KV!fek03jgOE|VCgLG3|V@BkZv<44|Gk+N-p z?Uhf3gXTwhkIUB?D2*FkvlYvWBDrP2^Ny_se!O`X%u>TR+ITRA+PxipCVIipDTIok zU}6X-6#~o;#s$O?o;lhL2F%8vWw~Y4coDFT2bV+p~SIufl!$Llt@qpG)>y#j(oU4_DI3Pgbaj;v|II(SGWNk~t<#h4B z-Z4`9_Sk zxq}9)aUS|>FMW@W_I7>fX}%uQrHO|m618`n%F#Z`ZFkAhPwiB+U=-y9da2y8=s=(Aa9;tUV8kkuP9iM+L!CZ^2N>I|_X6 z1W-wwu1pett*X`^P@oOEDT})*xoluNKV|ctTavU`-*X(niGsd)Q4QA$qxDz-X6(&J z7A9)A_w&k{=J59gmSHEl*0qkJF_hg*aHnRss*tPgb}$jw>0CmZ&^~{{l&L z+_YzwO!7eOdWQSI24rUiEHtC)X+Z6LLsVH1oC$fF4;cGW%dZILJR@9%$TLjAk-V>@ z0D7!_kaqn;HkP{%r%}AMgAGZB>P(2)zM6*I*8$z~Cy`*No`%cnmS*(oY$cNTv zVJCx^w2M%WaoyYC${jTtUK%i3SUO*c-zE=1K%$z2gG}@J+op8xyiemE7ix3@maHnK z3t=$>61;;l`sQ2S0B7o$)3YR0DvYkGyj4aMOO<|-A1nt6Fn%$;?v1SPP>?WyA+$;^ zjT0y@;{ZEUMP_PmL_#h+0;wFW#h4}M?YSAi5|Q!^7lwzZa(w-XyFnWfcqUff7ZmLa z5xVndUIZFJ2Ba_A2CQQ^x0qUvH45lae(-V{C%k^!nOyhXzvNMs89o}3^qJc)0LE0< zETFmv+wuiHLyi38T6&>;#oNZ-qi0#bU>rMr9#rf@u*qdtDdV%3cBO?;YW|)?7 zOO_qG%DPD1SmcWAcpzUKpLU@Xp0noIzh0kI_)?dq%{s&X1xg9mdE$DIjZeF*&4kYs zCu2hBCcjI8SP8=8o3pk_jEO=LEMN(GaUTJv4`QqRQuPXzN5XwVPF04IYJ_CocN(C^ zcw}#n%}3~GS4M7bKcFO#XYYCglWt4v%uOYzt9PWQbGl=L15Zt6z~y=x>68kR&%+v;M=EkGU5*BV*8?v6^QtG3 zqIH$2;_wPZsLxZJ(~pJH2PCgwAYckCNov%AvU!=#oP#FA&^8=oxgMdzYE9}y3N#O> zK?|vax-d%l`1l2&924623dfwXqt0CZOS0SMM6wK`ra5kIMX}qL(?SS&MZ$j^+v9H? zA2OkTBjpHOV2*SFaFgVl?#LugDtMi)Ks zSm_2St&S*zRznuCO)(|-wAWv6;+sZ-LGlqeLDcNMX`k1;D&T=kar(P~bWVdAPa=ti zf1_O<)DiHkTQ7mWG(HS353Q-V=w3@{`u|E=iuE6h05A;w1FxH*UgXN+yFkG&aTH0A z2lip_fvG3?=}Wr2-@S?Xm5HjXlaWBQZ!GJnJ|WbWB46qajGbx`O}OVR3uAB}h5npp zamZM)SuAl|EhHVuXZ)`=hvGu<-exN=G}5A!{Tt>Fe5LUPQrSC;yl+ zb9}oHae2i}`D7`UUgddZ1|6T+NS`J**(Rx-Nv4lo;+=+8@PrVrw9OOWoz$2+S{&~T z6@%1`C<+)RsbfSH{0KkK1d@>e21L11 z2Sy@Q%Oj4r#KI|9$%2n1h|xf1#q(iC=mK+M&|JyJx7Tl>F#S)(sHu8uYzCE>%pF3XMEjh9lGz;obF z`Pgx2m6RbvGF^(B))|r@xbG5 zPX^|1!FGI>{HFiaou^z{Idh_(057>^*s^K3pd<{oMGZrz&w@^y^XjMucU~+)D`{oK z_zeHE4gSL5Jp5v-wu}3gh-Llt-`nTmVl%u)p3itr&TDdS68257Jz{ zi-Wu1hxvz`TKkeVe(#rYoAlL=+%QPVUMMeskJm=Cx;;57`KUHFPNS_*rJ3aAX(RDr z*8AvGeB$;(36**)t`d!gij*ZhhciiOzPSlB0U(T~m6c4=y9*kt9M(IUOq9j9VM;)@ z<{n4n2a#25^k=Av{udxeiZ8zT?ToL-1aggFi4-=}&$6k>_mR%S87Cw!maP4!@(* zTM0k{lc>%1`Q1}=tqDN^GfV}p!FWp&!|;L=*J!%sy&Tk#VQ=^~Iy7dj z>ydsg&9q53DgLFxpbGR=9*2Th7sG-9gBt`|x(5j(WQgwykjLv7iKy7^jDR3p4pJnw z^h7V1H(LaZYdC-;E&sKiV=gX$2D0p%jWs${Yg04A*0?2#UZM}e7q*=Zy4nJ( z@{bc$Xp|CiT`-Khr=)%tf(^RMo^ia&e7blKV9EEX{iPSY-_2cq=}-7(aR^34YyQ@8KNK4i5m_cA8uVjC?B6@an&iqL0T!Z8N02+2zUEQi`c5exmPBFBiZqq?-!tzi zku{JqxYi$QNUHOWNkn88S!-&VdueBkFR+s+Va_-8xM!%}Rm&nHwOC%CDj462(XbND z;D-WN;mX$4UOX&ItSI`C7zrZ-^)ufVgShde&k8c*xT!ZiG>O$klf1(h{VI5ZbU#cy zLpJI{8RCjrzy`*o`0rSM5Fgens!t|H%+1N1NuSN5#_lR569d9-#YNU|v!9Dc?B6NiwsRT~zTvZ~J2m<`Xuu~qnNpSQ=gU2Ej1wr@&}Hxq2E7&NQX~b{R0M|4^71a(m@=EogXSN z>*!x}@=OgeEt=)h1hEOO{O?AG>TB7Tb^VIpw;>dklqTkZ!yklT_M*PA-V+}o0F$x| zv00FI5X!??MZ$8sY#kBX^Y0j!WsRwQO3k*)H4lHl@NvZOuHL!%EJ%o{nO^CFN9Z0oBM0>2m5NY7IfhREST9#&hMU{{XJ>K z0KKyce-qDe1Se^weT(UZvaHPRT#`>a{gs8Hp6vT7xQxz=Px-@fRD@pHx% zw&XXkB`iJ!&G+SnDxQcxi?0y3;j}R}drsa4&Pi|;d3hm_noQ|)Vu&zmz~jk8S6nO$ zhr~=fS#2UsFn(8;O8qN{=ZD0ca5ZxC2CsyccW~jM2dIB#hqxwyu39AMB2?WuMmh#c zw7DC9yuG&0kAef{rkE(XeOfJ2e^{#z2{dC22g?qJ>>eyg=;}4e6#r}Tfoy9>Z|le> z>*lUyYLgKirG0cV2Y|XaLx%jwu1HLUI`+1R{^r|XI0{Vnus{>G7zpPY5R6lZv<@*n z1g<SzFn%=QOrO0Z zyG~dH3NuyUSOhOj^s9-6k&XeM>mzl)P-1awdr<)mLSOJ``VxX0Fxgb< zTzN0^n|9oxynyWa;g_LRMx$U!e_)89cEbX;FDzlRqK??33@j>Euu7)9^Q?&%p@1N3<^T?UOv!F+;v@!+*6@=(DgOJ#fez@fF_iRrP|n1N8RQ+%lvi| zpPW8K-^3_5(g_W-%fnI@9NKTa!3>ROX;EHfum zrHs3)p`zTQr+PmBcRQB$q{k1t<{dDtoW(?VbHzVC>6Ve)9`mz)KW7GoSZ9{eZ(@BA zf$lT!o9hP*RMnaN0bRf@!R^zK%4o>m%2tnV71eCX8MId?+X_;wGl#R=fhEDfa#^5l zWPPYUlL)IyQpS6TkL>rVxRktXax5%7HL?bM@d;t0tXrmWLg!fVI;=f}=7&GX_}uM^ zxG%Ec&UzjS7o@}CxqvzaB&NUK*x*BVm*3*-jit*L`!6Kt$a zA=G0zuN;JJO&vh3*@ zXav_<789jZ|8O#Ak@y>+oOKmL+lgAs?Q<`YJA{pUO!%Uj1Af%TUmvpi51XB+b$+KRh*8GKQncg7Iuf4Z zOPtRDF6G5gv~`d}vMCtfJd`NRmSl~i(8O8TXsTR=o(tiukqAa+9;oFURk)ixhS_RS z{T4$!xjV5b-RK?UmDi)-cjBDQ(+vCwUWfU}+D%94-?%n#)s*~dmAot$fitZxx=d)r zNtLQh`z{=mjeMs*Enttd1i!J*M5z5$VOFaLZ*?B=Xh61V$S`U_NwXqO@P#J(bHn4D z^`VR%ZGWPli5^+wr&su&Q%E$393y9o%FsR$;@^ntEcF4<5J$2@)v-^an-5)vg_A$b z;;)7lt)&r6loNRw_H~PS=%j>nM?yPg+!OSqFEJb{XNw*b@MTChN+(qyA^DckDaZaV zsiH4{(Il~gFfi9*zcu*zAF?e4I>IzHrui8`pIA@s;#Gf;!@IOIu>e(v7Ep}TWq)vr zD_$??0-bLaHJSAc?aj}B0nE=Do91fILD6jGIjc}7CYMixr~a|r3Nv(II?XX#JQ|dt zu|I9b*;@Vh7{#3*mGG1_9#KQhF03GK9^n*&_7efF%>q&(%guQs?ZQM2S=m;#`b@D4 zbRmOv7tM7>(q)TVr*FP3yRzoFJVFHpm1+B9OyZV~C(AE*o^cKuft--k-a3~-k&~`= zGNC+*p!#YQL2DIE3dGI6@K8JVK#+2TBhtEx-jZSLk`oA>ZCj5dJ?v6j`38zn=ima5 z(Z`8snzE+WC1UNsj$UZl=ZY0b#*oZ_rTJ|Y8G^`?)Vb#r1H{|OTKi`4W^{+6F!_kr zf-u8*s_6+^+?_zrO{=g&c$trwvjwt6&PcU(Pykn*~z5g1T9B zk`K4as*G)wd>+@EE!YEQOG6OMnmG(-$c-%hOKQW%G3t$-Fr9v1QdHbl#XH0Vw1@z!R|k+mEz1t% zbG^eLT}MIvR5QNe=!b~Fq@Oymx{VsykmVbOH#vT2m9UMaDSa)H6;?6kLG~}hfx@Qv z{A)THF&P@Pbh*m=>{Y`M4pRooh*LtFwx0>S`}+8jQ~3FGYgcVe57d~~kD%fTC5U_zG$-dDy$Iv;xp*sHi zE+UBNE4-D&5O}pALjtG5FGhp5yWVfW*iz-tH^DW+L17d%D6Ml=U2=e6RP6epFZgiz zZPB1>$tC5cGLuVJh(*0qp~y0*-yVQt7}GPyrb+(60cRt#gZC~y5dGe^#bYv@WPVQn zZ9_%smt>Z_^xogHs;TL1$3qIKf}Gp3TG0V}Q2}gecFBy&=*}r#2X?U^&?cu6oHkcJ zp zMN278xqI^w1H-{h_HROy>;b13BI7Oy>Q+u=+>JXj-K1qVZv*SmCxmA)NgF=*<_;L; zpI|Jpk*=&C9fvQhAE+H8@6l_lYA?3k$q=SEhLAjCOTA|fCc-p`WE=|)LRMR}+3mM$ zB+*0=sj62Y8}pFIefOXn@iIqfm9Q%>FX<@TSdyB|*(x_I=i`HkA8WU;c&xqskjbd# z)8BktbM>EFNvgWko*Af!kSpmqQjvp(52q$JG8T`-%b*L4@6u1nVAW}$~l`L-OA9uIi>=LUcg|AV zW`tjYmans!<3uRyugocbeLw@!8Pm2fGxYLdZ52IC+cPzkIy&5z5@iV{n{?Fzd@H!N757|ogOY` z_NVztwZPc1IehW6#?vbLR~MH{phMvO)0VgZ7jWGJ^Ry+poG$&E8vQkxhNP_xpiu|Q z<`yrvW|jb81Lj#uYwjkc1{qBd@+5>KE+87Xo2G`Sklj9x5y}1NsF<-M7eo8rLx7^b zabWu-fdT<+3u<&z=L~_>7}y#Kh)BEb`=h|~*1K1l{~c0a8McU}P3CyV*;QOL$lmE> zBAdX^69aQ}A*^Z7sq#@0@Vyym8kkM9vg&1K3^7BX@i5=w2Ab*kND=!zp05(PV%-Rs z)yw5`%WglJcbsS_i7H7!x-?n)ZHNwQwgG$-FT4Jwa?SmR;Wm#nzr;@jN@ba7R8g@L zu1%LOQttG`!coi|t9U66)M=d(MgH$W&ffty`yWu{3%A!l zka%yw6H{S4i zH-6Ee%QPq6iqhl7&xw4F# zc!rTy)P~u0HKX0}m?Silz4!Qr>IbHBDO_A_2F9zVk|6xp_}fE-ve}^+Y8dt*1q1}!L3qFvZ!Qre{M^tJeZW1yqpJRm+%7SX<#yY zDx?$_j6(MIzf_n0?VS|WHBt)NN%r-q-ihs`MNzVrw@>S)W+)hkbyYM-LA{{&KIMW1 zRA=0UA}r^Z>HFA7Bs=$SGd2R0%|40y4ra)J{{btM*BR}vA7b6DepMDw39g#iA6Ck` ztdTD)^qhEhha)CEClz+CuysR_m#9ouH2m60i>hz=se^LDpO=^$%MGzFu{CAkDO-Ij zuIaK(5_j*Qh7RS$b9g$${z3E$#3|QS`*u?v$Iu9K+C@4icw8tuFc+%~noABEb zO8~x7{-|cigD}Y2N~vRI=`nj*;vOp`aqbAM`WI=jXQrWbdHB1a5GlWdNj=Kk5T?Ow zI_<7G=zD#?J%^5;&=L*MLKIsL9J@blz$|$wUc}wGuxU^`FX#BNv_sEJ;a;t?4snSV0#nGC3yq!0X3){jbihHY-T_*49>IvQa@(LT*8fWLit z>BKg%abW~$r^^7I7!Iu#b*ifLVl+k>IeRlXSvKDSp}X_mf1$%>?lo(0r>T;Du||wO zYc$M#h5H#=?4Q&_fDIiops4>$j*cQm)_e0Qa<^Te^9+se=;V)FsMfgwOjl=YWtXY7#qW=-VG!s4om_5>2*> zToM^Kwp-Qd(2vYa6@Q&HHxMPU;}V;5(%h{2kq;U=nbz|mFbe=}iSh|O${7N1Nr89--lE8eD5Fa3H*zMV4aRAcU5Xr8qlS(rEiPmq=MrXPmQ&CBz{`<_lj-_baCS6-OFcp<4h7l6+>$<mw;wNq-qlcv?{s9a|2ml=2VeRAB7~3 zLc8_+H!q}rt-UVNJiHyQg`1R=F)VQ=wSb#L>Pc-LiUjc0^*x=|)R_}C!(b~r*UA4S z=$Cw7$ROGu9!@cwO+Ts7FwdWLHMTr$ac(>&&POzD3+yRC~6T6wl4JU&8h$$Y*J zBT}}!1{mxT`=!!+8XW+zwLwZZz+>DW6#XJ%;mpaoRS?#|i;(&GezU?PpfU7~^iJ)s zX_Kk-Qm6FnYBhoTY`#~V*WpeB96Y(D*JRnZEgTcy?y62yGImEGc*2{2v=89Qk(9+z zFBJ>Tsui!Py%w(oPS6e2Vpw}EZb??d=+U=R{o-=<;?hi%JEo|I5PvXeTG#FV0BQq6 z%z3StUwOd0ngFxH-;mqL34gkV921fL1=5Dd{Xs!SoDqicr#FVVgCN$zR9&$A@~ICo*wHcUrl5@-xZp*DaLN<`Ksm@@IZdm!~xsF&Y%=)!$vk^m|tG!7Ibn4M}m9$01X@rkot69T@UQnbddmdBw4 zZ42v>!cX?%{+_liVR}lRBsx(dd9$?(J+V@XUl2|4Sn5i^8r~^^Qh$kqi(>6TaC~8k z3;3e*oH1}QKqQiq0G7<-RW6aI?V;E(thLUo)(4fHQ*N^{drT^TxI+_J^y_hvi`2is zC*Ag-oLv-iBKZsbey2_(ov5DjCfGia!i{#ALZGWN%kCVRVP3@K>>O3+QqNUpA7vpT zJMY4S%fgU16;Uf!MIuTwS)F?}2Yao-7DS$FGC?z?4QDCe0<&Q>c6`flf7bqY)8Foa zG}$6&+i_=ZoFuO!VT|unhwXQGipV+*v()YVAHdBVqg1@Iy4)?it6so;4$SDP>FQ#s zZCxS)6$)<xfV$X1XmHQFJGTzImOO`8mAn?{!;Hf(bcW>Nt5?H?V%>(am#S~! z?h%5fsomF3QW(YH;edkFgelt!L(hq}bfR69$jh-E$ZoUeHDEM<>btSKg;~^#VBbMb z`na!42S}DHdWmZ8Qlpp1YC6bSXHi?OX}{lqn~0avSrr@@cp0RvX=>`B7?OiW`Oi#3|9pFTW-K z+^8QiJ(l7A0u>IZq`vrZG>5g1;*LqlS2kXlwY+RaMLBt&B;`tD%uP{@ar+U@w zwYfPu06fOQ`>R8P1=hg3X+T54-F5x2dB5xtO~|bk4p2fc_VNHn7Dhvh7%i6F^8r>8 ztjGr2(LZ}m2!oiZiO5;y)cAuHPU)6eV;s`GHzv2** zjLBb#b3R$#DF$j>y%YdphK^wsM3vU#Tj-vojc1hs!3@6|t0fu3+&Hd3g2w{<1tyw) z^@e%PEgFClS6AV3leionw^4>lB%R5vs+M6LhNyaTYXXk-l=Q6pvvksj@Bzvl-P%fV6?k_qeR%*nnL+SsXqa#5j*=PcjbeG~|~o3K0!-naX#sD&qORCG7|Z{JNw4V6l`*YMY4 z9jl}RiHx+|;q_a>>`yQB*CCD?y-8To!Z5#`)AJY#I3{^Vns}0xcg%t!)I0L;4fvD? zR4!rN*9*V$AULcFBg8>G76d_quajS(fhMtcB%v#LQ0ooBC4&KqN{~(a85!0PFk1Wv zPJ{HT2Xl!lzqrS~`~78EfR@c<81a%6r)FKCJsRk+@ulE~zXQ`*8eLa3E^fY0y1*ic z-vM}UK1*~Ng*@Zqq$(0d3WW}Y=a3RGULuWy34#Y0use2db8&@`nv$xdMzyc4P0?D{ z;+`+;;bQ5MGFxMP);Z(_n{U`tI3}j$mg&rpi5U!}BJz|lE~p8>Q>Q5PzmFQ{uP|Z& zvwmg)lG;%c+inuilDyw}*P*5x#TW#-^7TK%6HdDNl z(iA~)f?b935*JM@lixF4a9ts6pTD;fM73@?` zvlmUf=h&sd&c9}ibU>Vxq--7yAGUpodI?wf`;MBP% z3!&g&YG*Tf6+^#5qdmq_`m&NK8l>bjsPw}t_f?^<`N*^86d(c~)(PZqreG01!anT1 zS4grYbb6AbusAIXO=`4s7hppbQX9w1r}vs0Kyybb}`Jf&9Kbl3Pw%GDg=^KFFq)khfCx+f{<_vaTVmr)jc_2}k!3Du>TQ!u zfRE_{hZ?`^Ot84yVY>WIL2_BvOrq;@0kqK&XM6d+O>n}+R*+tf&C{rAbUhGp357# zZKR#Pe!YM%o!f=Sji}c5W(#iUy<&182-PiP?IhIKDnI(TJ08`UhM_hD*mPH9v#Cp< zg%gJv``eE{%-^%qT?)DB>}FeSb(rW3mEmaw$axPh>(U=kAT*Jcs$vXe(TZOLYZfSM zO%%1=)x8FmacZ?KL>vY~f<*!VIL7WhcED~$OMwI|RPJ$4$7%2uq z9jPs0(`aZ~f;_%nz;!_36vT9^&FK9T0guPc*~I+oH-^trtyXNo8TGt%|HIR7LiIO= zZ5A#&c@;v3HirLciU=G~>6k;~{LpCV=N9#>#RI7A`qSE1-o5gpmZppK9_p24olG zGmZVD66qV=YaU(KmnS9E1MyUjfn}Rrtxm89F0c$7^^nxQ8&VNfc+(5sw*fKKYHrz5( zmkcG`?&nP>I>XAPLdIrk)fFmZ{-kl=Lb30x{+fl`;;{yPvwBw0n}ze zhBLoo4cFP}$8|T-vRa-G7!V~PDMF|~^%_oY1xyp9-Sq1wT;O=bG+v@8Ri8-!FA9PEFPW@kFk=?5a)sBedj0IWsd&E*n)wv_yc=8Br zQkdhJF2r$F{*=50d|p>?gI2{h6a4yqyqQnlcuvr+H*E=cXzp&mVo8W;f^=z zRsSTZ+&f!xF?D&Y(HZYwK3(8@b{M9#?gHx3NGvWD4X)I)z?Aq{zyot<*)tr8V+~_Z z803KuzuB-pv`|Yv{Z>6b=Bw#oEQO+)BJE9F zt3;op-@?6p8(1CTL_3^>?$)tnP0sa?J|KOUhg*PUvkOh1?|R*N(irmr$_u~%NSxtx z@<26_i0g+?nG8Hf1H`kEIoM8r;o*Yyzy5%9uR|RhD0-cGzy0LyNQo3TS{Gj29Nm*8 zbfZQqw!Ic(;6O&gBpjH`@~>e)*anonU5w4iSsk#t2W_e-fYui^+vwV_qc931oWw59gkz#Ee*)hL%;@-aUVhSWFnM^`tPi1g8-e) zXEA*f%WBx_T7{39(ZNO%V0X+}^r~Su&Dy8@s{>g1>NQG>b8kLsnv@b9zDDZ@STZOj z6!5>E4?s;=oMPiGELp73Uu?0>y!BcI@pkC>OC*xz2*xVgAqy7KqXrJghtXPc^nX0> zot(ZGQQR{S&mP#3lw&lHAYB3iOA?%lQ=@waCywiNMBnyneErtyQ@sv-)B>3zG8EOf;E8{AXQH$l7FiMe zWStrFzp&fRpB8VG{;w3ApMPC%S`cnJ9G4epwwr{o^Zrzp=I+CyRtqWn~#w|((x>3#idPN3cqazVanD`ZIcU^r34$DNmr7VVHq!LO z_s1s%evGlAu`u`WKQQk}h==gbt%`YX>r$J{#f@b+2(X&G^~EU^rGc1~Oe><*4V9=x z!<&atcly2MPW$$)M`*T1v=tlFRFTwUX z;o2#w?DgvhC4U5ZlvDi@LB0Sig)j8;%NaEs+!oQH+*jGSj8Jhg!=c-9aCO9+P{+eT zT%7S{(7F&6JufqF+w$~@rATqWP}OIMRh^^&1haA@2;@&dsh*gnW;9WSsBMe|8{Y7S z)Z3=?$tK!2&wLt`@shCXq4uJJ$^Dwdh|vP0bjs`MChiA(e}*0F z#^4q8z+@CPZ_L-Bw!T0}S;}FsHSt2Vls0@{?HMn5R6mU_Q7hKNhKSut>R(_Y(Ke@G|WZUuV9?@}$VQ4H$#G63VLU*Q*5)2&=A}lzP-%cH=ACb2p zrb4_6+uqK1|DZ;qd8VCZ8iq%9>^q$ZC^{L8P=EhY|B%sI)?EUu-L(s~QX&hA<(;2n zinP>LzXKhk$)ug(+ise_T=y(`-cdHL1$6ZsJ03Bu{0*W{x(NyH(frHU8GUmdk^g2% zS+vg<{;V?rI#YuA3{zgVbo3~FR=CDTz8s%Wg3_(CKdKlv;v_*5bAqpy+4l{0P7#@5 zB8Bei%GSY+5$6hzS;HoNpg*UEUwo#9AhLoxCh9>I6-zE+bRB~>} zuZu}AMf7DXiI5~lPEM;I(#z97r zDVqdynmq&Z{sf5)&At!Xb;hn$ ztaq4D0FB0r<227=uO6=c`1?gVKXKxoN}B45ibozeqEQC~iNZCF`Fks4DHflzr8|oG zhlOCr)CA*oFHQv7w356ABD&NImBxR~U_!YA#Hrp1EPWq6E3ajA(`Hiy3^Qs?83*=D z_7{@p?6oCw7s;MbJeQdo*)r|6Nju-Pu`HR2nw$txXYW(R26# zbgHiFLn3;mdaP%bz?A6R;|~+iTur{!|0YPX@Hq1~Y+K;!PF-?<7240^IKOhfhX<&Kf;@dB>DgCHHFGs_NWCSPyi)$C5yk(OJBK(s6qf43z9 zEipsxR`?AQInoQEI&Btv^kR4F@kO|jvdvr`B}J!&5(mlw+8u?7FkVht@+5rlXu&^i z%=)vQm%T0ot5CKrC!=ViHh+WF@TpXY8#zgCPnXziL0#7%yT)mxAN?KFp6pPh!9ADN zDZJ%wW!;S#a7hA(9HG35zon;#0^OcEOdyberTBdsfel!gk%Y}50R#=%tZei#*TkD` z8yVDcgA)D4DLhx*(f3A>+ODR%!;Q?y+^tnFPm^BcHP_7xtn!(y06Pq}H1DkC#d zx?N3WQM!!)+r}f4@a3<}_6~l8i$6i2lGUJK$;`4lcyvY=;DRVIwI&C|KTv7PqT2Z8xk9Y z@wxIU9s@(dSSlkr#akpdtvq{bxy0T3P$lc&A5O=964vHN)Zj7Dh`KQW*0d<6-;{7w z*ZmkdVFhk6b+<|yeLAEuopk%0$T8gflKk!wiFCBJB31qyj z3KUN*QiIN>gO5bH7q2w@oHN%ZGqOp_4XVXRFrDRMs7#0I6Kfm3jHimsj-+tv?S2oS z*$T`H2fb=zHgsmiT4$rdkpTg8Sup7Sm++x}_uh+M9#%D;{m5k=n(vS*q4a3FZ(MWD zHwfD+kSpowvQLDlHcrP%jOsU=^2zGiWA$7xsD3HKYv?$4f1&s-IEqNb2H*Mq87Vd> z=~baYAaAEDHI*~##v+(1Wh|gS*^rdFloUdsF3fMoTD~98av0<%{!!sysr(g27M&nI zD&MQGV>?-&Q-T)J2DE`exN z6tH433m#k}^K2L(kfW~1`T?REZ#d$bl=^TS=)D|jj59>3JH3Ub^1hD(+E;^J-&4=t zw|D#_Q9q^dEv*E%&idLfbUBR{l9^rLiGBfnIKB&4j{eE=8Aeg}(W0i%5eP$)C(ZhJPz2G}MtLX;@l z81W@SzC(O}izcL-?b^}e^0UrA)FHeE8G4s)&}cClg;dOBYmLR8T{H~j6WxgNcQXdK zPG41a5MgNp;dT-_43IG@pOy<Rf*!8W?(|A zmsKD+9<=d%05@EW^@=^9du0MoYyes1@e=SXKJr=Qh8vC6>xcZ5dO}`qDkuvoSZx$O zEoRH!H$I@%f&kmMMt_M4C@ApOixlqtX~NRinLY>P+!`LIaOOi~}dCSaSINRe1WkR2H9`DB0B+hi>=HMZ_G~ z&*!y_2uo{*==(pIGL^V=^71T`lbT}4?bE5Ipon<%leVkKtfqU6Uab+s|1R<0G@XSh z#8RK04l*R2_5#FKRBT>4WrfZZ%Dj?}WmvkvWA8%R>!_Hjay6vZjgffqXm07R&DgHb z_J?961gAL2;1r|OhL>`j7T5YZ!K$F@Z2G!;wDnc)Tow_8^{>0GYwS$WK5G7w@OZFF z(#015AvWn>i5kCWJS})PO%>A^iq#P@N%Q1DuR#-SQ3Yf=SEuStg`9(cX1$AbN9|ve zEPM`ffH+ur+w=S$ z-2P$901G{?Usy&s(^|aEcFV1u3GXg%Y13y|HbYm81{3I|^!g$IaY2PqaO5!#lQ2Tq z0(VGsL^Q_zk^eh7HM{cBLchFtT*;4%4o^PPM_G#u-Y2bb*W(gds1xh4f|*5K_r@C< zddxN!AR67ft?oRL9dLJKJ1W(HR?_c`=IuQskbEO7k5|mHGpA6}e(fj@xl&|~@q60! z#|}E8o~x^KPX5cHFG7!bcDQ>tz?iq&HE;mHRFRy`Yh)CA#e7*r*kJ2I`dR&tUhub+ z6VeRnJU|9ZNC_o0({TlpcBm4%;XNZ=!l*)Y1(1INz!z+v-T7f|YY@z^tigDRc|!-u z*+_m4J<$;_d**$qYzB?cPh}O4#)7KPZD7rbhq+Z}!4q&44-+*t6)+0`IH~Yf) z&-KA^a+9YjCnn{W;P+`e;lp;I0vGprt%VM*p*S0f&%?~4NBV|*+BoloTJygYG^aV+HStYx7)v(KN=sz z_1Z;bYV*ssag%d400^2sXaG8B|}%TnSZk%5v5dCAj&Yw-FzK zE1Of*1JM9}?z1o}1ryS%L(3z#cFVAYnc)3DlC(U?c!(}NMIkr$)9e{gR>O^Y>Q|>o zIs-YJaC3%>Pzu3@|6DURmkuD5rBc6~xYqMgh8Z0=y+pL{OyWIy$!zD(!_wN)G~%Yx zA9hUbk)L3}lzWdb^u!>`F6MV0lD}Q26}eJt#%OALuek!NW~227(%V|aviu92 z4+8u=2vY&%nW_V}McWYiDLCjrrM%dus}_keuxfKDj415u|H8yh(7(QU2Ag`PG%wO4 zg#Osx39*Lx1s@$* zdvbeh(uS|B-FcGYd1s}=Hp27Xds);XPpU(1zogG|wHR+N8Nw7)cfSOyO`iWyN*3fg z7ySj6VEP|d(z9v@w{Q9?lppgbpMAEj?&PPI#`m{PB2gW~{%}8HO?@RkB+Cbd9`U%< z+3Eo7HGzeXOmK859ns!eUCNgFEdti0-ck{Ql%mrEi~n*t^_+wZd<5BAKXaCV9PDB>#&|Gk?^F%UCg_l=x<{%TCbe z|I!eF<)mn%1QQIcJ3s(81RleWavddqGEeVS*9Z|Zx1fVXF32gv=4jJWdfeX;3=x>> zptlm$YvVxni?UvgGTV@p$$~2`QL0x^fNP$PP^qR$Egit0YUeC!L}#j3s7rtO1v$k= z#it$N^dhId^s~oTA|`s+r+HH2nO?nngvx~UF(!DDxTrp8#iwG@k6V~Q$Hn8_DXzxx ziZf?YB-4!jiak6*DU1nau!iHb)l#T{BKjj$%g)()Ywnah?_wT>T@5IV^v5<+tt zgTK!KOY?y7sFgm?@giLx7MLT8rbo#7e}_ejVUsRyI%yH8(CdAV))9Le3 z7~d&|&`^h=v9QP&Frz8hZxYWHP^H}p)9cFy^Ojk@gXZ`oojC1TM^$YbQBE(fqZ3RE z-f%gQ6j~d($nWfSYbG*L=r5i4xAomv%X3?2bsA{||jo_Pb~ z0iJ{yHxwvhf4WpV-oOJ0y@;4<%=MKIM4Py6v(6{WXoRcn4=m>kuF~S%TZeU&8eCWj z{uv2>)1|5csdK~e?aBT}%3DpX5!%A!Vkw*qKms?Kb=>DUdeQpsD}Qtn$0emoLprWu zM)WgrCI{a;WoP1LNtlfV;bwW`4s5p$hd0fE#`dOV-juHK{R{N16I)j5)5aj*r;2Yf zLwr;k`z0Jup+sG7QRm%S*hNNyejn(ESY7ddj;jWt9CN7%j;Af!?bO`uSpBR};bPYN zD9CpMdIa<}8&!%XhWO=zy-pm88n9Gh3P5yzPZAc@ideG5n*=rAZ~6m96l=b3I|}`Gh0fn zm$OSoQu@9<7%2jiYsz4Ut6$22j=)L%0~vW}arz+84z!YNae zG{M}};Q)f|+?`4IgILh)xV(h$Ie}w7OK;Gea#&G>jwNhu9Sfs(0EJ{~)^4--k|WZC zIpFXBK~ZriEbQUoYhSf}NOkCNpkOS+3mx_~gv6_GFpJB>GqSwbkxl>Yz}wT@Y_^|; zWo8}$-7Vq%ra}pn_mKi1gfb#M0q1nly>bJy7?Nw2zZ#FUZf%o;d5&9nu~rASHkwG< zrpNxcy-vMA`GYWxi!B^;_<+(*pKIVI0CC*dB4XEl9e zQ&$QM`T{~7nS-{PF?Fw0 zJu0RXC&_=Y*H*)~p7BqkIz$q1?#hJ(d9sOBg-;Q=9S6|o(5unZ5eWK zT**r@zp73c!mVsj(RuUIXRUNR#j3(|f-5xejuA2SY)-pToW<=GVBk~HXa$xlxYyG=R)yg$FPjpTH&!Z`>a_aPEoNW(yle< zAPmGvjH4lrM>RoA9iv6b8cKB_Mk+PEjbdtXOY3@eu9$d)h*ruRKo=rBt6ifn>+1iv zHLdulHloeXdGCm3$)Qf-vF%W`SUzdYS7@N=zGa%SH-B!W1y^|`^l}`A|Ut9$ZeEx~a)pt5w}vneYV}KM#xMshL(B?gkK3X2hUs zECWJA()d!}TSb4WR2D9SP*S7#8}7yN6>rN6g}xFk8IR0>BJg~=actulpc!AtOyK)0FnUp{ zyOe!%r&~*ek(u-2I?3G1lHR=Kki5pRW2rx3@Ng{S3b+grIMEkqVB@&8)Ut+q%QdIN z6qS*^P{puOKB7u@mV8zn^6K5t<%IYHRg0IS(=j&4<&ja5L$}#4 znaIn7)#3vG8jiaUT1vF~M*vGew7;;jodFB@Ck9aw`^#}2i?rdbKPy1p8dgo*Ze=qq z|ExM{^&oq?MbpHVbK$YN(e<2e(|4Ei@lvN^oA9jB#ChX-VExa7cL!=m6D*+guvRpn z%HHG3B6yR)2XtIksckPkuE491VyHVdb)it)3~Z@~(0iHY=&e#$c5 z`UW{2y!B0T)2MtY8B8HVzRr`7V(k3cN%<=1% zNpDg$CJ+|}qCO)~skXJbsfcpd8&CL!pV5ikJv5H7O|kq$`qm}h&8m<4s1Riut}*mD z9C8!Z?16@fPIX0=;Eje|y|}av{LX#t<7l&jO*LnGmwg}U&zh8?bAUN7U-#mv0itfAUNiRw5|stGI4-M3Wjp2TmJoV%U9&4sW1~$Mc8Ylj;8d zr{9(tR9^O!;ht0@6=(Wyuy(e$ajhjo@6%lfWp@iAVSa*FtW?#>a(N6A^o#m=UmdMQp04 z%U{jJL&%Y*&Iy9}zz&wkZy7JuFLuJ%2IvH9vt$WNK%gq|13VPQ?b0c+sFf-ERLr%~ zhl&@5y!yYUqem95MIo}58FSb^U--dopWCA}OL}2=bjy)SMLo|r;mYzls}&1~vwbm$ z)^p`i^BAz|L?%8=crvBluu)Ysb~?#o@7a-awFg^>qCCSD-)&u+>p6IpmQRew$!X>J6?%(N5eMDw7!Z&>9^j(!@ zY*N`g`BXoKZcSmD_t0N!drC2$dqcxZA>dW0h>5FBPUSsw`0dvERBlTTwZhm(X6Qro z4Zflz61GVUn_rZNF0JiWyu-zetp!S5drkv1XU;{Dz__~`d(0V>?Hg`0(6U}`>DmpuJ0YlpoiMeV^W=k> zh=8CN3*-)ljb2$Tg+)qui7Uw)gi^{k_o$w})W2Cwv}?v51I{*2a!&Dl`*{+;P7@Lb zqPSf9(=X2%I(2SY7JJX2*c&bC1;B1J+ID**E=@YKq7(lI&5m&Oz`S?fzxW4qal*CC zwar(hswL1FXm#NgbG!djF5Y-Zs?H+;{oMu-x9?p-C0cSI=!GNs4C))N0KhygP$tnEd17 zRpA!7`NbChjfQsoQie0nal3zJlwe#V&0BcTUIuzzQtKvdScNSJ5 z8vBt-{yQ@_H5{k>agtdzXbCh@@B>h?Iu_WN!`x9_nlqaR_QR^QEm6?Z_(Ui2FKen_ z!u*Bp=*EQ$1b@V%C{@X_E_91zbjSJEpoM+JKB)s`>{1j5*h?qVV&2^8d=bh`*cZ_z zw8bRLBf^oV4f84XBPtZ@EtO0IutGp}?F1jLA!mvR`;8i`E?R>@MWMoCsbVY50%SWw zBje#2Qr|64xhz)uAw}X~?+C!k*n5M#kzr~G!$;k2LPZx^(NK(|U{i(SQW@g{kPX$uC zE;n}p+4)aU$V6n7WioXf*qDWAm;*(FDMFTz9Wl@nYPkgqVU$Ei^JvVPyA1G4qDzd->?X|OOHqu&u3=Fd090mArFV<@MW!&n$q2=FE#I- zHB5us)T~YEGl?w_na7Uz0`mMl75pW9hoc$l?4fW1`)2jpZ$VR5x zH*LVcyegy7S7wWq;IplCo3al2lF6x%zE1MCP?-$+iXA}Y0WetNQS;=TUK=gyvxR9n zL2gWJG_(=vg>tHecBPVe=wE}3DxwVEf#?;&gn8vVLwK=gh1#)t5@l{om6occldSvN zLrX{KUJ@ z`iPu|h*ev*mM|({TiVJ~w#>rYvT@^jgk_Izq%<&+mS2{Yn;z?eVi3wiC;c;V)aiII zPHx~hSZc)PGa`|Q{*dz)hnpyuC*Sp(nk9><7UxDC&t2A&nAQ6%xVpQjXzl3=WIu*i z1?0GSWAnY(5V@C{di6m;rUI!i?6S8l;B_5pUh=~`%Y_1ByCBm_Sr(IWoq>$6Du#(f z5|)+^RwPJW*LkAJvIL&TiwaTAB}v35a{(Zpi&bgK=F&vOEr5pgPSlOnK*5putqOlmY}C$*kx=SP+cQ?2IdUtE31CgGfO!8$L=@(}sQc`)TS)oD$u>vp>^2 zee$8J#o`AWXw9q(O$E>v6(8Obz!u%KAiUR{AbH6L5V&QWe^jYg;W5r5RLwazWfol> zOlYi*1&4ZZXUJs6`29xJb>X<|)4NZRp+if#oHT@?>+q5SB#!UFl9+e6gh04JS+;fZ zNuBepY)qB2hq>l_f*?!~)7SQh><=yO;omI>Y(r52vARV36E|t*&eR2pUhA#hY!$39 z2yVbPYrTV4_CI5>}d)!>TdMN)x@BrIW z`vUZ1@kEE7*xm zufjuZmEZKv)2+~omO#>$8WB)Ywysoz z&$T?(veErGe1-zhVWe`7Di%UU)B;FN7ym_#EE(X-U*Tq~NOBW1m-B4n7E0djg*;Fd-#2NsTcU3!>0q1uwB=cey9I*v z9xTq|+7Emaxmf}51ajU;ps(;9bOS~j_fl5If;z^|Osiq^<|c|}`! z7`J*do!N{~9ZIm{&L4F~b6h-8x9F>VA9xy4?mg`HdVV!%Q4%U>u95iYDAFHVDvYN# zSqzM-h>hC0Eze&f6NqL4dOXyi zP^R)4I#w!G^*(kV%@&mkcY*YOYjcMU#(mq}L&M*6qh(&6m`B^6Eu7BIT-yNwRB|CpM;{&vI07Gbp0!q6 zZuAYZykw;rK_o(&L9(BFrGaBAo?iVmv_>cP9}oS$G1B%!Z1-T+iXb=@L#-7l$fmUQ z%oQ`rKWN%zoO|rqCXZf08@5FgOz$*M!hw08FnwXb4qpc7tuA+OvYZ8f27oNJp38wm z=yJz^{vHa0gg=37hb#bj^dc5&(9{NHuCT(W17fR-&zJ5cX4%T=R*SReB6DLS*i|^} zS>Lf4^kFFF;CQen*I8hi;TlC~)1!a+E&h#&eGnq%DhB=FQD{0MUGLY&geyQwQDgWx zqDxcW0Ff=w6np^RoLau%l|?Wwwia$YSZysjpa3h2VZbeC`)73z5&NNCx=nZUFQ}A-69a_BuUa*1>ee(Qo1zu94nZ#{C3U<%#d!53~OTw46<*)G`!X3 z1^5i*N&*m&FuIL=@1w?(y?xr}q~o|tm7JN>j4O0$nkWsFiOF=dZ%7@C5LwQc^m7`#%xPzCAP%0DGyq=}FBYLj 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�bHzE9j%s3Dnko}KPYI<+>6m@cCX8QVV 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�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 G1X)^OqFNaM literal 0 HcmV?d00001 diff --git a/ansible/roles/providers/files/usr/lib/python3/dist-packages/cloudinit/feature_overrides.py b/ansible/roles/providers/files/usr/lib/python3/dist-packages/cloudinit/feature_overrides.py new file mode 100644 index 0000000..fa96e17 --- /dev/null +++ b/ansible/roles/providers/files/usr/lib/python3/dist-packages/cloudinit/feature_overrides.py @@ -0,0 +1 @@ +ERROR_ON_USER_DATA_FAILURE = False diff --git a/ansible/roles/providers/files/usr/libexec/chrony-helper b/ansible/roles/providers/files/usr/libexec/chrony-helper new file mode 100644 index 0000000..bc99c7c --- /dev/null +++ b/ansible/roles/providers/files/usr/libexec/chrony-helper @@ -0,0 +1,251 @@ +#!/bin/bash +# This script configures running chronyd to use NTP servers obtained from +# DHCP and _ntp._udp DNS SRV records. Files with servers from DHCP are managed +# externally (e.g. by a dhclient script). Files with servers from DNS SRV +# records are updated here using the dig utility. The script can also list +# and set static sources in the chronyd configuration file. + +chronyc=/usr/bin/chronyc +chrony_conf=/etc/chrony.conf +chrony_service=chronyd.service +helper_dir=/var/run/chrony-helper +added_servers_file=$helper_dir/added_servers + +network_sysconfig_file=/etc/sysconfig/network +dhclient_servers_files=/var/lib/dhclient/chrony.servers.* +dnssrv_servers_files=$helper_dir/dnssrv@* +dnssrv_timer_prefix=chrony-dnssrv@ + +chrony_command() { + $chronyc -a -n -m "$1" +} + +is_running() { + chrony_command "tracking" &> /dev/null +} + +is_update_needed() { + for file in $dhclient_servers_files $dnssrv_servers_files \ + $added_servers_file; do + [ -e "$file" ] && return 0 + done + return 1 +} + +update_daemon() { + local all_servers_with_args all_servers added_servers + + if ! is_running; then + rm -f $added_servers_file + return 0 + fi + + all_servers_with_args=$( + cat $dhclient_servers_files $dnssrv_servers_files 2> /dev/null) + + all_servers=$( + echo "$all_servers_with_args" | + while read server serverargs; do + echo "$server" + done | sort -u) + added_servers=$( ( + cat $added_servers_file 2> /dev/null + echo "$all_servers_with_args" | + while read server serverargs; do + [ -z "$server" ] && continue + chrony_command "add server $server $serverargs" &> /dev/null && + echo "$server" + done) | sort -u) + + comm -23 <(echo -n "$added_servers") <(echo -n "$all_servers") | + while read server; do + chrony_command "delete $server" &> /dev/null + done + + added_servers=$(comm -12 <(echo -n "$added_servers") <(echo -n "$all_servers")) + + [ -n "$added_servers" ] && echo "$added_servers" > $added_servers_file || + rm -f $added_servers_file +} + +get_dnssrv_servers() { + local name=$1 + + if ! command -v dig &> /dev/null; then + echo "Missing dig (DNS lookup utility)" >&2 + return 1 + fi + + ( + . $network_sysconfig_file &> /dev/null + + output=$(dig "$name" srv +short +ndots=2 +search 2> /dev/null) + [ $? -ne 0 ] && return 0 + + echo "$output" | while read prio weight port target; do + server=${target%.} + [ -z "$server" ] && continue + echo "$server port $port ${NTPSERVERARGS:-iburst}" + done + ) +} + +check_dnssrv_name() { + local name=$1 + + if [ -z "$name" ]; then + echo "No DNS SRV name specified" >&2 + return 1 + fi + + if [ "${name:0:9}" != _ntp._udp ]; then + echo "DNS SRV name $name doesn't start with _ntp._udp" >&2 + return 1 + fi +} + +update_dnssrv_servers() { + local name=$1 + local srv_file=$helper_dir/dnssrv@$name servers + + check_dnssrv_name "$name" || return 1 + + servers=$(get_dnssrv_servers "$name") + [ -n "$servers" ] && echo "$servers" > "$srv_file" || rm -f "$srv_file" +} + +set_dnssrv_timer() { + local state=$1 name=$2 + local srv_file=$helper_dir/dnssrv@$name servers + local timer=$dnssrv_timer_prefix$(systemd-escape "$name").timer + + check_dnssrv_name "$name" || return 1 + + if [ "$state" = enable ]; then + systemctl enable "$timer" + systemctl start "$timer" + elif [ "$state" = disable ]; then + systemctl stop "$timer" + systemctl disable "$timer" + rm -f "$srv_file" + fi +} + +list_dnssrv_timers() { + systemctl --all --full -t timer list-units | grep "^$dnssrv_timer_prefix" | \ + sed "s|^$dnssrv_timer_prefix\(.*\)\.timer.*|\1|" | + while read -r name; do + systemd-escape --unescape "$name" + done +} + +prepare_helper_dir() { + mkdir -p $helper_dir + exec 100> $helper_dir/lock + if ! flock -w 20 100; then + echo "Failed to lock $helper_dir" >&2 + return 1 + fi +} + +is_source_line() { + local pattern="^[ \t]*(server|pool|peer|refclock)[ \t]+[^ \t]+" + [[ "$1" =~ $pattern ]] +} + +list_static_sources() { + while read line; do + is_source_line "$line" && echo "$line" || : + done < $chrony_conf +} + +set_static_sources() { + local new_config tmp_conf + + new_config=$( + sources=$( + while read line; do + is_source_line "$line" && echo "$line" + done) + + while read line; do + if ! is_source_line "$line"; then + echo "$line" + continue + fi + + tmp_sources=$( + local removed=0 + + echo "$sources" | while read line2; do + [ "$removed" -ne 0 -o "$line" != "$line2" ] && \ + echo "$line2" || removed=1 + done) + + [ "$sources" == "$tmp_sources" ] && continue + sources=$tmp_sources + echo "$line" + done < $chrony_conf + + echo "$sources" + ) + + tmp_conf=${chrony_conf}.tmp + + cp -a $chrony_conf $tmp_conf && + echo "$new_config" > $tmp_conf && + mv $tmp_conf $chrony_conf || return 1 + + systemctl try-restart $chrony_service +} + +print_help() { + echo "Usage: $0 COMMAND" + echo + echo "Commands:" + echo " update-daemon" + echo " update-dnssrv-servers NAME" + echo " enable-dnssrv NAME" + echo " disable-dnssrv NAME" + echo " list-dnssrv" + echo " list-static-sources" + echo " set-static-sources < sources.list" + echo " is-running" + echo " command CHRONYC-COMMAND" +} + +case "$1" in + update-daemon|add-dhclient-servers|remove-dhclient-servers) + is_update_needed || exit 0 + prepare_helper_dir && update_daemon + ;; + update-dnssrv-servers) + prepare_helper_dir && update_dnssrv_servers "$2" && update_daemon + ;; + enable-dnssrv) + set_dnssrv_timer enable "$2" + ;; + disable-dnssrv) + set_dnssrv_timer disable "$2" && prepare_helper_dir && update_daemon + ;; + list-dnssrv) + list_dnssrv_timers + ;; + list-static-sources) + list_static_sources + ;; + set-static-sources) + set_static_sources + ;; + is-running) + is_running + ;; + command|forced-command) + chrony_command "$2" + ;; + *) + print_help + exit 2 +esac + +exit $? \ No newline at end of file diff --git a/ansible/roles/providers/files/usr/local/bin/modify-cloud-init-cfg.sh b/ansible/roles/providers/files/usr/local/bin/modify-cloud-init-cfg.sh new file mode 100644 index 0000000..e6c70f4 --- /dev/null +++ b/ansible/roles/providers/files/usr/local/bin/modify-cloud-init-cfg.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +sed -i 's/preserve_hostname'":"' false/preserve_hostname'":"' true/' /etc/cloud/cloud.cfg \ No newline at end of file diff --git a/ansible/roles/providers/tasks/aws.yml b/ansible/roles/providers/tasks/aws.yml new file mode 100644 index 0000000..6078d14 --- /dev/null +++ b/ansible/roles/providers/tasks/aws.yml @@ -0,0 +1,74 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: upgrade pip to latest + pip: + name: pip + executable: pip3 + state: latest + +- name: install aws clients + pip: + name: "{{ packages }}" + executable: pip3 + vars: + packages: + - awscli + when: ansible_distribution != "Amazon" + +# Remove after https://github.com/aws/amazon-ssm-agent/issues/235 is fixed. +- name: install aws agents RPM on Redhat distributions + package: + name: "{{ item }}" + state: present + disable_gpg_check: true + with_items: + - "{{ amazon_ssm_agent_rpm }}" + when: + - ansible_os_family == "RedHat" + - ansible_distribution != "Amazon" + +- name: install aws agents RPM + package: + name: "{{ item }}" + state: present + with_items: + - amazon-ssm-agent + - awscli + when: ansible_distribution == "Amazon" + +- name: Ensure ssm agent is running RPM + service: + name: amazon-ssm-agent + state: started + enabled: yes + when: ansible_os_family == "RedHat" + +- name: install aws agents Ubuntu + shell: snap install amazon-ssm-agent --classic + when: ansible_distribution == "Ubuntu" + +- name: Ensure ssm agent is running Ubuntu + service: + name: snap.amazon-ssm-agent.amazon-ssm-agent.service + state: started + enabled: yes + when: ansible_distribution == "Ubuntu" + +- name: Disable Hyper-V KVP protocol daemon on Ubuntu + systemd: + name: hv-kvp-daemon + state: stopped + enabled: false + when: ansible_os_family == "Debian" diff --git a/ansible/roles/providers/tasks/azure.yml b/ansible/roles/providers/tasks/azure.yml new file mode 100644 index 0000000..b3c84b4 --- /dev/null +++ b/ansible/roles/providers/tasks/azure.yml @@ -0,0 +1,67 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- import_tasks: redhat.yml + when: ansible_os_family == "RedHat" + +- import_tasks: debian.yml + when: ansible_os_family == "Debian" + +- name: Configure PTP + lineinfile: + path: /etc/chrony/chrony.conf + create: yes + line: refclock PHC /dev/ptp0 poll 3 dpoll -2 offset 0 + +- name: Ensure makestep parameter set as per Azure recommendation + lineinfile: + path: /etc/chrony/chrony.conf + regexp: '^makestep' + line: makestep 1.0 -1 + +- name: Install iptables persistence + apt: + name: "{{ packages }}" + state: present + force_apt_get: yes + vars: + packages: + - iptables-persistent + when: ansible_os_family == "Debian" + +- name: Block traffic to 168.63.129.16 port 80 for cve-2021-27075 + copy: + src: files/etc/azure/iptables + dest: /etc/iptables/rules.v4 + owner: root + group: root + mode: 0644 + when: ansible_os_family == "Debian" + +- name: Load iptable rules from file + community.general.iptables_state: + state: restored + path: /etc/iptables/rules.v4 + when: ansible_os_family == "Debian" + +- name: Install netbase and nfs-common + apt: + name: "{{ packages }}" + state: present + force_apt_get: yes + vars: + packages: + - netbase + - nfs-common + when: ansible_os_family == "Debian" diff --git a/ansible/roles/providers/tasks/cloudstack.yml b/ansible/roles/providers/tasks/cloudstack.yml new file mode 100644 index 0000000..e222d10 --- /dev/null +++ b/ansible/roles/providers/tasks/cloudstack.yml @@ -0,0 +1,39 @@ +# Copyright 2022 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Add CloudStack Datasource + copy: + dest: /etc/cloud/cloud.cfg.d/cloudstack.cfg + content: |- + datasource_list: ['CloudStack'] + datasource: + CloudStack: + max_wait: 120 + timeout: 50 + owner: root + group: root + mode: 0644 + +- name: Run dracut cmd to regenerate initramfs with all drivers - needed when converting to different hypervisor templates + shell: dracut --force --no-hostonly + when: ansible_os_family == "RedHat" + +- name: Add draut cmd to regenerate initramfs with only necessary drivers on first boot + lineinfile: + state: present + path: /etc/cloud/cloud.cfg.d/cloudstack.cfg + line: |- + bootcmd: + - dracut --force + when: ansible_os_family == "RedHat" diff --git a/ansible/roles/providers/tasks/debian.yml b/ansible/roles/providers/tasks/debian.yml new file mode 100644 index 0000000..63a0b45 --- /dev/null +++ b/ansible/roles/providers/tasks/debian.yml @@ -0,0 +1,34 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: upgrade pip to latest + pip: + name: pip + executable: pip3 + state: latest + +- name: upgrade pyOpenSSL and cryptography + pip: + name: + - pyOpenSSL==22.0.* + - cryptography==38.0.* + executable: pip3 + +- name: install Azure clients + pip: + executable: pip3 + name: "{{ packages }}" + vars: + packages: + - azure-cli diff --git a/ansible/roles/providers/tasks/googlecompute.yml b/ansible/roles/providers/tasks/googlecompute.yml new file mode 100644 index 0000000..815534e --- /dev/null +++ b/ansible/roles/providers/tasks/googlecompute.yml @@ -0,0 +1,47 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Download gcloud SDK + get_url: + url: https://sdk.cloud.google.com/ + dest: /tmp/install-gcloud.sh + mode: 0700 + +- name: Execute install-gcloud.sh + shell: bash -o errexit -o pipefail /tmp/install-gcloud.sh --disable-prompts --install-dir=/ + +- name: Remove install-gcloud.sh + file: + path: /tmp/install-gcloud.sh + state: absent + +- name: Find all files in /google-cloud-sdk/bin/ + find: + paths: /google-cloud-sdk/bin/ + register: find + +- name: Create symlinks to /bin + become: True + file: + src: "{{ item.path }}" + path: "/bin/{{ item.path | basename }}" + state: link + with_items: "{{ find.files }}" + +- name: Disable Hyper-V KVP protocol daemon on Ubuntu + systemd: + name: hv-kvp-daemon + state: stopped + enabled: false + when: ansible_os_family == "Debian" diff --git a/ansible/roles/providers/tasks/main.yml b/ansible/roles/providers/tasks/main.yml new file mode 100644 index 0000000..17eba0f --- /dev/null +++ b/ansible/roles/providers/tasks/main.yml @@ -0,0 +1,120 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- include_tasks: aws.yml + when: packer_builder_type.startswith('amazon') + +- include_tasks: azure.yml + when: packer_builder_type.startswith('azure') + +- include_tasks: outscale.yml + when: packer_builder_type.startswith('outscale') + +- include_tasks: vmware.yml + when: packer_builder_type is search('vmware') or + packer_builder_type is search('vsphere') + +- include_tasks: googlecompute.yml + when: packer_builder_type.startswith('googlecompute') + +- include_tasks: oci.yml + when: packer_builder_type.startswith('oracle-oci') + +- include_tasks: qemu.yml + when: packer_builder_type is search('qemu') and + build_target is not search('raw') + +- include_tasks: cloudstack.yml + when: packer_builder_type is search('qemu') and + provider is defined and + provider is search('cloudstack') + +- include_tasks: raw.yml + when: packer_builder_type is search('qemu') and + build_target is search('raw') + +- include_tasks: nutanix.yml + when: packer_builder_type is search('nutanix') + +# Create a boot order configuration +# b/w containerd and cloud final, cloud config services + +- name: Creates unit file directory for cloud-final + file: + path: /etc/systemd/system/cloud-final.service.d + state: directory + +- name: Create cloud-final boot order drop in file + copy: + dest: /etc/systemd/system/cloud-final.service.d/boot-order.conf + src: etc/systemd/system/cloud-final.service.d/boot-order.conf + owner: root + group: root + mode: "0755" + +- name: Creates unit file directory for cloud-config + file: + path: /etc/systemd/system/cloud-config.service.d + state: directory + +- name: Create cloud-final boot order drop in file + copy: + dest: /etc/systemd/system/cloud-config.service.d/boot-order.conf + src: etc/systemd/system/cloud-config.service.d/boot-order.conf + owner: root + group: root + mode: "0755" + +# Some OS might disable cloud-final service on boot (rhel 7). +# Enable all cloud-init services on boot. +- name: Make sure all cloud init services are enabled + service: + name: "{{ item }}" + enabled: yes + with_items: + - cloud-final + - cloud-config + - cloud-init + - cloud-init-local + when: ansible_os_family != "Flatcar" + +- name: Create cloud-init config file + copy: + src: files/etc/cloud/cloud.cfg.d/05_logging.cfg + dest: /etc/cloud/cloud.cfg.d/05_logging.cfg + owner: root + group: root + mode: 0644 + when: ansible_os_family != "Flatcar" + +- name: set cloudinit feature flags + copy: + src: usr/lib/python3/dist-packages/cloudinit/feature_overrides.py + dest: /usr/lib/python3/dist-packages/cloudinit/feature_overrides.py + owner: root + group: root + mode: 0644 + when: ansible_os_family == "Debian" + +- name: Ensure chrony is running + systemd: + enabled: yes + state: started + daemon_reload: yes + name: chronyd + when: (packer_builder_type.startswith('amazon') or + packer_builder_type.startswith('azure') or + packer_builder_type is search('vmware') or + packer_builder_type is search('vsphere')) and + ansible_os_family != "Flatcar" diff --git a/ansible/roles/providers/tasks/nutanix.yml b/ansible/roles/providers/tasks/nutanix.yml new file mode 100644 index 0000000..9d3c0bc --- /dev/null +++ b/ansible/roles/providers/tasks/nutanix.yml @@ -0,0 +1,76 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Install cloud-init packages + apt: + name: "{{ packages }}" + state: present + force_apt_get: yes + vars: + packages: + - cloud-init + - cloud-guest-utils + - cloud-initramfs-copymods + - cloud-initramfs-dyn-netconf + when: ansible_os_family == "Debian" + +- name: Install cloud-init packages + yum: + name: "{{ packages }}" + state: present + vars: + packages: + - cloud-init + - cloud-utils-growpart + when: ansible_os_family == "RedHat" + +- name: Install CSI prerequisites on Ubuntu + apt: + name: "{{ packages }}" + state: present + force_apt_get: yes + vars: + packages: + - nfs-common + - open-iscsi + - lvm2 + - xfsprogs + when: ansible_os_family == "Debian" + +- name: Install CSI prerequisites on RedHat + yum: + name: "{{ packages }}" + state: present + vars: + packages: + - iscsi-initiator-utils + - nfs-utils + - lvm2 + - xfsprogs + when: ansible_os_family == "RedHat" + +- name: Enable iSCSI initiator daemon on Ubuntu or RedHat + systemd: + name: iscsid + state: started + enabled: true + when: ansible_os_family == "Debian" or + ansible_os_family == "RedHat" + +- name: Disable Hyper-V KVP protocol daemon on Ubuntu + systemd: + name: hv-kvp-daemon + state: stopped + enabled: false + when: ansible_os_family == "Debian" diff --git a/ansible/roles/providers/tasks/oci.yml b/ansible/roles/providers/tasks/oci.yml new file mode 100644 index 0000000..c6ce64b --- /dev/null +++ b/ansible/roles/providers/tasks/oci.yml @@ -0,0 +1,34 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Remove the default input reject all iptable rule + lineinfile: + path: /etc/iptables/rules.v4 + state: absent + regexp: "-A INPUT -j REJECT --reject-with icmp-host-prohibited" + when: ansible_distribution == "Ubuntu" + +- name: Remove the default input reject all iptable rule + lineinfile: + path: /etc/iptables/rules.v4 + state: absent + regexp: "-A FORWARD -j REJECT --reject-with icmp-host-prohibited" + when: ansible_distribution == "Ubuntu" + +- name: Disable firewalld service + systemd: + name: firewalld + state: stopped + enabled: false + when: ansible_distribution == "OracleLinux" \ No newline at end of file diff --git a/ansible/roles/providers/tasks/outscale.yml b/ansible/roles/providers/tasks/outscale.yml new file mode 100644 index 0000000..985b01c --- /dev/null +++ b/ansible/roles/providers/tasks/outscale.yml @@ -0,0 +1,24 @@ +- name: Download cloud-int outscale package for Ubuntu + copy: + src: files/tmp/cloud-init_22.2-outscale.deb + dest: /tmp/cloud-init_22.2-outscale.deb + owner: root + group: root + mode: 0755 + when: ansible_distribution == "Ubuntu" + +- name: Install cloud-init outscale package + apt: + deb: /tmp/cloud-init_22.2-outscale.deb + force: True + force_apt_get: True + when: ansible_distribution == "Ubuntu" + +- name: Change cloud-init metadata outscale config in Ubuntu + copy: + src: files/etc/cloud/cloud.cfg.d/99_metadata.cfg + dest: /etc/cloud/cloud.cfg.d/99_metadata.cfg + owner: root + group: root + mode: 0644 + when: ansible_distribution == "Ubuntu" diff --git a/ansible/roles/providers/tasks/qemu.yml b/ansible/roles/providers/tasks/qemu.yml new file mode 100644 index 0000000..23575f7 --- /dev/null +++ b/ansible/roles/providers/tasks/qemu.yml @@ -0,0 +1,49 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Install cloud-init packages + apt: + name: "{{ packages }}" + state: present + force_apt_get: yes + vars: + packages: + - cloud-init + - cloud-guest-utils + - cloud-initramfs-copymods + - cloud-initramfs-dyn-netconf + when: ansible_os_family == "Debian" + +- name: Install cloud-init packages + yum: + name: "{{ packages }}" + state: present + vars: + packages: + - cloud-init + - cloud-utils-growpart + when: ansible_os_family == "RedHat" + +#- name: Unlock password +# replace: +# path: /etc/cloud/cloud.cfg +# regexp: '(?i)lock_passwd: True' +# replace: 'lock_passwd: False' + +- name: Disable Hyper-V KVP protocol daemon on Ubuntu + systemd: + name: hv-kvp-daemon + state: stopped + enabled: false + when: ansible_os_family == "Debian" diff --git a/ansible/roles/providers/tasks/raw.yml b/ansible/roles/providers/tasks/raw.yml new file mode 100644 index 0000000..d52a270 --- /dev/null +++ b/ansible/roles/providers/tasks/raw.yml @@ -0,0 +1,57 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Install cloud-init packages + apt: + name: "{{ packages }}" + state: present + force_apt_get: yes + vars: + packages: + - cloud-init + - cloud-guest-utils + - cloud-initramfs-copymods + - cloud-initramfs-dyn-netconf + when: ansible_os_family == "Debian" + +- name: Install cloud-init packages + yum: + name: "{{ packages }}" + state: present + vars: + packages: + - cloud-init + - cloud-utils-growpart + when: ansible_os_family == "RedHat" + +#- name: Unlock password +# replace: +# path: /etc/cloud/cloud.cfg +# regexp: '(?i)lock_passwd: True' +# replace: 'lock_passwd: False' + +- name: Symlink /usr/libexec/cloud-init to /usr/lib/cloud-init + file: + src: /usr/libexec/cloud-init + dest: /usr/lib/cloud-init + mode: 0777 + state: link + when: ansible_os_family == "RedHat" + +- name: Disable Hyper-V KVP protocol daemon on Ubuntu + systemd: + name: hv-kvp-daemon + state: stopped + enabled: false + when: ansible_os_family == "Debian" diff --git a/ansible/roles/providers/tasks/redhat.yml b/ansible/roles/providers/tasks/redhat.yml new file mode 100644 index 0000000..7c3f8f9 --- /dev/null +++ b/ansible/roles/providers/tasks/redhat.yml @@ -0,0 +1,30 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: import the Microsoft repository key + command: rpm --import https://packages.microsoft.com/keys/microsoft.asc + +- name: Add the yum repository for the azure cli + yum_repository: + name: azure_cli + description: Azure CLI + baseurl: https://packages.microsoft.com/yumrepos/azure-cli + gpgkey: https://packages.microsoft.com/keys/microsoft.asc + gpgcheck: yes + enabled: yes + +- name: install Azure CLI + package: + name: azure-cli + state: present diff --git a/ansible/roles/providers/tasks/vmware-photon.yml b/ansible/roles/providers/tasks/vmware-photon.yml new file mode 100644 index 0000000..a609141 --- /dev/null +++ b/ansible/roles/providers/tasks/vmware-photon.yml @@ -0,0 +1,79 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +- name: Install cloud-init and tools for VMware Photon OS + command: tdnf install {{ packages }} -y + vars: + packages: "cloud-init cloud-utils python3-netifaces" + +- name: Remove cloud-init /etc/cloud/cloud.cfg.d/99-disable-networking-config.cfg + file: + path: /etc/cloud/cloud.cfg.d/99-disable-networking-config.cfg + state: absent + +- name: Install networkd-dispatcher service (Download from source) + unarchive: + src: "{{ networkd_dispatcher_download_url }}" + dest: /tmp + remote_src: yes + +- name: Create needed directories + file: + path: "{{ item.dir }}" + state: directory + loop: + - { dir: /etc/conf.d } + - { dir: /etc/networkd-dispatcher/carrier.d } + - { dir: /etc/networkd-dispatcher/configured.d } + - { dir: /etc/networkd-dispatcher/configuring.d } + - { dir: /etc/networkd-dispatcher/degraded.d } + - { dir: /etc/networkd-dispatcher/dormant.d } + - { dir: /etc/networkd-dispatcher/no-carrier.d } + - { dir: /etc/networkd-dispatcher/off.d } + - { dir: /etc/networkd-dispatcher/routable.d } + +- name: Install networkd-dispatcher service (Move files) + command: mv "{{ item.src }}" "{{ item.dest }}" + loop: + - { src: /tmp/networkd-dispatcher-2.1/networkd-dispatcher, dest: /usr/bin } + - { src: /tmp/networkd-dispatcher-2.1/networkd-dispatcher.service, dest: /etc/systemd/system } + - { src: /tmp/networkd-dispatcher-2.1/networkd-dispatcher.conf, dest: /etc/conf.d } + +- name: Install networkd-dispatcher service (Run networkd-dispatcher) + systemd: + name: networkd-dispatcher + state: started + enabled: yes + +- name: Copy networkd-dispatcher scripts to add DHCP provided NTP servers + template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: a+x + vars: + server_dir: "/var/lib/dhclient" + chrony_helper_dir: "/usr/libexec" + loop: + - { src: files/etc/networkd-dispatcher/routable.d/20-chrony.j2, dest: /etc/networkd-dispatcher/routable.d/20-chrony } + - { src: files/etc/networkd-dispatcher/off.d/20-chrony.j2, dest: /etc/networkd-dispatcher/off.d/20-chrony } + - { src: files/etc/networkd-dispatcher/no-carrier.d/20-chrony.j2, dest: /etc/networkd-dispatcher/no-carrier.d/20-chrony } + +- name: Copy chrony-helper script + copy: + src: files/usr/libexec/chrony-helper + dest: /usr/libexec/chrony-helper + owner: root + group: root + mode: a+x diff --git a/ansible/roles/providers/tasks/vmware-redhat.yml b/ansible/roles/providers/tasks/vmware-redhat.yml new file mode 100644 index 0000000..0047e90 --- /dev/null +++ b/ansible/roles/providers/tasks/vmware-redhat.yml @@ -0,0 +1,51 @@ +# Copyright 2022 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +- name: Install cloud-init packages + yum: + name: "{{ packages }}" + state: present + vars: + packages: + - cloud-init + - cloud-utils-growpart + - python2-pip + +# pip on CentOS needs to be upgraded, but since it's still +# Python 2.7, need < 21.0 +- name: Upgrade pip + pip: + name: pip<21.0 + state: forcereinstall + when: ansible_distribution_major_version == '7' + +# Directly installing Guestinfo datasource is needed so long as +# cloud-init is < 21.3 +- name: Download cloud-init datasource for VMware Guestinfo + get_url: + url: '{{ guestinfo_datasource_script }}' + dest: /tmp/cloud-init-vmware.sh + mode: 0700 + +- name: Execute cloud-init-vmware.sh + shell: bash -o errexit -o pipefail /tmp/cloud-init-vmware.sh + environment: + REPO_SLUG: '{{ guestinfo_datasource_slug }}' + GIT_REF: '{{ guestinfo_datasource_ref }}' + +- name: Remove cloud-init-vmware.sh + file: + path: /tmp/cloud-init-vmware.sh + state: absent diff --git a/ansible/roles/providers/tasks/vmware-ubuntu.yml b/ansible/roles/providers/tasks/vmware-ubuntu.yml new file mode 100644 index 0000000..f9aacdb --- /dev/null +++ b/ansible/roles/providers/tasks/vmware-ubuntu.yml @@ -0,0 +1,45 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +- name: Install cloud-init packages + apt: + name: "{{ packages }}" + state: present + force_apt_get: yes + vars: + packages: + - cloud-init + - cloud-guest-utils + - cloud-initramfs-copymods + - cloud-initramfs-dyn-netconf + +- name: Disable Hyper-V KVP protocol daemon on Ubuntu + systemd: + name: hv-kvp-daemon + state: stopped + enabled: false + +- name: Copy networkd-dispatcher scripts to add DHCP provided NTP servers + template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: a+x + vars: + server_dir: "/var/lib/dhcp" + chrony_helper_dir: "/usr/lib/chrony" + loop: + - { src: files/etc/networkd-dispatcher/routable.d/20-chrony.j2, dest: /etc/networkd-dispatcher/routable.d/20-chrony } + - { src: files/etc/networkd-dispatcher/off.d/20-chrony.j2, dest: /etc/networkd-dispatcher/off.d/20-chrony } + - { src: files/etc/networkd-dispatcher/no-carrier.d/20-chrony.j2, dest: /etc/networkd-dispatcher/no-carrier.d/20-chrony } diff --git a/ansible/roles/providers/tasks/vmware.yml b/ansible/roles/providers/tasks/vmware.yml new file mode 100644 index 0000000..00478a9 --- /dev/null +++ b/ansible/roles/providers/tasks/vmware.yml @@ -0,0 +1,57 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- include_tasks: vmware-photon.yml + when: ansible_os_family == "VMware Photon OS" + +- include_tasks: vmware-ubuntu.yml + when: ansible_os_family == "Debian" + +- include_tasks: vmware-redhat.yml + when: ansible_os_family == "RedHat" + +- name: Create provider vmtools config drop-in file + copy: + src: files/etc/vmware-tools/tools.conf + dest: /etc/vmware-tools/tools.conf + owner: root + group: root + mode: 0644 + when: ansible_os_family != "Flatcar" + +- name: Create service to modify cloud-init config + copy: + src: files/etc/systemd/system/modify-cloud-init-cfg.service + dest: /etc/systemd/system/modify-cloud-init-cfg.service + owner: root + group: root + mode: 0644 + when: ansible_os_family != "Flatcar" + +- name: Copy cloud-init modification script + copy: + src: files/usr/local/bin/modify-cloud-init-cfg.sh + dest: /usr/local/bin/modify-cloud-init-cfg.sh + owner: root + group: root + mode: 0755 + when: ansible_os_family != "Flatcar" + +- name: Enable modify-cloud-init-cfg.service + systemd: + name: modify-cloud-init-cfg.service + daemon_reload: yes + enabled: True + state: stopped + when: ansible_os_family != "Flatcar" diff --git a/ansible/roles/python/defaults/main.yml b/ansible/roles/python/defaults/main.yml new file mode 100644 index 0000000..ee1d14b --- /dev/null +++ b/ansible/roles/python/defaults/main.yml @@ -0,0 +1,19 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +pypy_python_version: "3.6" +pypy_version: "7.2.0" +pypy_download_path: "/tmp/pypy.tar.bz2" +pypy_install_path: "/opt" diff --git a/ansible/roles/python/tasks/flatcar.yml b/ansible/roles/python/tasks/flatcar.yml new file mode 100644 index 0000000..54e8bfa --- /dev/null +++ b/ansible/roles/python/tasks/flatcar.yml @@ -0,0 +1,30 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Check if pypy is installed + raw: "[ -f {{ pypy_install_path }}/pypy/bin/pypy ] && echo 'true' || echo 'false'" + register: pypy_installed + +- name: Install pypy + block: + - name: Download pypy archive + raw: "curl https://github.com/squeaky-pl/portable-pypy/releases/download/pypy{{ pypy_python_version }}-{{ pypy_version }}/pypy{{ pypy_python_version }}-{{ pypy_version }}-linux_x86_64-portable.tar.bz2 -L --output {{ pypy_download_path }}" + - name: Extract archive + raw: "tar -xjf {{ pypy_download_path }} -C {{ pypy_install_path }}" + - name: Rename pypy folder + raw: "mv {{ pypy_install_path }}/pypy{{ pypy_python_version }}-{{ pypy_version }}-linux_x86_64-portable/ {{ pypy_install_path }}/pypy" + - name: Delete downloaded archive + raw: "rm -f {{ pypy_download_path }}" + when: + - pypy_installed.stdout_lines[0] == "false" diff --git a/ansible/roles/python/tasks/main.yml b/ansible/roles/python/tasks/main.yml new file mode 100644 index 0000000..b111aba --- /dev/null +++ b/ansible/roles/python/tasks/main.yml @@ -0,0 +1,22 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Get distribution name from lsb-release + raw: "grep DISTRIB_ID /etc/lsb-release || echo '/etc/lsb-release not found'" + register: distrib_id + +- include_tasks: flatcar.yml + # We can't use ansible_os_family fact here for consistency, as facts gathering + # is disabled in the playbook which includes this role. See playbook for more details. + when: distrib_id.stdout_lines[0] is search("Flatcar") diff --git a/ansible/roles/setup/defaults/main.yml b/ansible/roles/setup/defaults/main.yml new file mode 100644 index 0000000..82ba314 --- /dev/null +++ b/ansible/roles/setup/defaults/main.yml @@ -0,0 +1,27 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +debs: "" +extra_debs: "" +pinned_debs: [] + +redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm" +epel_rpm_gpg_key: "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7" +rpms: "" +extra_rpms: "" + +disable_public_repos: false +external_binary_path: "{{ '/opt/bin' if ansible_os_family == 'Flatcar' else '/usr/local/bin' }}" +extra_repos: "" +pip_conf_file: "" diff --git a/ansible/roles/setup/files/etc/systemd/system-environment-generators/10-flatcar-path b/ansible/roles/setup/files/etc/systemd/system-environment-generators/10-flatcar-path new file mode 100755 index 0000000..1b34da6 --- /dev/null +++ b/ansible/roles/setup/files/etc/systemd/system-environment-generators/10-flatcar-path @@ -0,0 +1,3 @@ +#!/bin/bash +. /etc/profile +echo "PATH=$PATH" diff --git a/ansible/roles/setup/files/etc/systemd/system/usr-libexec.mount b/ansible/roles/setup/files/etc/systemd/system/usr-libexec.mount new file mode 100644 index 0000000..cd0dedf --- /dev/null +++ b/ansible/roles/setup/files/etc/systemd/system/usr-libexec.mount @@ -0,0 +1,11 @@ +[Unit] +Description=Kubernetes flex volume plugin directory + +[Mount] +What=overlay +Where=/usr/libexec +Type=overlay +Options=lowerdir=/usr/libexec,workdir=/opt/libexec.work,upperdir=/opt/libexec + +[Install] +WantedBy=multi-user.target diff --git a/ansible/roles/setup/tasks/bootstrap-flatcar.yml b/ansible/roles/setup/tasks/bootstrap-flatcar.yml new file mode 100644 index 0000000..a2468d2 --- /dev/null +++ b/ansible/roles/setup/tasks/bootstrap-flatcar.yml @@ -0,0 +1,28 @@ +--- +# Flatcar ships without Python installed + +- name: Check if bootstrap is needed + raw: stat /opt/bin/.bootstrapped + register: need_bootstrap + environment: {} + failed_when: false + changed_when: false + tags: + - facts + +- name: Set the ansible_python_interpreter fact + set_fact: + ansible_python_interpreter: "{{ external_binary_path }}/python" + tags: + - facts + +# Some tasks are not compatible with Flatcar, so to centralize and deduplicate the logic of checking +# if we run on Flatcar, we define it here. +# +# This is required until https://github.com/ansible/ansible/issues/77537 is fixed and used. +- name: Override Flatcar's OS family + set_fact: + ansible_os_family: Flatcar + when: ansible_os_family == "Flatcar Container Linux by Kinvolk" + tags: + - facts diff --git a/ansible/roles/setup/tasks/debian.yml b/ansible/roles/setup/tasks/debian.yml new file mode 100644 index 0000000..5b306e7 --- /dev/null +++ b/ansible/roles/setup/tasks/debian.yml @@ -0,0 +1,105 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Put templated sources.list in place + template: + src: etc/apt/sources.list.j2 + dest: /etc/apt/sources.list + mode: 0644 + # OCI Base images have the required apt sources list embedded inside the image, adding the sources list + # from this repo leads to build failures(especially in Arm), hence ignoring the step. + when: packer_builder_type != "oracle-oci" + +- name: Put templated apt.conf.d/90proxy in place when defined + template: + src: etc/apt/apt.conf.d/90proxy + dest: /etc/apt/apt.conf.d/90proxy + mode: 0644 + when: http_proxy is defined or https_proxy is defined + +- name: Ensure cloud-final is in a running state + service: + name: cloud-final + state: started + check_mode: yes + register: cloudfinalstatus + until: cloudfinalstatus.status.ActiveState == "active" + retries: 5 + delay: 10 + when: packer_builder_type == "oracle-oci" and extra_repos != "" + +- name: Find existing repo files + find: + depth: 1 + paths: + - /etc/apt + - /etc/apt/sources.list.d + patterns: '*.list' + register: repo_files + when: disable_public_repos|bool + +- name: Disable repos + command: "mv {{ item.path }} {{ item.path }}.disabled" + loop: "{{ repo_files.files }}" + when: disable_public_repos|bool + +- name: Install extra repos + copy: + src: "{{ item }}" + dest: "/etc/apt/sources.list.d/{{ item | basename }}" + mode: 0644 + loop: "{{ extra_repos.split() }}" + when: extra_repos != "" + +- name: perform a dist-upgrade + apt: + force_apt_get: True + update_cache: True + upgrade: dist + register: apt_lock_status + until: apt_lock_status is not failed + retries: 5 + delay: 10 + +- name: install baseline dependencies + apt: + force_apt_get: True + update_cache: True + name: "{{ debs }}" + state: latest + register: apt_lock_status + until: apt_lock_status is not failed + retries: 5 + delay: 10 + +- name: install extra debs + apt: + force_apt_get: True + name: "{{ extra_debs.split() }}" + state: latest + register: apt_lock_status + until: apt_lock_status is not failed + retries: 5 + delay: 10 + +- name: install pinned debs + apt: + force_apt_get: True + name: "{{ pinned_debs }}" + state: present + force: yes + register: apt_lock_status + until: apt_lock_status is not failed + retries: 5 + delay: 10 diff --git a/ansible/roles/setup/tasks/flatcar.yml b/ansible/roles/setup/tasks/flatcar.yml new file mode 100644 index 0000000..ec842d5 --- /dev/null +++ b/ansible/roles/setup/tasks/flatcar.yml @@ -0,0 +1,55 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- include_tasks: bootstrap-flatcar.yml + +- name: Create /opt/libexec overlay directories + file: + path: "{{ item }}" + state: directory + with_items: + - /opt/libexec + - /opt/libexec.work + +- name: Create usr-libexec.mount unit + copy: + src: etc/systemd/system/usr-libexec.mount + dest: /etc/systemd/system/usr-libexec.mount + owner: root + group: root + mode: "0644" + +- name: Enable usr-libexec.mount unit + systemd: + daemon_reload: yes + enabled: yes + name: usr-libexec.mount + +- name: Create system-environment-generators directory + file: + path: /etc/systemd/system-environment-generators + state: directory + +- name: Add env generator that includes system PATH on service path + copy: + src: etc/systemd/system-environment-generators/10-flatcar-path + dest: /etc/systemd/system-environment-generators/10-flatcar-path + owner: root + group: root + mode: "0755" + +- name: Enable systemd-timesyncd unit + systemd: + enabled: yes + name: systemd-timesyncd.service diff --git a/ansible/roles/setup/tasks/main.yml b/ansible/roles/setup/tasks/main.yml new file mode 100644 index 0000000..ca48161 --- /dev/null +++ b/ansible/roles/setup/tasks/main.yml @@ -0,0 +1,36 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- import_tasks: debian.yml + when: ansible_os_family == "Debian" + +- import_tasks: flatcar.yml + # This task overrides ansible_os_family to "Flatcar" as a workaround for + # regression between Flatcar and Ansible, so rest of the code can use just + # "Flatcar" for comparison, which is the correct value. + when: ansible_os_family in ["Flatcar", "Flatcar Container Linux by Kinvolk"] + +- import_tasks: redhat.yml + when: ansible_os_family == "RedHat" + +- import_tasks: photon.yml + when: ansible_os_family == "VMware Photon OS" + +# Copy in pip config file when defined +- name: Install pip config file + copy: + src: "{{ pip_conf_file }}" + dest: /etc/pip.conf + mode: 0644 + when: pip_conf_file != "" diff --git a/ansible/roles/setup/tasks/photon.yml b/ansible/roles/setup/tasks/photon.yml new file mode 100644 index 0000000..239b96e --- /dev/null +++ b/ansible/roles/setup/tasks/photon.yml @@ -0,0 +1,61 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: add bash_profile + template: + dest: /home/builder/.bash_profile + src: photon_bash_profile + mode: 0600 + owner: builder + group: builder + +- import_tasks: rpm_repos.yml + +- name: Perform a tdnf distro-sync + command: tdnf distro-sync -y --refresh + register: distro + changed_when: '"Nothing to do" not in distro.stderr' + +- name: Concatenate the Photon RPMs + set_fact: + photon_rpms: "{{ rpms | join(' ') }}" + +- name: install baseline dependencies + command: tdnf install {{ photon_rpms }} -y + when: photon_rpms != "" + +- name: install extra RPMs + command: tdnf install {{ extra_rpms }} -y + when: extra_rpms != "" + +# Default size of 1G is insufficient when downloading additional components +- name: Increase tmpfs size + mount: + path: /tmp + src: "tmpfs" + fstype: tmpfs + opts: "size=5G" + state: remounted + +- name: reset iptables rules input + replace: + path: /etc/systemd/scripts/ip4save + regexp: 'INPUT DROP' + replace: 'INPUT ACCEPT' + +- name: reset ip6tables rules input + replace: + path: /etc/systemd/scripts/ip6save + regexp: 'INPUT DROP' + replace: 'INPUT ACCEPT' diff --git a/ansible/roles/setup/tasks/redhat.yml b/ansible/roles/setup/tasks/redhat.yml new file mode 100644 index 0000000..2914fa8 --- /dev/null +++ b/ansible/roles/setup/tasks/redhat.yml @@ -0,0 +1,54 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: RHEL subscription + redhat_subscription: + state: present + username: "{{ lookup('env', 'RHSM_USER') }}" + password: "{{ lookup('env', 'RHSM_PASS') }}" + auto_attach: true + when: ansible_distribution == "RedHat" + +- name: import epel gpg key + rpm_key: + state: present + key: "{{ epel_rpm_gpg_key }}" + when: epel_rpm_gpg_key != "" + +- name: add epel repo + yum: + name: "{{ redhat_epel_rpm }}" + state: present + lock_timeout: 60 + when: redhat_epel_rpm != "" + +- import_tasks: rpm_repos.yml + +- name: perform a yum update + yum: + name: '*' + state: latest + lock_timeout: 60 + +- name: install baseline dependencies + yum: + name: "{{ rpms }}" + state: present + lock_timeout: 60 + +- name: install extra rpms + yum: + name: "{{ extra_rpms.split() }}" + state: present + lock_timeout: 60 diff --git a/ansible/roles/setup/tasks/rpm_repos.yml b/ansible/roles/setup/tasks/rpm_repos.yml new file mode 100644 index 0000000..6de5c1a --- /dev/null +++ b/ansible/roles/setup/tasks/rpm_repos.yml @@ -0,0 +1,34 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Find existing repo files + find: + depth: 1 + paths: /etc/yum.repos.d + patterns: '*.repo' + register: repo_files + when: disable_public_repos|bool + +- name: Disable repos + command: "mv {{ item.path }} {{ item.path }}.disabled" + loop: "{{ repo_files.files }}" + when: disable_public_repos|bool + +- name: Install extra repos + copy: + src: "{{ item }}" + dest: "/etc/yum.repos.d/{{ item | basename }}" + mode: 0644 + loop: "{{ extra_repos.split() }}" + when: extra_repos != "" diff --git a/ansible/roles/setup/templates/etc/apt/apt.conf.d/90proxy b/ansible/roles/setup/templates/etc/apt/apt.conf.d/90proxy new file mode 100644 index 0000000..9ce93c3 --- /dev/null +++ b/ansible/roles/setup/templates/etc/apt/apt.conf.d/90proxy @@ -0,0 +1,8 @@ +Acquire { +{% if http_proxy %} + http::Proxy "{{ http_proxy }}"; +{% endif %} +{% if https_proxy %} + https::Proxy "{{ https_proxy }}"; +{% endif %} +} \ No newline at end of file diff --git a/ansible/roles/setup/templates/etc/apt/sources.list.j2 b/ansible/roles/setup/templates/etc/apt/sources.list.j2 new file mode 100644 index 0000000..7df6804 --- /dev/null +++ b/ansible/roles/setup/templates/etc/apt/sources.list.j2 @@ -0,0 +1,4 @@ +deb http://us.archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} main restricted universe +deb http://us.archive.ubuntu.com/ubuntu {{ ansible_distribution_release }}-updates main restricted universe +deb http://us.archive.ubuntu.com/ubuntu {{ ansible_distribution_release }}-backports main restricted universe +deb http://security.ubuntu.com/ubuntu {{ ansible_distribution_release }}-security main restricted universe diff --git a/ansible/roles/setup/templates/photon_bash_profile b/ansible/roles/setup/templates/photon_bash_profile new file mode 100644 index 0000000..5a1a919 --- /dev/null +++ b/ansible/roles/setup/templates/photon_bash_profile @@ -0,0 +1,2 @@ +PATH=$PATH:/usr/sbin:/usr/local/sbin +export PATH diff --git a/ansible/roles/sysprep/defaults/main.yml b/ansible/roles/sysprep/defaults/main.yml new file mode 100644 index 0000000..326dfd5 --- /dev/null +++ b/ansible/roles/sysprep/defaults/main.yml @@ -0,0 +1,17 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +extra_repos: "" +pip_conf_file: "" +remove_extra_repos: false diff --git a/ansible/roles/sysprep/files/etc/hosts b/ansible/roles/sysprep/files/etc/hosts new file mode 100644 index 0000000..593cbd9 --- /dev/null +++ b/ansible/roles/sysprep/files/etc/hosts @@ -0,0 +1,2 @@ +127.0.0.1 localhost localhost.local +::1 localhost ip6-localhost ip6-loopback diff --git a/ansible/roles/sysprep/files/etc/netplan/51-kubevirt-netplan.yaml b/ansible/roles/sysprep/files/etc/netplan/51-kubevirt-netplan.yaml new file mode 100644 index 0000000..c6f9327 --- /dev/null +++ b/ansible/roles/sysprep/files/etc/netplan/51-kubevirt-netplan.yaml @@ -0,0 +1,7 @@ +network: + version: 2 + ethernets: + id0: + match: + name: enp*s* + dhcp4: true diff --git a/ansible/roles/sysprep/tasks/debian.yml b/ansible/roles/sysprep/tasks/debian.yml new file mode 100644 index 0000000..05f3a3c --- /dev/null +++ b/ansible/roles/sysprep/tasks/debian.yml @@ -0,0 +1,95 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Define file modes + set_fact: + last_log_mode: "0664" + machine_id_mode: "0644" + +- name: apt-mark all installed packages + shell: dpkg-query -f '${binary:Package}\n' -W | xargs apt-mark hold + +- name: Remove extra repos + file: + path: "/etc/apt/sources.list.d/{{ item | basename }}" + state: absent + loop: "{{ extra_repos.split() }}" + when: remove_extra_repos and extra_repos != "" + +- name: Find disabled repo files + find: + depth: 1 + paths: + - /etc/apt + - /etc/apt/sources.list.d + patterns: '*.list.disabled' + register: repo_files + when: disable_public_repos|default(false)|bool and reenable_public_repos|default(true)|bool + +- name: Enable repos + command: "mv {{ item.path }} {{ item.path | regex_replace('.disabled') }}" + loop: "{{ repo_files.files }}" + when: disable_public_repos|default(false)|bool and reenable_public_repos|default(true)|bool + +- name: Remove templated apt.conf.d/90proxy used for http(s)_proxy support + file: + path: etc/apt/apt.conf.d/90proxy + state: absent + when: http_proxy is defined or https_proxy is defined + +- name: Stop auditing + service: + name: rsyslog + state: stopped + +- name: Remove apt package caches + apt: + autoclean: yes + autoremove: yes + force_apt_get: yes + +- name: Remove apt package lists + file: + state: "{{ item.state }}" + path: "{{ item.path }}" + owner: root + group: root + mode: "{{ item.mode }}" + loop: + - { path: /var/lib/apt/lists, state: absent, mode: "0755" } + - { path: /var/lib/apt/lists, state: directory, mode: "0755" } + +- name: Disable apt-daily services + systemd: + name: "{{ item }}" + state: stopped + enabled: false + loop: + - apt-daily.timer + - apt-daily-upgrade.timer + +- name: Get installed packages + package_facts: + +- name: Disable unattended upgrades if installed + systemd: + name: unattended-upgrades + state: stopped + enabled: false + when: "'unattended-upgrades' in ansible_facts.packages" + +- name: Reset network interface IDs + file: + state: absent + path: /etc/udev/rules.d/70-persistent-net.rules diff --git a/ansible/roles/sysprep/tasks/flatcar.yml b/ansible/roles/sysprep/tasks/flatcar.yml new file mode 100644 index 0000000..8854d29 --- /dev/null +++ b/ansible/roles/sysprep/tasks/flatcar.yml @@ -0,0 +1,57 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Define file modes + set_fact: + last_log_mode: "0644" + machine_id_mode: "0444" + +- name: Invalidate the machine-id file for systemd to reevaluate presets + file: + path: /etc/machine-id + state: absent + +- name: Stop and mask update-engine to freeze the image version + systemd: + name: update-engine + state: stopped + enabled: no + masked: yes + +- name: Stop and mask the locksmith reboot manager since it depends on update-engine + systemd: + name: locksmithd + state: stopped + enabled: no + masked: yes + +- name: Mask docker + systemd: + name: "{{ item }}" + state: stopped + enabled: no + masked: yes + with_items: + - docker.socket + - docker.service + +- name: Set cgroup v1 to match the cgroupfs driver in containerd and kubelet + shell: | + echo 'set linux_append="$linux_append systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"' >> /usr/share/oem/grub.cfg + when: kubernetes_semver is version('v1.21.0', '<') + +- name: Set oem_id in grub + shell: | + echo 'set oem_id="{{oem_id}}"' >> /usr/share/oem/grub.cfg + when: (oem_id is defined) and (oem_id != "") diff --git a/ansible/roles/sysprep/tasks/main.yml b/ansible/roles/sysprep/tasks/main.yml new file mode 100644 index 0000000..04a07ad --- /dev/null +++ b/ansible/roles/sysprep/tasks/main.yml @@ -0,0 +1,234 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- import_tasks: debian.yml + when: ansible_os_family == "Debian" + +- import_tasks: flatcar.yml + when: ansible_os_family == "Flatcar" + +- import_tasks: redhat.yml + when: ansible_os_family == "RedHat" + +- import_tasks: photon.yml + when: ansible_os_family == "VMware Photon OS" + +- name: Remove containerd http proxy conf file if needed + file: + path: /etc/systemd/system/containerd.service.d/http-proxy.conf + state: absent + when: http_proxy is defined or https_proxy is defined + +- name: Remove pip conf file if needed + file: + path: /etc/pip.conf + state: absent + when: remove_extra_repos and pip_conf_file != "" + +- name: Truncate machine id + file: + state: "{{ item.state }}" + path: "{{ item.path }}" + owner: root + group: root + mode: "{{ item.mode }}" + loop: + - { path: /etc/machine-id, state: absent, mode: "{{ machine_id_mode }}" } + - { path: /etc/machine-id, state: touch, mode: "{{ machine_id_mode }}" } + when: ansible_os_family != "Flatcar" + +- name: Truncate hostname file + file: + state: "{{ item.state }}" + path: "{{ item.path }}" + owner: root + group: root + mode: "{{ item.mode }}" + loop: + - { path: /etc/hostname, state: absent, mode: "0644" } + - { path: /etc/hostname, state: touch, mode: "0644" } + +- name: Set hostname + hostname: + name: localhost.local + when: ansible_os_family != "VMware Photon OS" and ansible_os_family != "Flatcar" and packer_build_name != "nutanix" + +- name: Reset hosts file + copy: + src: files/etc/hosts + dest: /etc/hosts + owner: root + group: root + mode: "0644" + +- name: Truncate audit logs + file: + state: "{{ item.state }}" + path: "{{ item.path }}" + owner: root + group: utmp + mode: "{{ item.mode }}" + loop: + - { path: /var/log/wtmp, state: absent, mode: "0664" } + - { path: /var/log/lastlog, state: absent, mode: "{{ last_log_mode }}" } + - { path: /var/log/wtmp, state: touch, mode: "0664" } + - { path: /var/log/lastlog, state: touch, mode: "{{ last_log_mode }}" } + +- name: Remove cloud-init lib dir and logs + file: + state: absent + path: "{{ item }}" + loop: + - /var/lib/cloud + - /var/log/cloud-init.log + - /var/log/cloud-init-output.log + - /var/run/cloud-init + +# A shallow search in /tmp and /var/tmp is used to declare which files or +# directories will be removed as part of resetting temp space. The reason +# a state absent->directory task isn't used is because Ansible's own data +# directory on the remote host(s) is /tmp/.ansible. Thus, by removing /tmp, +# Ansible can no longer access the remote host. +- name: Find temp files + find: + depth: 1 + file_type: any + paths: + - /tmp + - /var/tmp + pattern: '*' + register: temp_files + +- name: Reset temp space + file: + state: absent + path: "{{ item.path }}" + loop: "{{ temp_files.files }}" + +- name: Find netplan files + find: + depth: 1 + file_type: any + paths: + - /lib/netplan + - /etc/netplan + - /run/netplan + pattern: '*.yaml' + register: netplan_files + +- name: Delete netplan files + file: + state: absent + path: "{{ item.path }}" + loop: "{{ netplan_files.files }}" + when: netplan_files.files is defined and (netplan_files.files|length>0) + +- name: Create netplan for KubeVirt + vars: + kubevirt: "{{ lookup('env', 'KUBEVIRT') }}" + copy: + src: files/etc/netplan/51-kubevirt-netplan.yaml + dest: /etc/netplan/51-kubevirt-netplan.yaml + mode: "0644" + when: ansible_os_family == "Debian" and kubevirt == "true" + +- name: Find SSH host keys + find: + path: /etc/ssh + pattern: 'ssh_host_*' + register: ssh_host_keys + +- name: Remove SSH host keys + file: + state: absent + path: "{{ item.path }}" + loop: "{{ ssh_host_keys.files }}" + +- name: Remove SSH authorized users + file: + state: absent + path: "{{ item.path }}" + loop: + - { path: /root/.ssh/authorized_keys } + - { path: "/home/{{ ansible_env.SUDO_USER | default(ansible_user_id) }}/.ssh/authorized_keys" } + when: ansible_os_family != "Flatcar" + +- name: Remove SSH authorized users for Flatcar + file: + state: absent + path: "{{ item.path }}" + loop: + - { path: /root/.ssh/authorized_keys } + when: ansible_os_family == "Flatcar" + + +- name: Truncate all remaining log files in /var/log + shell: + cmd: | + find /var/log -type f -iname '*.log' | xargs truncate -s 0 + when: ansible_os_family != "Flatcar" + +- name: Delete all logrotated log zips + shell: + cmd: | + find /var/log -type f -name '*.gz' -exec rm {} + + when: ansible_os_family != "Flatcar" + +- name: Remove swapfile + file: + state: "{{ item.state }}" + path: "{{ item.path }}" + loop: + - { path: /swapfile, state: absent } + - { path: /mnt/resource/swapfile, state: absent } + when: ansible_memory_mb.swap.total != 0 + +- name: Truncate shell history + file: + state: absent + path: "{{ item.path }}" + loop: + - { path: /root/.bash_history } + - { path: "/home/{{ ansible_env.SUDO_USER | default(ansible_user_id) }}/.bash_history" } + +- name: Rotate journalctl to archive logs + shell: + cmd: | + journalctl --rotate + when: not ( ansible_os_family == "RedHat" and ansible_distribution_major_version|int <= 7 ) + +- name: Remove archived journalctl logs + shell: + cmd: | + journalctl -m --vacuum-time=1s + +- name: Ensure ignition runs on next boot + file: + state: touch + path: /boot/flatcar/first_boot + owner: root + group: root + when: ansible_os_family == "Flatcar" + +- name: Remove any default Ignition files used by Packer + file: + state: absent + path: /usr/share/oem/config.ign + when: ansible_os_family == "Flatcar" + +- name: start ssh + systemd: + name: ssh + enabled: yes + when: ansible_os_family == "Debian" diff --git a/ansible/roles/sysprep/tasks/photon.yml b/ansible/roles/sysprep/tasks/photon.yml new file mode 100644 index 0000000..4a626bc --- /dev/null +++ b/ansible/roles/sysprep/tasks/photon.yml @@ -0,0 +1,50 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Define file modes + set_fact: + last_log_mode: "0644" + machine_id_mode: "0444" + +- name: set hostname + command: hostnamectl set-hostname localhost.local + +- name: Remove the kickstart log + file: + state: absent + path: /root/anaconda-ks.cfg + +- name: Get installed packages + shell: tdnf list installed | cut -d ' ' -f 1 + register: packages + +- name: create a package list + set_fact: + package_list: "{{ packages.stdout_lines | join(' ') }}" + +- name: exclude packages from upgrade + lineinfile: + path: /etc/tdnf/tdnf.conf + regexp: '^excludepkgs=' + line: excludepkgs={{ package_list }} + +- import_tasks: rpm_repos.yml + +- name: Remove tdnf package caches + command: /usr/bin/tdnf -y clean all + +- name: Lock root account + user: + name: root + password_lock: yes diff --git a/ansible/roles/sysprep/tasks/redhat.yml b/ansible/roles/sysprep/tasks/redhat.yml new file mode 100644 index 0000000..5886192 --- /dev/null +++ b/ansible/roles/sysprep/tasks/redhat.yml @@ -0,0 +1,79 @@ +# Copyright 2019 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Define file modes + set_fact: + last_log_mode: "0644" + machine_id_mode: "0444" + +- name: Get installed packages + package_facts: + +- name: create the package list + set_fact: + package_list: "{{ ansible_facts.packages.keys() | join(' ') }}" + +- name: exclude the packages from upgrades + lineinfile: + path: /etc/yum.conf + regexp: '^exclude=' + line: exclude={{ package_list }} + +- import_tasks: rpm_repos.yml + +# Oracle Linux does not have temp-disk-swapfile service +- name: Disable swap service and ensure it is masked + systemd: + name: temp-disk-swapfile + enabled: no + masked: yes + when: ansible_memory_mb.swap.total != 0 and ansible_distribution_major_version|int <= 7 + +- name: Disable swap service and ensure it is masked on RHEL 8 + systemd: + name: swap.target + enabled: no + masked: yes + when: ansible_memory_mb.swap.total != 0 and ansible_distribution_major_version|int == 8 + +- name: Remove RHEL subscription + block: + - name: enable repo mgmt with subscription-manager + command: subscription-manager config --rhsm.manage_repos=1 + - name: Remove subscriptions + rhsm_repository: + name: '*' + state: absent + - name: Unregister system + redhat_subscription: + state: absent + - name: clean local subscription data + command: subscription-manager clean + when: ansible_distribution == "RedHat" + +- name: Remove yum package caches + yum: + autoremove: yes + lock_timeout: 60 + +- name: Remove yum package lists + command: /usr/bin/yum -y clean all + +- name: Reset network interface IDs + shell: sed -i '/^\(HWADDR\|UUID\)=/d' /etc/sysconfig/network-scripts/ifcfg-* + +- name: Remove the kickstart log + file: + state: absent + path: /root/anaconda-ks.cfg diff --git a/ansible/roles/sysprep/tasks/rpm_repos.yml b/ansible/roles/sysprep/tasks/rpm_repos.yml new file mode 100644 index 0000000..edbac04 --- /dev/null +++ b/ansible/roles/sysprep/tasks/rpm_repos.yml @@ -0,0 +1,33 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Remove extra repos + file: + path: "/etc/yum.repos.d/{{ item | basename }}" + state: absent + loop: "{{ extra_repos.split() }}" + when: remove_extra_repos and extra_repos != "" + +- name: Find disabled repo files + find: + depth: 1 + paths: /etc/yum.repos.d + patterns: '*.repo.disabled' + register: repo_files + when: disable_public_repos|default(false)|bool and reenable_public_repos|default(true)|bool + +- name: Enable repos + command: "mv {{ item.path }} {{ item.path | regex_replace('.disabled') }}" + loop: "{{ repo_files.files }}" + when: disable_public_repos|default(false)|bool and reenable_public_repos|default(true)|bool diff --git a/ansible/windows/OWNERS b/ansible/windows/OWNERS new file mode 100644 index 0000000..d047699 --- /dev/null +++ b/ansible/windows/OWNERS @@ -0,0 +1,4 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - image-builder-windows-maintainers diff --git a/ansible/windows/ansible_winrm.ps1 b/ansible/windows/ansible_winrm.ps1 new file mode 100644 index 0000000..04ba099 --- /dev/null +++ b/ansible/windows/ansible_winrm.ps1 @@ -0,0 +1,48 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file is from packer documentation: +# https://www.packer.io/docs/provisioners/ansible.html#winrm-communicator +# https://www.packer.io/docs/builders/amazon/ebs#connecting-to-windows-instances-using-winrm + +Set-ExecutionPolicy Unrestricted -Scope LocalMachine -Force -ErrorAction Ignore + +# Don't set this before Set-ExecutionPolicy as it throws an error +$ErrorActionPreference = "stop" + +# Remove HTTP listener +Remove-Item -Path WSMan:\Localhost\listener\listener* -Recurse + +# Create a self-signed certificate to let ssl work +$Cert = New-SelfSignedCertificate -CertstoreLocation Cert:\LocalMachine\My -DnsName "packer" +New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $Cert.Thumbprint -Force + +# WinRM +write-output "Setting up WinRM" +write-host "(host) setting up WinRM" + +cmd.exe /c winrm quickconfig -q +cmd.exe /c winrm set "winrm/config" '@{MaxTimeoutms="1800000"}' +cmd.exe /c winrm set "winrm/config/winrs" '@{MaxMemoryPerShellMB="1024"}' +cmd.exe /c winrm set "winrm/config/service" '@{AllowUnencrypted="true"}' +cmd.exe /c winrm set "winrm/config/client" '@{AllowUnencrypted="true"}' +cmd.exe /c winrm set "winrm/config/service/auth" '@{Basic="true"}' +cmd.exe /c winrm set "winrm/config/client/auth" '@{Basic="true"}' +cmd.exe /c winrm set "winrm/config/service/auth" '@{CredSSP="true"}' +cmd.exe /c winrm set "winrm/config/listener?Address=*+Transport=HTTPS" "@{Port=`"5986`";Hostname=`"packer`";CertificateThumbprint=`"$($Cert.Thumbprint)`"}" +cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes +cmd.exe /c netsh firewall add portopening TCP 5986 "Port 5986" +cmd.exe /c net stop winrm +cmd.exe /c sc config winrm start= auto +cmd.exe /c net start winrm diff --git a/ansible/windows/example.vars.yml b/ansible/windows/example.vars.yml new file mode 100644 index 0000000..2edbe5d --- /dev/null +++ b/ansible/windows/example.vars.yml @@ -0,0 +1,38 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +kubernetes_base_url: https://dl.k8s.io/release/v1.19.2/bin/windows/amd64 +cloudbase_init_url: https://github.com/cloudbase/cloudbase-init/releases/download/1.1.2/CloudbaseInitSetup_1_1_2_x64.msi +wins_url: https://github.com/rancher/wins/releases/download/v0.0.4/wins.exe +nssm_url: https://azurek8scishared.blob.core.windows.net/nssm/nssm.exe +gmsa_keyvault_url: https://kubernetesartifacts.azureedge.net/ccgakvplugin/v1.1.4/binaries/windows-gmsa-ccgakvplugin-v1.1.4.zip + +runtime: docker-ee +docker_ee_version: "19.03.12" +kubernetes_install_path: 'c:\k' +windows_service_manager: 'nssm' +pause_image: "registry.k8s.io/pause:3.9" +load_additional_components: true +additional_registry_images: true +additional_registry_images_list: "docker.io/sigwindowstools/flannel:0.12.0, docker.io/sigwindowstools/kube-proxy:v1.19.2" +prepull: false +distribution_version: 2019 + +cloudbase_metadata_services: "cloudbaseinit.metadata.services.azureservice.AzureService, cloudbaseinit.metadata.services.ovfservice.OvfService" +cloudbase_plugins: "cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin, cloudbaseinit.plugins.common.userdata.UserDataPlugin, cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin" +cloudbase_metadata_services_unattend: "cloudbaseinit.metadata.services.azureservice.AzureService, cloudbaseinit.metadata.services.ovfservice.OvfService" +cloudbase_plugins_unattend: "cloudbaseinit.plugins.common.mtu.MTUPlugin" + +debug_tools: true +additional_debug_files: "https://raw.githubusercontent.com/kubernetes-sigs/sig-windows-tools/master/hack/DebugWindowsNode.ps1" diff --git a/ansible/windows/node_windows.yml b/ansible/windows/node_windows.yml new file mode 100644 index 0000000..4d57969 --- /dev/null +++ b/ansible/windows/node_windows.yml @@ -0,0 +1,105 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +- hosts: all + vars: + node_custom_roles_pre: "" + node_custom_roles_post: "" + custom_role_names: "" + + tasks: + - name: Check if cloudbase-init url is set + set_fact: + install_cloudbase_init: '{{ true if (cloudbase_init_url is defined) and (cloudbase_init_url|length > 0) else false }}' + - name: Check if wins url is set + set_fact: + use_wins: '{{ true if (wins_url is defined) and (wins_url|length > 0) else false }}' + + # https://docs.ansible.com/ansible/latest/user_guide/windows_performance.html + - name: Optimise powershell + win_shell: | + function Optimize-PowershellAssemblies { + # NGEN powershell assembly, improves startup time of powershell by 10x + $old_path = $env:path + try { + $env:path = [Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory() + [AppDomain]::CurrentDomain.GetAssemblies() | % { + if (! $_.location) {continue} + $Name = Split-Path $_.location -leaf + if ($Name.startswith("Microsoft.PowerShell.")) { + Write-Progress -Activity "Native Image Installation" -Status "$name" + ngen install $_.location | % {"`t$_"} + } + } + } finally { + $env:path = $old_path + } + } + Optimize-PowershellAssemblies + become: yes + become_method: runas + become_user: SYSTEM + + - name: Get Install Drive + win_shell: $env:SYSTEMDRIVE + register: systemdrive + + - name: Get Program Files Directory + win_shell: $env:ProgramFiles + register: programfiles + + - name: Get All Users profile path + win_shell: $env:ALLUSERSPROFILE.Replace("\", "\\") + register: alluserprofile + + - name: Get TEMP Directory + win_shell: $env:TEMP + register: tempdir + + - include_role: + name: "{{ role }}" + loop: "{{ node_custom_roles_pre.split() }}" + loop_control: + loop_var: role + when: node_custom_roles_pre != "" + - include_role: + name: systemprep + - include_role: + name: cloudbase-init + when: install_cloudbase_init + - include_role: + name: providers + - include_role: + name: runtimes + - include_role: + name: kubernetes + - include_role: + name: gmsa + - include_role: + name: load_additional_components + when: load_additional_components | bool + - include_role: + name: debug + - include_role: + name: "{{ role }}" + loop: "{{ custom_role_names.split() + node_custom_roles_post.split() }}" + loop_control: + loop_var: role + when: custom_role_names != "" or node_custom_roles_post != "" + + environment: + HTTP_PROXY: "{{ http_proxy | default('') }}" + HTTPS_PROXY: "{{ https_proxy | default('') }}" + NO_PROXY: "{{ no_proxy | default('') }}" diff --git a/ansible/windows/roles/cloudbase-init/tasks/main.yml b/ansible/windows/roles/cloudbase-init/tasks/main.yml new file mode 100644 index 0000000..0224756 --- /dev/null +++ b/ansible/windows/roles/cloudbase-init/tasks/main.yml @@ -0,0 +1,53 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Download Cloudbase-init + win_get_url: + url: "{{ cloudbase_init_url }}" + dest: '{{ tempdir.stdout | trim }}\CloudbaseInitSetup.msi' + register: installer + retries: 5 + delay: 3 + until: installer is not failed + +- name: Ensure log directory + win_file: + path: '{{ systemdrive.stdout | trim }}\logs' + state: directory + +- name: Install Cloudbase-init + win_package: + path: '{{ installer.dest }}' + log_path: '{{ systemdrive.stdout | trim }}\logs\cloudbase-install-log-{{lookup("pipe", "date +%Y%m%dT%H%M%S")}}.log' + +# configuration modified from https://github.com/cloudbase/windows-openstack-imaging-tools/tree/master/Examples/config/azure +- name: Set up cloudbase-init unattend configuration + win_template: + src: templates/cloudbase-init-unattend.conf + dest: '{{ programfiles.stdout | trim }}\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init-unattend.conf' + +# configuration modified from https://github.com/cloudbase/windows-openstack-imaging-tools/tree/master/Examples/config/azure +- name: Set up cloudbase-init configuration + win_template: + src: templates/cloudbase-init.conf + dest: '{{ programfiles.stdout | trim }}\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf' + +- name: Configure set up complete + win_shell: | + # If this file already exists then the following command fails + Remove-Item -Force {{ systemdrive.stdout | trim }}\Windows\Setup\Scripts\SetupComplete.cmd + & "{{ programfiles.stdout | trim }}\Cloudbase Solutions\Cloudbase-Init\bin\SetSetupComplete.cmd" + become: yes + become_method: runas + become_user: System diff --git a/ansible/windows/roles/cloudbase-init/templates/cloudbase-init-unattend.conf b/ansible/windows/roles/cloudbase-init/templates/cloudbase-init-unattend.conf new file mode 100644 index 0000000..4301dd6 --- /dev/null +++ b/ansible/windows/roles/cloudbase-init/templates/cloudbase-init-unattend.conf @@ -0,0 +1,28 @@ +[DEFAULT] +# This configuration with SetUserPasswordPlugin and CreateUserPlugin will create a user capi +# and generate a 123 charater random password. SSH can be configred on the machine to enable access. +username=capi +groups=Administrators +inject_user_password=false +user_password_length=123 +first_logon_behaviour=no + +config_drive_raw_hhd=true +config_drive_cdrom=true +config_drive_vfat=true +bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe +mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\ +verbose=true +debug=true +logdir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\ +logfile=cloudbase-init-unattend.log +default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN +logging_serial_port_settings={{ cloudbase_logging_serial_port }} +mtu_use_dhcp_config=true +ntp_use_dhcp_config=true +local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\ +metadata_services={{ cloudbase_metadata_services_unattend }} +plugins={{ cloudbase_plugins_unattend }} +allow_reboot=false +stop_service_on_exit=false +check_latest_version=false diff --git a/ansible/windows/roles/cloudbase-init/templates/cloudbase-init.conf b/ansible/windows/roles/cloudbase-init/templates/cloudbase-init.conf new file mode 100644 index 0000000..6732db7 --- /dev/null +++ b/ansible/windows/roles/cloudbase-init/templates/cloudbase-init.conf @@ -0,0 +1,36 @@ +[DEFAULT] +# This configuration with SetUserPasswordPlugin and CreateUserPlugin will create a user capi +# and generate a 123 charater random password. SSH can be configred on the machine to enable access. +username=capi +groups=Administrators +inject_user_password=false +user_password_length=123 +first_logon_behaviour=no +rename_admin_user=true + +config_drive_raw_hhd=true +config_drive_cdrom=true +config_drive_vfat=true +bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe +mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\ +verbose=true +debug=true +logdir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\ +logfile=cloudbase-init.log +default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN +logging_serial_port_settings={{ cloudbase_logging_serial_port }} +mtu_use_dhcp_config=true +ntp_use_dhcp_config=true +local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\ + +san_policy=OnlineAll +trim_enabled=True + +metadata_report_provisioning_started=True +metadata_report_provisioning_completed=True +ephemeral_disk_volume_label="Temporary Storage" +netbios_host_name_compatibility={{ netbios_host_name_compatibility }} + +metadata_services={{ cloudbase_metadata_services }} +plugins=cloudbaseinit.plugins.common.userdata.UserDataPlugin, + {{ cloudbase_plugins }} diff --git a/ansible/windows/roles/debug/defaults/main.yml b/ansible/windows/roles/debug/defaults/main.yml new file mode 100644 index 0000000..cdda26d --- /dev/null +++ b/ansible/windows/roles/debug/defaults/main.yml @@ -0,0 +1,27 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +debug_files: +- "https://github.com/microsoft/SDN/raw/master/Kubernetes/windows/debug/collectlogs.ps1" +- "https://github.com/microsoft/SDN/raw/master/Kubernetes/windows/debug/dumpVfpPolicies.ps1" +- "https://github.com/microsoft/SDN/raw/master/Kubernetes/windows/debug/portReservationTest.ps1" +- "https://github.com/microsoft/SDN/raw/master/Kubernetes/windows/debug/starthnstrace.cmd" +- "https://github.com/microsoft/SDN/raw/master/Kubernetes/windows/debug/startpacketcapture.cmd" +- "https://github.com/microsoft/SDN/raw/master/Kubernetes/windows/debug/stoppacketcapture.cmd" +- "https://github.com/Microsoft/SDN/raw/master/Kubernetes/windows/debug/VFP.psm1" +- "https://github.com/microsoft/SDN/raw/master/Kubernetes/windows/helper.psm1" +- "https://github.com/Microsoft/SDN/raw/master/Kubernetes/windows/hns.psm1" +- "https://raw.githubusercontent.com/kubernetes-sigs/sig-windows-tools/master/hack/DebugWindowsNode.ps1" + +additional_debug_files_list: "{{ additional_debug_files.split(',') if (additional_debug_files is defined) and (additional_debug_files|length > 0) else [] }}" diff --git a/ansible/windows/roles/debug/tasks/main.yml b/ansible/windows/roles/debug/tasks/main.yml new file mode 100644 index 0000000..134c265 --- /dev/null +++ b/ansible/windows/roles/debug/tasks/main.yml @@ -0,0 +1,33 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Add debug tools directory + win_file: + path: '{{ kubernetes_install_path }}\debug' + state: directory + when: (debug_tools | default(true) | bool) or (additional_debug_files_list|length > 0) + +- name: Get debug files + win_get_url: + url: '{{ item }}' + dest: '{{ kubernetes_install_path }}\debug\' + loop: '{{ debug_files }}' + when: debug_tools | default(true)|bool + +- name: Get additional debug files + win_get_url: + url: '{{ item }}' + dest: '{{ kubernetes_install_path }}\debug\' + loop: "{{ additional_debug_files_list }}" + when: additional_debug_files_list|length > 0 \ No newline at end of file diff --git a/ansible/windows/roles/gmsa/defaults/main.yml b/ansible/windows/roles/gmsa/defaults/main.yml new file mode 100644 index 0000000..8e2393d --- /dev/null +++ b/ansible/windows/roles/gmsa/defaults/main.yml @@ -0,0 +1,16 @@ +# Copyright 2022 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- + +gmsa_keyvault: '{{ true if (gmsa_keyvault_url is defined) and (gmsa_keyvault_url | length > 0) else false }}' diff --git a/ansible/windows/roles/gmsa/files/install-gmsa-keyvault-plugin.ps1 b/ansible/windows/roles/gmsa/files/install-gmsa-keyvault-plugin.ps1 new file mode 100644 index 0000000..a670b07 --- /dev/null +++ b/ansible/windows/roles/gmsa/files/install-gmsa-keyvault-plugin.ps1 @@ -0,0 +1,134 @@ +# Copyright 2022 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# script modified from https://github.com/Azure/AgentBaker/blob/8d5323f3b1a622d558e624e5a6b0963229f80b2a/staging/cse/windows/configfunc.ps1 under MIT +$ErrorActionPreference = 'Stop' + +function Enable-Privilege { + param($Privilege) + $Definition = @' + using System; + using System.Runtime.InteropServices; + public class AdjPriv { + [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] + internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, + ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr rele); + [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] + internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok); + [DllImport("advapi32.dll", SetLastError = true)] + internal static extern bool LookupPrivilegeValue(string host, string name, + ref long pluid); + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct TokPriv1Luid { + public int Count; + public long Luid; + public int Attr; + } + internal const int SE_PRIVILEGE_ENABLED = 0x00000002; + internal const int TOKEN_QUERY = 0x00000008; + internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; + public static bool EnablePrivilege(long processHandle, string privilege) { + bool retVal; + TokPriv1Luid tp; + IntPtr hproc = new IntPtr(processHandle); + IntPtr htok = IntPtr.Zero; + retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, + ref htok); + tp.Count = 1; + tp.Luid = 0; + tp.Attr = SE_PRIVILEGE_ENABLED; + retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid); + retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, + IntPtr.Zero); + return retVal; + } + } +'@ + $ProcessHandle = (Get-Process -id $pid).Handle + $type = Add-Type $definition -PassThru + $type[0]::EnablePrivilege($processHandle, $Privilege) +} + +function Aquire-Privilege { + param($Privilege) + + write-output "Acquiring the $Privilege privilege" + $enablePrivilegeResponse=$false + for($i = 0; $i -lt 10; $i++) { + write-output "Retry $i : Trying to enable the $Privilege privilege" + $enablePrivilegeResponse = Enable-Privilege -Privilege "$Privilege" -ErrorAction 'Continue' + if ($enablePrivilegeResponse) { + break + } + Start-Sleep 1 + } + if(!$enablePrivilegeResponse) { + write-output "Failed to enable the $Privilege privilege." + exit 1 + } +} + +# Enable the PowerShell privilege to set the registry permissions. +Aquire-Privilege -Privilege "SeTakeOwnershipPrivilege" + +# Set the registry permissions. +write-output "Setting GMSA plugin registry permissions" +try { + $ccgKeyPath = "System\CurrentControlSet\Control\CCG\COMClasses" + $owner = [System.Security.Principal.NTAccount]"BUILTIN\Administrators" + + $key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey( + $ccgKeyPath, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, + [System.Security.AccessControl.RegistryRights]::TakeOwnership) + $acl = $key.GetAccessControl() + $originalOwner = $acl.owner + $acl.SetOwner($owner) + $key.SetAccessControl($acl) + + $key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey( + $ccgKeyPath, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, + [System.Security.AccessControl.RegistryRights]::ChangePermissions) + $acl = $key.GetAccessControl() + $rule = New-Object System.Security.AccessControl.RegistryAccessRule( + $owner, + [System.Security.AccessControl.RegistryRights]::FullControl, + [System.Security.AccessControl.AccessControlType]::Allow) + $acl.SetAccessRule($rule) + $key.SetAccessControl($acl) +} catch { + write-output "Failed to set GMSA plugin registry permissions. $_" + exit 1 +} + +# Set the appropriate registry values. +try { + write-output "Setting the appropriate GMSA plugin registry values" + reg.exe import "registerplugin.reg" +} catch { + write-output "Failed to set GMSA plugin registry values. $_" + exit 1 +} + +write-output "Restore original access to registry key" +$acl = $key.GetAccessControl() +$acl.RemoveAccessRule($rule) +$acl.SetOwner([System.Security.Principal.NTAccount]$originalowner) +Aquire-Privilege -Privilege "SeRestorePrivilege" +$key.SetAccessControl($acl) +$key.close() + + +write-output "Successfully installed the GMSA plugin" diff --git a/ansible/windows/roles/gmsa/tasks/gmsa_keyvault.yml b/ansible/windows/roles/gmsa/tasks/gmsa_keyvault.yml new file mode 100644 index 0000000..cc12d9a --- /dev/null +++ b/ansible/windows/roles/gmsa/tasks/gmsa_keyvault.yml @@ -0,0 +1,64 @@ +# Copyright 2022 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Download gMSA Key Vault plugin + win_get_url: + url: '{{ gmsa_keyvault_url }}' + dest: '{{ tempdir.stdout | trim }}\windows-gmsa-ccgakvplugin.zip' + async: 1800 + poll: 60 + retries: 5 + delay: 3 + register: gmsadownload + until: gmsadownload is not failed + +- name: Unzip gMSA Key Vault Archive + win_unzip: + src: '{{ gmsadownload.dest }}' + dest: '{{ kubernetes_install_path }}' + recurse: no + delete_archive: yes + +- name: Copy gMSA Key Vault plugin to System32 + win_shell: | + Move-Item -Force -Path {{ kubernetes_install_path }}\CCGAKVPlugin.dll -Destination {{ systemdrive.stdout | trim }}\Windows\System32\ + +# This is done via a script because Ansible doesn't have the ability to take ownership of registry keys +# The script enables the privilege for the process running and modifies the reg keys. Once process exits it no longer has privileges +# See https://groups.google.com/g/ansible-project/c/5Bt7jgq6ZFA/m/_XJtVzmhBwAJ +- name: Copy gMSA Key Vault installer file + win_copy: + src: install-gmsa-keyvault-plugin.ps1 + dest: '{{ kubernetes_install_path }}' + +- name: Register gMSA Key Vault plugin + win_shell: | + {{ kubernetes_install_path }}\install-gmsa-keyvault-plugin.ps1 + +- name: Install registry CCG logging manifest + win_shell: | + wevtutil.exe um {{ kubernetes_install_path }}\CCGEvents.man + wevtutil.exe im {{ kubernetes_install_path }}\CCGEvents.man + +- name: Install registry Key Vault plugin logging manifest + win_shell: | + wevtutil.exe um {{ kubernetes_install_path }}\CCGAKVPluginEvents.man + wevtutil.exe im {{ kubernetes_install_path }}\CCGAKVPluginEvents.man + +- name: Clean up gMSA install files + win_shell: | + Remove-Item {{ kubernetes_install_path }}\CCGEvents.man + Remove-Item {{ kubernetes_install_path }}\CCGAKVPluginEvents.man + Remove-Item {{ kubernetes_install_path }}\registerplugin.reg + Remove-Item {{ kubernetes_install_path }}\install-gmsa-keyvault-plugin.ps1 diff --git a/ansible/windows/roles/gmsa/tasks/main.yml b/ansible/windows/roles/gmsa/tasks/main.yml new file mode 100644 index 0000000..2cfd9f5 --- /dev/null +++ b/ansible/windows/roles/gmsa/tasks/main.yml @@ -0,0 +1,17 @@ +# Copyright 2022 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- + +- import_tasks: gmsa_keyvault.yml + when: gmsa_keyvault | bool diff --git a/ansible/windows/roles/kubernetes/defaults/main.yml b/ansible/windows/roles/kubernetes/defaults/main.yml new file mode 100644 index 0000000..949647b --- /dev/null +++ b/ansible/windows/roles/kubernetes/defaults/main.yml @@ -0,0 +1,18 @@ +# Copyright 2018 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +kubernetes_bins: +- kubeadm +- kubectl +- kubelet diff --git a/ansible/windows/roles/kubernetes/tasks/kubelet.yml b/ansible/windows/roles/kubernetes/tasks/kubelet.yml new file mode 100644 index 0000000..531463e --- /dev/null +++ b/ansible/windows/roles/kubernetes/tasks/kubelet.yml @@ -0,0 +1,58 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Create kubelet directory structure + win_file: + path: "{{ item }}" + state: directory + loop: + - '{{ systemdrive.stdout | trim }}\var\log\kubelet' + - '{{ systemdrive.stdout | trim }}\etc\kubernetes' + - '{{ systemdrive.stdout | trim }}\etc\kubernetes\manifests' + - '{{ systemdrive.stdout | trim }}\etc\kubernetes\pki' + +# this is required in 1.22 and below due to invalid absolute path handling +# https://github.com/kubernetes-sigs/image-builder/issues/853 +- name: Symlink kubelet pki folder + win_shell: New-Item -path $env:SystemDrive\var\lib\kubelet\etc\kubernetes\pki -type SymbolicLink -value $env:SystemDrive\etc\kubernetes\pki\ -Force + when: kubernetes_semver is version('v1.23.0', '<') + +- import_tasks: nssm.yml + when: windows_service_manager == "nssm" + +- import_tasks: sc.yml + when: windows_service_manager == "windows_service" + +# Dependency selection: https://www.reddit.com/r/ansible/comments/imfdgn/setting_a_variable_conditionally/g41anaf/?utm_source=reddit&utm_medium=web2x&context=3 +- name: Ensure kubelet is installed + win_service: + name: kubelet + dependencies: [ "{{ runtime_service }}" ] + start_mode: auto + vars: + dependencies: + containerd: containerd + docker-ee: docker + default: docker + runtime_service: "{{ dependencies[runtime] | default(dependencies['docker']) }}" + +- name: Add firewall rule for kubelet + win_firewall_rule: + name: kubelet + localport: 10250 + action: allow + direction: in + protocol: tcp + state: present + enabled: yes diff --git a/ansible/windows/roles/kubernetes/tasks/main.yml b/ansible/windows/roles/kubernetes/tasks/main.yml new file mode 100644 index 0000000..d64f117 --- /dev/null +++ b/ansible/windows/roles/kubernetes/tasks/main.yml @@ -0,0 +1,31 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Create kubernetes directory structure + win_file: + path: '{{ kubernetes_install_path }}' + state: directory + +- import_tasks: url.yml + +- name: Add kubernetes folder to path + win_path: + elements: + - '{{ kubernetes_install_path }}' + scope: Machine + +- import_tasks: kubelet.yml + +- import_tasks: wins.yml + when: use_wins diff --git a/ansible/windows/roles/kubernetes/tasks/nssm.yml b/ansible/windows/roles/kubernetes/tasks/nssm.yml new file mode 100644 index 0000000..3947b79 --- /dev/null +++ b/ansible/windows/roles/kubernetes/tasks/nssm.yml @@ -0,0 +1,39 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Download nssm + win_get_url: + url: '{{ nssm_url }}' + dest: '{{ kubernetes_install_path }}\' + retries: 5 + delay: 3 + register: nssm_download + until: nssm_download is not failed + +- name: Create kubelet start file for nssm + win_template: + src: templates/StartKubelet.ps1 + dest: '{{ kubernetes_install_path }}\StartKubelet.ps1' + +- name: Install kubelet via nssm + win_nssm: + name: kubelet + start_mode: auto + state: present + application: '%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe' + arguments: '-ExecutionPolicy Bypass -NoProfile {{ kubernetes_install_path }}\StartKubelet.ps1' + app_rotate_bytes: 10485760 + stderr_file: '{{ systemdrive.stdout | trim }}\var\log\kubelet\kubelet.err.log' + stdout_file: '{{ systemdrive.stdout | trim }}\var\log\kubelet\kubelet.log' + app_rotate_online: 1 diff --git a/ansible/windows/roles/kubernetes/tasks/sc.yml b/ansible/windows/roles/kubernetes/tasks/sc.yml new file mode 100644 index 0000000..0e6cf51 --- /dev/null +++ b/ansible/windows/roles/kubernetes/tasks/sc.yml @@ -0,0 +1,27 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +# Install kubelet as a windows service +# Requires --windows-service flag: https://github.com/kubernetes/kubernetes/blob/7f23a743e8c23ac6489340bbb34fa6f1d392db9d/cmd/kubelet/app/options/osflags_windows.go#L26 +# Does not support kubeadm KUBELET_KUBEADM_ARGS which is used by Cluster API to pass extra user args +- name: Set dockershim args + set_fact: + additional_kubelet_args: "--image-pull-progress-deadline=20m --network-plugin=cni" + when: runtime == "docker-ee" and kubernetes_semver is version('v1.24.0', '<') + +- name: Install kubelet as service + win_service: + name: kubelet + start_mode: manual + path: '{{ kubernetes_install_path }}\kubelet.exe --windows-service --cert-dir={{ systemdrive.stdout | trim }}/var/lib/kubelet/pki --config={{ systemdrive.stdout | trim }}/var/lib/kubelet/config.yaml --bootstrap-kubeconfig={{ systemdrive.stdout | trim }}/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig={{ systemdrive.stdout | trim }}/etc/kubernetes/kubelet.conf --hostname-override=$(hostname) --pod-infra-container-image=`"{{ pause_image }}`" --enable-debugging-handlers --cgroups-per-qos=false --enforce-node-allocatable=`"`" --resolv-conf=`"`" --log-dir={{ systemdrive.stdout | trim }}/var/log/kubelet --logtostderr=false {{ additional_kubelet_args if additional_kubelet_args is defined }}' diff --git a/ansible/windows/roles/kubernetes/tasks/url.yml b/ansible/windows/roles/kubernetes/tasks/url.yml new file mode 100644 index 0000000..7a90966 --- /dev/null +++ b/ansible/windows/roles/kubernetes/tasks/url.yml @@ -0,0 +1,24 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Download kubernetes binaries + win_get_url: + url: "{{ kubernetes_base_url }}/{{ item }}.exe" + dest: '{{ kubernetes_install_path }}\' + loop: "{{ kubernetes_bins }}" + retries: 5 + delay: 3 + register: kubernetes_download + until: kubernetes_download is not failed + \ No newline at end of file diff --git a/ansible/windows/roles/kubernetes/tasks/wins.yml b/ansible/windows/roles/kubernetes/tasks/wins.yml new file mode 100644 index 0000000..e3f4164 --- /dev/null +++ b/ansible/windows/roles/kubernetes/tasks/wins.yml @@ -0,0 +1,29 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Get wins + win_get_url: + url: '{{ wins_url }}' + dest: '{{ kubernetes_install_path }}' + retries: 5 + delay: 3 + register: wins_download + until: wins_download is not failed + +- name: Register wins.exe + win_command: wins.exe srv app run --register + +- name: Ensure that wins service is running + win_service: + name: rancher-wins diff --git a/ansible/windows/roles/kubernetes/templates/StartKubelet.ps1 b/ansible/windows/roles/kubernetes/templates/StartKubelet.ps1 new file mode 100644 index 0000000..a84bdf2 --- /dev/null +++ b/ansible/windows/roles/kubernetes/templates/StartKubelet.ps1 @@ -0,0 +1,43 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# From https://github.com/kubernetes-sigs/sig-windows-tools/blob/master/kubeadm/scripts/PrepareNode.ps1 +$FileContent = Get-Content -Path "/var/lib/kubelet/kubeadm-flags.env" +$kubeAdmArgs = $FileContent.TrimStart('KUBELET_KUBEADM_ARGS=').Trim('"') + +$args = "--cert-dir=$env:SYSTEMDRIVE/var/lib/kubelet/pki", + "--config=$env:SYSTEMDRIVE/var/lib/kubelet/config.yaml", + "--bootstrap-kubeconfig=$env:SYSTEMDRIVE/etc/kubernetes/bootstrap-kubelet.conf", + "--kubeconfig=$env:SYSTEMDRIVE/etc/kubernetes/kubelet.conf", + "--hostname-override=$(hostname)", + "--pod-infra-container-image=`"{{ pause_image }}`"", + "--enable-debugging-handlers", + "--cgroups-per-qos=false", + "--enforce-node-allocatable=`"`"", + "--resolv-conf=`"`"" + +{% if runtime == "docker-ee" and kubernetes_semver is version('v1.24.0', '<') %} +{% raw %} +$netId = docker network ls -f name=host --format "{{ .ID }}" +{% endraw %} +if ($netId.Length -lt 1) { + docker network create -d nat host +} + +$args += "--image-pull-progress-deadline=20m", + "--network-plugin=cni" +{% endif %} + +$kubeletCommandLine = "{{ kubernetes_install_path }}\kubelet.exe " + ($args -join " ") + " $kubeAdmArgs" +Invoke-Expression $kubeletCommandLine diff --git a/ansible/windows/roles/load_additional_components/defaults/main.yml b/ansible/windows/roles/load_additional_components/defaults/main.yml new file mode 100644 index 0000000..44aa745 --- /dev/null +++ b/ansible/windows/roles/load_additional_components/defaults/main.yml @@ -0,0 +1,22 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- + +additional_registry_images: "" +additional_registry_images_list: "" +additional_url_images: "" +additional_url_images_list: "" +additional_executables: "" +additional_executables_list: "" +additional_executables_destination_path: "" \ No newline at end of file diff --git a/ansible/windows/roles/load_additional_components/tasks/executables.yml b/ansible/windows/roles/load_additional_components/tasks/executables.yml new file mode 100644 index 0000000..54be847 --- /dev/null +++ b/ansible/windows/roles/load_additional_components/tasks/executables.yml @@ -0,0 +1,31 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Create temporary download dir + win_file: + path: "{{ additional_executables_destination_path }}/" + state: directory + +- name: Download additional executables + win_get_url: + url: "{{ item }}" + dest: "{{ additional_executables_destination_path }}/" + loop: "{{ additional_executables_list.split(',') }}" + async: 1800 + poll: 60 + retries: 5 + delay: 3 + register: download + until: download is not failed + \ No newline at end of file diff --git a/ansible/windows/roles/load_additional_components/tasks/main.yml b/ansible/windows/roles/load_additional_components/tasks/main.yml new file mode 100644 index 0000000..8b3921e --- /dev/null +++ b/ansible/windows/roles/load_additional_components/tasks/main.yml @@ -0,0 +1,22 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- import_tasks: executables.yml + when: additional_executables | bool + +- import_tasks: registry.yml + when: additional_registry_images | bool + +- import_tasks: url.yml + when: additional_url_images | bool diff --git a/ansible/windows/roles/load_additional_components/tasks/registry.yml b/ansible/windows/roles/load_additional_components/tasks/registry.yml new file mode 100644 index 0000000..381d551 --- /dev/null +++ b/ansible/windows/roles/load_additional_components/tasks/registry.yml @@ -0,0 +1,39 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Pull containerd additional images + win_shell: | + #refresh the path to ensure ansible sees update + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ctr.exe -n k8s.io images pull {{ item }} + loop: "{{ additional_registry_images_list.split(',') }}" + async: 1800 + poll: 60 + retries: 5 + delay: 3 + register: pull + until: pull is not failed + when: runtime == "containerd" + +- name: Pre-pull docker additional images + win_shell: docker pull {{ item }} + loop: "{{ additional_registry_images_list.split(',') }}" + async: 1800 + poll: 60 + retries: 5 + delay: 3 + register: pull + until: pull is not failed + when: runtime == "docker-ee" + \ No newline at end of file diff --git a/ansible/windows/roles/load_additional_components/tasks/url.yml b/ansible/windows/roles/load_additional_components/tasks/url.yml new file mode 100644 index 0000000..a1d241c --- /dev/null +++ b/ansible/windows/roles/load_additional_components/tasks/url.yml @@ -0,0 +1,83 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Create temporary download dir + win_file: + path: '{{ tempdir.stdout | trim }}\images' + state: directory + when: runtime == "containerd" + +- name: Download additional images from url + win_get_url: + url: "{{ item }}" + dest: '{{ tempdir.stdout | trim }}\images\' + register: images + loop: "{{ additional_url_images_list.split(',') }}" + async: 1800 + poll: 60 + retries: 5 + delay: 3 + when: runtime == "containerd" + +- name: Load additional images to containerd + win_shell: | + Function DeGZip-File{ + Param( + $infile, + $outfile = ($infile -replace '\.gz$','') + ) + $input = New-Object System.IO.FileStream $inFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) + $output = New-Object System.IO.FileStream $outFile, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None) + $gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress) + $buffer = New-Object byte[](1024) + while($true){ + $read = $gzipstream.Read($buffer, 0, 1024) + if ($read -le 0){break} + $output.Write($buffer, 0, $read) + } + $gzipStream.Close() + $output.Close() + $input.Close() + } + + $file = "{{ item.dest }}" + $ext = $file.substring($file.length - 3, 3) + + if ($ext -eq ".gz") { + DeGZip-File $file + $file = ($file -replace '\.gz$','') + } + #refresh the path to ensure ansible sees update + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ctr.exe -n k8s.io images import --no-unpack $file + loop: "{{ images.results }}" + when: runtime == "containerd" + +- name: Load additional docker images + win_shell: | + docker import {{ item }} + loop: "{{ additional_url_images_list.split(',') }}" + async: 1800 + poll: 60 + retries: 5 + delay: 3 + register: import + until: import is not failed + when: runtime == "docker" + +- name: Remove downloaded files + win_file: + state: absent + path: '{{ tempdir.stdout | trim }}\images' + when: runtime == "containerd" \ No newline at end of file diff --git a/ansible/windows/roles/providers/defaults/main.yml b/ansible/windows/roles/providers/defaults/main.yml new file mode 100644 index 0000000..20548e6 --- /dev/null +++ b/ansible/windows/roles/providers/defaults/main.yml @@ -0,0 +1,15 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +packer_builder_type: "" diff --git a/ansible/windows/roles/providers/tasks/azure.yml b/ansible/windows/roles/providers/tasks/azure.yml new file mode 100644 index 0000000..1197a5c --- /dev/null +++ b/ansible/windows/roles/providers/tasks/azure.yml @@ -0,0 +1,48 @@ +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- + +- name: Create Azure wireserver access group + ansible.windows.win_group: + name: WireServerAccess + description: Controls access to the Azure WireServer + +# AzureGuestAgent and Cloudbase-init need access to wireserver otherwise VM doesn't boot +# So we give the users access via the firewall security filters +# https://docs.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallsecurityfilter +# +# Permissions set on the Firewall rule: +# S-1-1-0 is Everyone. We mark this as Allow (A) to ensure the Block is enforced for all users other than on the exception list. +# S-1-5-18 is LocalSystem used by AzureGuestAgent. We mark this as Deny (D) to add to Block exception list. +# We also add the newly created group WireServerAccess to the block exception list and add Cloudbase-init user later. +# +# View the details of the SDDL string used with ConvertFrom-SddlString and see well known sids: https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids +- name: Block traffic to 168.63.129.16 port 80 for cve-2021-27075 + win_shell: | + $wsg = Get-LocalGroup -n "WireServerAccess" + $r = New-NetFirewallRule -DisplayName 'Block-Outbound-168.63.129.16-port-80-for-cve-2021-27075' -Direction Outbound -RemoteAddress '168.63.129.16' -RemotePort '80' -Protocol TCP -Action Block + $r | Get-NetFirewallSecurityFilter | Set-NetFirewallSecurityFilter -LocalUser "O:LSD:(D;;CC;;;S-1-5-18)(D;;CC;;;$($wsg.SID.Value))(A;;CC;;;S-1-1-0)" + become: yes + become_method: runas + become_user: SYSTEM + +- name: Add users to WireServerAccessGroup + ansible.windows.win_group_membership: + name: WireServerAccess + members: + - cloudbase-init + +- name: Add additional users + ansible.windows.win_group_membership: + name: WireServerAccess + members: "{{ users }}" + vars: + users: "{{ wire_server_users.split(',') if (wire_server_users is defined) and (wire_server_users|length > 0) else [] }}" diff --git a/ansible/windows/roles/providers/tasks/main.yml b/ansible/windows/roles/providers/tasks/main.yml new file mode 100644 index 0000000..f9a7a61 --- /dev/null +++ b/ansible/windows/roles/providers/tasks/main.yml @@ -0,0 +1,14 @@ +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- + +- include_tasks: azure.yml + when: packer_builder_type.startswith('azure') diff --git a/ansible/windows/roles/runtimes/defaults/main.yml b/ansible/windows/roles/runtimes/defaults/main.yml new file mode 100644 index 0000000..d218ed3 --- /dev/null +++ b/ansible/windows/roles/runtimes/defaults/main.yml @@ -0,0 +1,28 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +pause_image: mcr.microsoft.com/oss/kubernetes/pause:3.9 +containerd_additional_settings: "" +containerd_config_file: "config.toml" + +prepull: false +prepull_images: + 2019: + - mcr.microsoft.com/windows/servercore:ltsc2019 + - mcr.microsoft.com/windows/nanoserver:1809 + - "{{ pause_image }}" + 2004: + - mcr.microsoft.com/windows/servercore:2004 + - mcr.microsoft.com/windows/nanoserver:2004 + - "{{ pause_image }}" \ No newline at end of file diff --git a/ansible/windows/roles/runtimes/tasks/containerd.yml b/ansible/windows/roles/runtimes/tasks/containerd.yml new file mode 100644 index 0000000..7485d3f --- /dev/null +++ b/ansible/windows/roles/runtimes/tasks/containerd.yml @@ -0,0 +1,111 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Download containerd + win_get_url: + url: '{{ containerd_url }}' + dest: '{{ tempdir.stdout | trim }}\containerd.tar.gz' + checksum: '{{ containerd_sha256 }}' + checksum_algorithm: "sha256" + url_timeout: 300 + register: containerd + retries: 5 + delay: 3 + until: containerd is not failed + +- name: Create containerd directory structure + win_file: + path: "{{ item }}" + state: directory + loop: + - '{{ programfiles.stdout | trim }}\containerd' + - '{{ alluserprofile.stdout | trim }}\containerd\state' + - '{{ alluserprofile.stdout | trim }}\containerd\root' + - '{{ systemdrive.stdout | trim }}/opt/cni/bin' + - '{{ systemdrive.stdout | trim }}/etc/cni/net.d' + +- name: Check if containerd exists + win_stat: + path: '{{ programfiles.stdout | trim }}\containerd\containerd.exe' + register: containerd_file + +- name: Unpack containerd binaries + win_command: cmd /c tar -zxvf {{ containerd.dest }} -C "{{ programfiles.stdout | trim }}\containerd" --strip-components 1 + when: not containerd_file.stat.exists + +- name: Add containerd to path + win_path: + elements: + - '{{ programfiles.stdout | trim }}\containerd' + scope: machine + +- name: Copy containerd config file {{ containerd_config_file }} + win_template: + dest: '{{ programfiles.stdout | trim }}\containerd\config.toml' + src: "{{ containerd_config_file }}" + vars: + allusersprofile: "{{ alluserprofile.stdout | trim }}" + plugin_bin_dir: "{{ systemdrive.stdout | trim }}/opt/cni/bin" + plugin_conf_dir: "{{ systemdrive.stdout | trim }}/etc/cni/net.d" + # programfiles is C:\Program Files, but should be C:\\Program Files + # otherwise task Register Containerd fails with "invalid escape sequence: \P" + containerd_conf_dir: '{{ programfiles.stdout | trim | regex_replace("\\", "\\\\") }}\\\\containerd' + +- name: Check if a Containerd service is installed + win_service: + name: containerd + register: containerd_service + +- name: Register Containerd + win_shell: | + #refresh the path to ensure ansible sees update + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + containerd.exe --register-service + when: containerd_service.exists == false + +# Enables DNS resolution of SMB shares +# https://github.com/kubernetes-sigs/windows-gmsa/issues/30#issuecomment-802240945 +- name: Apply SMB Resolution Fix for containerd + win_regedit: + path: HKLM:\SYSTEM\CurrentControlSet\Services\hns\State + state: present + name: EnableCompartmentNamespace + data: 1 + type: dword + +- name: Create Windows Defender Exclusions + win_shell: | + Add-MpPreference -ExclusionProcess "{{ programfiles.stdout | trim }}\containerd\containerd.exe" + Add-MpPreference -ExclusionProcess "{{ programfiles.stdout | trim }}\containerd\ctr.exe" + +- name: Ensure Containerd Service is running + win_service: + name: containerd + start_mode: auto + state: started + +- name: Pre-pull containerd images + win_shell: | + #refresh the path to ensure ansible sees update + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ctr.exe -n k8s.io images pull {{ item }} + loop: "{{ images }}" + async: 1800 + poll: 60 + retries: 5 + register: pull + until: pull is not failed + when: (prepull | bool) + vars: + images: "{{ prepull_images[distribution_version] | default([]) }}" diff --git a/ansible/windows/roles/runtimes/tasks/docker_ee.yml b/ansible/windows/roles/runtimes/tasks/docker_ee.yml new file mode 100644 index 0000000..3392237 --- /dev/null +++ b/ansible/windows/roles/runtimes/tasks/docker_ee.yml @@ -0,0 +1,45 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- + +# Requires license. If you running on Azure License is currently provided with Windows Server images. +- name: Install docker via OneGet + win_shell: | + Install-PackageProvider -Name DockerMsftProvider -Force -ForceBootstrap | Out-Null + $package = Find-Package -Name Docker -ProviderName DockerMsftProvider -RequiredVersion {{ docker_ee_version }} + $package | Install-Package -Force | Out-Null + +- name: Start Docker Service + win_service: + name: docker + start_mode: auto + state: started + +- name: Set up Docker Network + win_shell: | + $exists=docker network ls -f name=host -q + if (-not $exists) { docker network create -d nat host } + +- name: Pre-pull docker images + win_command: docker pull {{ item }} + loop: "{{ images }}" + async: 1800 + poll: 60 + retries: 5 + register: pull + until: pull is not failed + when: (prepull | bool) + vars: + images: "{{ prepull_images[distribution_version] | default([]) }}" + \ No newline at end of file diff --git a/ansible/windows/roles/runtimes/tasks/main.yml b/ansible/windows/roles/runtimes/tasks/main.yml new file mode 100644 index 0000000..71da864 --- /dev/null +++ b/ansible/windows/roles/runtimes/tasks/main.yml @@ -0,0 +1,19 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- import_tasks: containerd.yml + when: runtime == "containerd" + +- import_tasks: docker_ee.yml + when: runtime == "docker-ee" diff --git a/ansible/windows/roles/runtimes/templates/config.toml b/ansible/windows/roles/runtimes/templates/config.toml new file mode 100644 index 0000000..8bf07df --- /dev/null +++ b/ansible/windows/roles/runtimes/templates/config.toml @@ -0,0 +1,37 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +root = "{{ allusersprofile }}\\containerd\\root" +state = "{{ allusersprofile }}\\containerd\\state" +version = 2 + +{% if 'imports' not in containerd_additional_settings | b64decode %} +imports = ["{{ containerd_conf_dir }}\\conf.d\\*.toml"] +{% endif %} + +[grpc] + address = "\\\\.\\pipe\\containerd-containerd" + +[plugins] + [plugins."io.containerd.grpc.v1.cri"] + sandbox_image = "{{ pause_image }}" + [plugins."io.containerd.grpc.v1.cri".cni] + bin_dir = "{{ plugin_bin_dir }}" + conf_dir = "{{ plugin_conf_dir }}" +{% if packer_builder_type.startswith('azure') %} + [plugins."io.containerd.grpc.v1.cri".registry.headers] + X-Meta-Source-Client = ["azure/capz"] +{% endif %} + +{{containerd_additional_settings | b64decode}} diff --git a/ansible/windows/roles/systemprep/defaults/main.yml b/ansible/windows/roles/systemprep/defaults/main.yml new file mode 100644 index 0000000..d77195f --- /dev/null +++ b/ansible/windows/roles/systemprep/defaults/main.yml @@ -0,0 +1,17 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +windows_updates_kbs_numbers: "{{ windows_updates_kbs.split() if (windows_updates_kbs is defined) and (windows_updates_kbs|length > 0) else [] }}" +windows_updates_category_names: "{{ windows_updates_categories.split() if (windows_updates_categories is defined) and (windows_updates_categories|length > 0) else [] }}" +ssh_source_url: "{{ ssh_source_url if ssh_source_url is defined else ''}}" diff --git a/ansible/windows/roles/systemprep/tasks/main.yml b/ansible/windows/roles/systemprep/tasks/main.yml new file mode 100644 index 0000000..2a7a179 --- /dev/null +++ b/ansible/windows/roles/systemprep/tasks/main.yml @@ -0,0 +1,179 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +# This file was adapted from https://github.com/Azure/aks-engine/blob/master/vhd/packer/configure-windows-vhd.ps1 for ansible +- name: Remove Windows updates default registry settings + win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\ + state: absent + delete_key: yes + +- name: Add Windows update registry path + win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate + state: present + +- name: Add Windows automatic update registry path + win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU + state: present + +# https://docs.microsoft.com/en-us/windows/deployment/update/waas-wu-settings#configuring-automatic-updates-by-editing-the-registry +- name: Disable Windows automatic updates in registry + win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU + state: present + name: NoAutoUpdate + data: 1 + type: dword + +- name: Set Windows automatic updates to notify only in registry + win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU + state: present + name: AUOptions + data: 2 + type: dword + +# Hyper-V messes with networking components on startup after the feature is enabled +# causing issues with communication over winrm and setting winrm to delayed start +# gives Hyper-V enough time to finish configuration before having packer continue. +- name: Set WinRm Service to delayed start + win_command: sc.exe config winrm start=delayed-auto + +# Best effort to update defender signatures +# This can fail if there is already a signature +# update running which means we will get them anyways +# Also at the time the VM is provisioned Defender will trigger any required updates +- name: Update Windows Defender signatures + win_shell: | + $service = Get-Service "Windefend" + $service.WaitForStatus("Running","00:5:00") + Update-MpSignature + ignore_errors: yes + +# Find KB Article numbers: +# - WS 2019 https://support.microsoft.com/en-us/help/4464619 +# - WS 2022 https://support.microsoft.com/topic/windows-server-2022-update-history-e1caa597-00c5-4ab9-9f3e-8212fe80b2ee +# Task to install specific updates by KB. All categories are specified as the module +# won't install the update unless the category matches. Setting windows_updates_kbs_numbers to [] +# will skip this task. +- name: Install Windows updates based on KB numbers + win_updates: + whitelist: "{{ windows_updates_kbs_numbers }}" + reboot: yes + category_names: + - Application + - Connectors + - CriticalUpdates + - DefinitionUpdates + - DeveloperKits + - Drivers + - FeaturePacks + - Guidance + - SecurityUpdates + - ServicePacks + - Tools + - UpdateRollups + - Updates + when: windows_updates_kbs_numbers|length > 0 + +# Task to install any outstanding updates that belong to specific categories. Setting +# windows_updates_category_names to [] will skip this task. +- name: Install Windows updates based on Categories + win_updates: + category_names: "{{ windows_updates_category_names }}" + reboot: yes + when: windows_updates_category_names|length > 0 + +- import_tasks: ssh-feature.yml + when: ssh_source_url == "" + +- import_tasks: ssh-archive.yml + when: ssh_source_url != "" + +- name: Set default SSH shell to Powershell + win_regedit: + path: HKLM:\SOFTWARE\OpenSSH + state: present + name: DefaultShell + data: '{{ systemdrive.stdout | trim }}\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' + type: string + +- name: Create SSH program data folder + win_shell: If (-Not (Test-Path -Path "$env:ProgramData\ssh")) { mkdir "$env:ProgramData\ssh" } + +- name: Enable ssh login without a password + win_shell: Add-Content -Path "$env:ProgramData\ssh\sshd_config" -Value "PasswordAuthentication no`nPubkeyAuthentication yes" + +- name: Set SSH service startup mode to auto and ensure it is started + win_service: + name: sshd + start_mode: auto + state: started + +# Apply HNS flags for fixes that need to be enabled via Registry +# these eventually get turned on automatically and can be removed in future releases +- name: Apply HNS control Flags 0x40 and 0x10 in 2022-11B patches + win_regedit: + path: HKLM:\SYSTEM\CurrentControlSet\Services\hns\State + state: present + name: HNSControlFlag + data: 0x50 + type: dword + when: distribution_version == "2019" + +- name: Apply WCIFS fix + win_regedit: + path: HKLM:\SYSTEM\CurrentControlSet\Services\wcifs + state: present + name: WcifsSOPCountDisabled + data: 0 + type: dword + when: distribution_version == "2019" + +- name: Expand dynamic port range to 34000-65535 to avoid port exhaustion + win_shell: netsh int ipv4 set dynamicportrange tcp 34000 31536 + +- name: Add required Windows Features + win_feature: + name: + - Containers + - Hyper-V-PowerShell + state: present + register: win_feature + +# Due to a limitation in some CNI plugins the Hyper-V role needs to be installed in order +# to use the VMSwitch Powershell Cmdlets. +# An issue has been logged to have the networking components to be split out but until +# that is complete, environments that do not support running a hypervisor require the +# below which skips the CPU check for Hypervisor support and still installs the VMSwitch Cmlets +# when disable_hypervisor is set to true +# https://github.com/microsoft/Windows-Containers/issues/80 + +- name: Add Hyper-V + win_shell: | + dism /online /enable-feature /featurename:Microsoft-Hyper-V /all /NoRestart + register: hyperv_installed + failed_when: hyperv_installed.rc != 1 and hyperv_installed.rc != 0 + +- name: Disable Hypervisor + win_shell: | + dism /online /disable-feature /featurename:Microsoft-Hyper-V-Online /NoRestart + when: (disable_hypervisor | default(false) | bool) + register: hypervisor_disabled + failed_when: hypervisor_disabled.rc != 1 and hypervisor_disabled.rc != 0 + +- name: Reboot + win_reboot: diff --git a/ansible/windows/roles/systemprep/tasks/ssh-archive.yml b/ansible/windows/roles/systemprep/tasks/ssh-archive.yml new file mode 100644 index 0000000..bb27d2f --- /dev/null +++ b/ansible/windows/roles/systemprep/tasks/ssh-archive.yml @@ -0,0 +1,73 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: Create OpenSSH directory structure + win_file: + path: "{{ item }}" + state: directory + loop: + - '{{ programfiles.stdout | trim }}\OpenSSH' + - '{{ alluserprofile.stdout | trim }}\ssh' + +# Win32-OpenSSH requires SYSTEM and Administrator groups having Write +# permissions on directory 'C:\Program Files\OpenSSH', authenticated +# users having only Read and Execute permissions on it, see: +# https://github.com/PowerShell/Win32-OpenSSH/wiki/Install-Win32-OpenSSH +# +# "Make sure binary location has the Write permissions to just to SYSTEM, +# Administrator groups. Authenticated users should and only have Read and +# Execute." +# +# Folder 'C:\Program Files\OpenSSH' inherits users and permissions from its +# parent folder when it is created, by default, SYSTEM and Administrator +# already have Write permissions on it, the only exception is the inherited +# user BUILTIN\Users has ReadAndExecute permission but only authenticated +# users are allowed to have such permission, this prevent us from connecting +# to the sshd server, just remove it. +- name: Disable inheritance of OpenSSH directory + win_acl_inheritance: + path: '{{ programfiles.stdout | trim }}\OpenSSH' + state: absent + reorganize: yes +- name: Remove permission for Users + win_acl: + path: '{{ programfiles.stdout | trim }}\OpenSSH' + user: BUILTIN\Users + rights: ReadAndExecute,Synchronize + type: allow + state: absent + inherit: 'None' + propagation: 'None' + +- name: Download OpenSSH Archive + win_get_url: + url: '{{ ssh_source_url }}' + dest: '{{ tempdir.stdout | trim }}\OpenSSH.zip' + register: ssh + retries: 5 + delay: 3 + until: ssh is not failed + +- name: Unzip OpenSSH Archive + win_unzip: + src: '{{ ssh.dest }}' + dest: '{{ tempdir.stdout | trim }}' + recurse: no + delete_archive: yes + +- name: Install OpenSSH + win_shell: | + Get-ChildItem -Path "{{ tempdir.stdout | trim }}\OpenSSH-Win64\*" -Recurse | Move-Item -Destination "{{ programfiles.stdout | trim }}\OpenSSH" + Get-ChildItem -Path "{{ programfiles.stdout | trim }}\OpenSSH" | Unblock-File + & 'C:\Program Files\OpenSSH\install-sshd.ps1' diff --git a/ansible/windows/roles/systemprep/tasks/ssh-feature.yml b/ansible/windows/roles/systemprep/tasks/ssh-feature.yml new file mode 100644 index 0000000..8df8426 --- /dev/null +++ b/ansible/windows/roles/systemprep/tasks/ssh-feature.yml @@ -0,0 +1,21 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Requires admin rights to install +# https://docs.ansible.com/ansible/latest/user_guide/become.html#become-and-windows +- name: Install OpenSSH + win_shell: Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 + become: yes + become_method: runas + become_user: SYSTEM diff --git a/azure_targets.sh b/azure_targets.sh new file mode 100644 index 0000000..ede51d3 --- /dev/null +++ b/azure_targets.sh @@ -0,0 +1,6 @@ +VHD_TARGETS="ubuntu-1804 ubuntu-2004 ubuntu-2204 centos-7 rhel-8 windows-2019 windows-2019-containerd windows-2022-containerd" +VHD_CI_TARGETS="ubuntu-2004 ubuntu-2204 centos-7 windows-2019-containerd windows-2022-containerd" +SIG_TARGETS="ubuntu-1804 ubuntu-2004 ubuntu-2204 centos-7 rhel-8 windows-2019 windows-2019-containerd windows-2022-containerd flatcar" +SIG_CI_TARGETS="ubuntu-2004 ubuntu-2204 centos-7 windows-2019-containerd windows-2022-containerd flatcar" +SIG_GEN2_TARGETS="ubuntu-1804 ubuntu-2004 ubuntu-2204 centos-7 flatcar" +SIG_GEN2_CI_TARGETS="ubuntu-2004 ubuntu-2204 centos-7 flatcar" diff --git a/cloudinit/.gitignore b/cloudinit/.gitignore new file mode 100644 index 0000000..5131f95 --- /dev/null +++ b/cloudinit/.gitignore @@ -0,0 +1,3 @@ +/*.iso +/*.tar.gz +/.cidata-* \ No newline at end of file diff --git a/cloudinit/Makefile b/cloudinit/Makefile new file mode 100644 index 0000000..5a243ea --- /dev/null +++ b/cloudinit/Makefile @@ -0,0 +1,31 @@ +all: build + +CONFIG_DIR ?= .cidata-$(shell date +%s) +CONFIG_DIR ?= $(abspath $(CONFIG_DIR)) + +ifneq (,$(strip $(KUBERNETES_VERSION))) +ISO ?= cidata-$(KUBERNETES_VERSION).iso +endif +ISO ?= cidata.iso +ISO := $(abspath $(ISO)) + +$(ISO): + @rm -f $@ + @mkdir -p $(CONFIG_DIR) && cp user-data meta-data $(CONFIG_DIR)/ +ifneq (,$(strip $(KUBERNETES_VERSION))) + sed 's/kubernetesVersion: v1.13.6/kubernetesVersion: $(KUBERNETES_VERSION)/' >$(CONFIG_DIR)/user-data /dev/null))) + cd $(CONFIG_DIR) && genisoimage -output $@ -volid cidata -joliet -rock user-data meta-data +else + hdiutil makehybrid -o $@ -hfs -joliet -iso -default-volume-name cidata $(CONFIG_DIR) +endif + @rm -fr $(CONFIG_DIR) +.PHONY: $(ISO) + +build: $(ISO) + +clean: + rm -fr .cidata-* *.iso *.tar.gz +.PHONY: clean diff --git a/cloudinit/README.md b/cloudinit/README.md new file mode 100644 index 0000000..27fb37b --- /dev/null +++ b/cloudinit/README.md @@ -0,0 +1,7 @@ +# Cloud-init Test Data + +The files in this directory: + +* **Are** example data used for testing +* Are **not** included in any of the images +* Should **not** be used in production systems diff --git a/cloudinit/ca.crt b/cloudinit/ca.crt new file mode 100644 index 0000000..7184ffc --- /dev/null +++ b/cloudinit/ca.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICyzCCAbOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl +cm5ldGVzMB4XDTE5MDUyMTE0MzkwN1oXDTI5MDUxODE0MzkwN1owFTETMBEGA1UE +AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPrB +vAWE8PxPLuWT264DwTpINrtiERzO0XGHUwfXW2QltBQKv7c2JfUufZBqZqzD4KzV +6IbUGoOl4Gmf5IEp4nbNFF3xHcbbNRPcosE9e6MXJpKddy+JP1h3JRuXJgI/T5ng +XCLqVDlVoH7PPJ1Sl2PN61HoNF+AOwh4qVBgVgQna+HGy2zqqz4N9YwGMoCcMwHL +0vBTvp/CxfyDdliWiaC8XnyEKb6CtUc0v5iNC5tk7DD6MVheqLYUBL3pDct4AQVy +Ur0nJm1mQUihFnveUitiRzZ64LVuTne8kvupfQrPAPoDyT80yxp8BLx9Diz9dCp+ +JtjilQL9/tYN403bKS0CAwEAAaMmMCQwDgYDVR0PAQH/BAQDAgKkMBIGA1UdEwEB +/wQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBACOu+VKnB+55sQk627pxEsVu +7KMRSFInjzFjksisjiIOXeaUKoQHZZcso32U67p61LPaTZeDFzKEYS+dXFrfyab5 +xgUU2h1CFSX9zPRlp/OALt+5KW76nSwPkEl9OxMvW0MglShAZUm2J9EbFIdKzLZX +2TyzfVlxXuLWdToDjE2xFxvyJKz8wj7il9S/oaNerxQwxPXNoevlmPijOKZP3v/K +oatSeOqvUds0IudEMBEauupqKFfz5Xwpui268wNzodxyPrBK/wBH0qFxtnwEcfYD +P05LwTpP+xvqxqNNyIFRwFZw54hKIqVfKVqNib/iYZq4JzDW5pWrQa3xoA2IoGM= +-----END CERTIFICATE----- diff --git a/cloudinit/ca.key b/cloudinit/ca.key new file mode 100644 index 0000000..1c75d62 --- /dev/null +++ b/cloudinit/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA+sG8BYTw/E8u5ZPbrgPBOkg2u2IRHM7RcYdTB9dbZCW0FAq/ +tzYl9S59kGpmrMPgrNXohtQag6XgaZ/kgSnids0UXfEdxts1E9yiwT17oxcmkp13 +L4k/WHclG5cmAj9PmeBcIupUOVWgfs88nVKXY83rUeg0X4A7CHipUGBWBCdr4cbL +bOqrPg31jAYygJwzAcvS8FO+n8LF/IN2WJaJoLxefIQpvoK1RzS/mI0Lm2TsMPox +WF6othQEvekNy3gBBXJSvScmbWZBSKEWe95SK2JHNnrgtW5Od7yS+6l9Cs8A+gPJ +PzTLGnwEvH0OLP10Kn4m2OKVAv3+1g3jTdspLQIDAQABAoIBAAnGocjpTOfZQm/K +2uam2NKn3BJkGVyxJ3MwnmkQ2UxHOAUMAjtnTgWIAXcu3r/fhxPV5xHSlRIK1nvn +suXiNyUAi8m5y7pj82bJ1EKKXhaWoXdX0jy1MhYF1xmDRAUUaMsL9yuZTR1LHE21 +UJyliqdmcMUps1kBy8thwOAUUWYp+4ZUKSVh9vPaOQbsPaTEWgQziz5rY/D7tABz +cqLfmEFfSKDBmldvWvLga8xIMoLAQHjvTj6m04TWjndppazdwGokv6VLphcr8xXu +2VUBBnXUaS9ed0GPo8I7OuF0J3fTynkvNE+WjJ+gCj352V02OaDB3i5xc0WV3mUI +5mX/JpUCgYEA/ZFfzOyq5EIaGqIvNWvkv5L5zuoeP7HTK5odlyYdSBk7qlJU9BW2 +LY6QS+FClAc1ZQxiegfk7yt+Q1f1KRlwOjqOsQ5uM/fkMhK5D01I9jRHT62v46Uo +5vOhAk+8vTof/BUXP4mAImlYfGbxC9Pd/uza3Nqd7MtV8mcG8u5skmcCgYEA/Sl1 +LKMBhazHx/Y8uw0aKlW3HmEfhE11RAAPmDn3Fr/CO1MkLaPRzVWDBO3l5DI30l8H +apGYd78UePW43og0vHueN5IjHalOIdvWRFDqLbB68/MJDBcYSMTV0VRZq3owV3U8 +8gjILQhxZd2A2nbSbGzhaqA4qYQxHkc4SsJ4c0sCgYEA9NPeuhBxWH9ykPCzwFLy +xP52cRCgMEPUby8ZDw3gC/NBJszj9eDYy9fw/zL6g88KAQ0aMFVa+Ir4GLHeptZH +BBHyIIacZVUeYjKtuFaY8g+8IvTC9XxMp+HoPkEbLWHn7A+5KMHslE0/AK6sYw0o +NbIgCEqVXUNfMvPDQ+BtuFUCgYEAhwvmhbkuxrA0omaoXt/OZWb0GDCXL2xicbPW +nc3OEUSZyCvB0NbixDYpfiepyuE/BenLev3P55D2ys/JnmvqNeF7tQkv0lLOayFq +W2cOhPDvpdKvsNNhEPBvXwswCllUEFNp/3LP1bX7R+uxINjhxxUN6mMt1r+9s/kp +/jdfKaECgYEA1SDLH7H6ANHEiel+oKXKHDf7WD+ZYphfaekwTYd4SyJJDPMJ5AXF +Mu7gYKsAVl2Eb7BgmE/yIkTSsxqvIDWllPQjnVfClXEGfcrWCuLuQjqxQUFne+Li +GzvsaINFnePtiea3zt2BK9FUSp3ceIFuZATcSbODq8HOJzutSreXbts= +-----END RSA PRIVATE KEY----- diff --git a/cloudinit/id_rsa.capi b/cloudinit/id_rsa.capi new file mode 100644 index 0000000..0919a94 --- /dev/null +++ b/cloudinit/id_rsa.capi @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAxSCrM09uuR2LocFidwsKMwilyN8veG0KKHEH9fxEau15Fiee1t65 +G6TF7TNJdSCn4Y55EcxZFw2uVoEWBbwKLIevC3ipmIaCWDip8r8f52AU/2mFnS7v3nYL/E +/BmChtTIlOkXTb7BrFyACoZJwbXLJk3Edp4hMDpl0OTPz96DLMFpPuFiHFsiVOgVgEGyv2 +pVZ+ZCEUBWrOO5Oj5JtU4O0yr/jDl+iT87/IDqsb6nFxSqBHE7YQtibCr5kcADrS6wR4eQ +TMzilfRvgp5oM5jP6eqlNZ33m7wzbgbc04VET9JU3CRpn/oOVrWyfL3VHfjABWRqb6AEZd +b9bOFG5HnQAAA8BPyNvFT8jbxQAAAAdzc2gtcnNhAAABAQDFIKszT265HYuhwWJ3CwozCK +XI3y94bQoocQf1/ERq7XkWJ57W3rkbpMXtM0l1IKfhjnkRzFkXDa5WgRYFvAosh68LeKmY +hoJYOKnyvx/nYBT/aYWdLu/edgv8T8GYKG1MiU6RdNvsGsXIAKhknBtcsmTcR2niEwOmXQ +5M/P3oMswWk+4WIcWyJU6BWAQbK/alVn5kIRQFas47k6Pkm1Tg7TKv+MOX6JPzv8gOqxvq +cXFKoEcTthC2JsKvmRwAOtLrBHh5BMzOKV9G+CnmgzmM/p6qU1nfebvDNuBtzThURP0lTc +JGmf+g5WtbJ8vdUd+MAFZGpvoARl1v1s4UbkedAAAAAwEAAQAAAQAR4DeGLKLWyJYb8gRy +1R50qEkYYRzV59Vu+2kEZn7xz10WpDskMwhIOHX2X5s+stpmetwBwC0oCQaRM52CoZ2ukh +NOj/+ZJEF3rJPEvo4vFihxTOlf6py36K6Hj9f3a1sWALGOQTGcRIVA8MZUcU+N5WN+Ej/I +z36aPIAKfTqtLp1CdaAuw/9l48+e71yE4ESiaXBhTGlaTzATqLcN2URhjYwsJZverxlEtE +qWQmQ+vwm0JT1hsU5OEm/K7rtg9h0DZh1fLZlkbOSDX01nOsNnx4eAtuq+TLOuCvcc7Fl9 +FT4oYbGlhad/xLtxbcXrukEMLI3x4cMMfTVxJIQ5PcThAAAAgDu0Jmj+bWT4HhCTmfL8jG +g14j3HxyDx7fXCF7VJvZfpffxhkmHJEJL5+450XWm5XVE/4yYPF0Gghyh0wHcdXlB8IMHl +D7u9eWBOGNCSxv0uZAVKnRRlRsxF6kZ4uN+T+BGfIHTOBwli2xYv7tAAyryShbcf1iMKUh +SY31U9BsokAAAAgQDttiLUC4wjwlqEwAWg7FAnmz7QQ7wX6SxVWhbWm5XTkX3NfF2n4Ukg +qLoZh63RT3akrVtrWj0jG/Ozu/csSSNz0lgh1NbkWe9/wwiJWyKZEpHv5TIn6FhBSp4Txk +jBD68gkV80ys3Uw0SwI7eIUkKkPgUkMZMlYkeglXiDxHVjlQAAAIEA1Es12D9jRRHmNJgk +XOI8XObwfXZL+QVY8n2afliO3T3zDAwzbILY1Fn9DdSc87cjnadZ5lL3GPamxBzrdBnBx+ +DBU4b5oeUHkCh+bvDxdrbc4hsiVqgcRGD2p7M2001tcjJUCcqSA5r6wDTYO+WvAoMcZurr +NoSRtzHmev+i0ekAAAAEY2FwaQECAwQFBgc= +-----END OPENSSH PRIVATE KEY----- diff --git a/cloudinit/id_rsa.capi.pub b/cloudinit/id_rsa.capi.pub new file mode 100644 index 0000000..cee89af --- /dev/null +++ b/cloudinit/id_rsa.capi.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFIKszT265HYuhwWJ3CwozCKXI3y94bQoocQf1/ERq7XkWJ57W3rkbpMXtM0l1IKfhjnkRzFkXDa5WgRYFvAosh68LeKmYhoJYOKnyvx/nYBT/aYWdLu/edgv8T8GYKG1MiU6RdNvsGsXIAKhknBtcsmTcR2niEwOmXQ5M/P3oMswWk+4WIcWyJU6BWAQbK/alVn5kIRQFas47k6Pkm1Tg7TKv+MOX6JPzv8gOqxvqcXFKoEcTthC2JsKvmRwAOtLrBHh5BMzOKV9G+CnmgzmM/p6qU1nfebvDNuBtzThURP0lTcJGmf+g5WtbJ8vdUd+MAFZGpvoARl1v1s4Ubked capi diff --git a/cloudinit/meta-data b/cloudinit/meta-data new file mode 100644 index 0000000..6289f80 --- /dev/null +++ b/cloudinit/meta-data @@ -0,0 +1,12 @@ +cleanup-guestinfo: +- userdata +instance-id: iid-capi +local-hostname: capi.vm +network: + version: 2 + ethernets: + nics: + match: + name: ens* + dhcp4: yes + dhcp6: yes diff --git a/cloudinit/user-data b/cloudinit/user-data new file mode 100644 index 0000000..0e5e9e8 --- /dev/null +++ b/cloudinit/user-data @@ -0,0 +1,263 @@ +## template: jinja +#cloud-config + +write_files: +- path: /etc/kubernetes/pki/ca.crt + owner: root:root + permissions: '0640' + content: | + -----BEGIN CERTIFICATE----- + MIICyzCCAbOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl + cm5ldGVzMB4XDTE5MTIwODA3MjcwM1oXDTI5MTIwNTA3MzIwM1owFTETMBEGA1UE + AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN7G + 25nzw1aZ4Jt1dsrS/KeYMT4bxtmStu/8hvAy3vYD+OgLieIJI3UvNQfkf7UXE8Ad + 9nfGM3FcFSrsaXZ7ojW3Qd9fmwwS59nq0RY8ueF4spD/sO4jWe3AcC5m9sLB/ua5 + L4uWBqcBIa2iTHGqzIYxQfPhfSMncA+YieNCdcMk08t34uXPUjXTGck1I4UrBs/H + NvSszihL9ysL/tJC4prd1lBUxXJ+44aANVMloOWfr7yljgTXxEFCjJxec+xh2P6O + bqG7+TZvp75odj9jkVTZ2ENNq8tZFQjL+nD2fyafcXWoD3GmVT/tm+PtqUsc6C3M + pZCsmtkYHOOc3yklrl0CAwEAAaMmMCQwDgYDVR0PAQH/BAQDAgKkMBIGA1UdEwEB + /wQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBABxtRM80A5yJvxkZ0ELdHRWJ + 0rN+4tdFxjsDk9kci2pjuTAnNza8iiQrq6GaV00GO2nxQW0SXMj/5NL9H/wCqMi1 + vvueeDh8rOPCQ/UyjSi9/W4Jujh/L5KmTob7Ltw3alXFeE3Q/jwLVEFn3CSg2n3s + 1NefiO1/kJCYrKgtBiy704C6jvPDvaHV/SVvGhG9u+4xvyQeyzy4EOaupMeUps/A + 8qblFkiDwo/87cZoTr9z9qhitJI743UIEcrcoexMw+XhH7f0SsKa0gZn25PW63VO + K9CPCsvRfOFctN/qdvQSD7NIy7eqExn+9KWlPRmZuy2fMawXLXyn1JIAJTq5eeQ= + -----END CERTIFICATE----- + +- path: /etc/kubernetes/pki/ca.key + owner: root:root + permissions: '0600' + content: | + -----BEGIN RSA PRIVATE KEY----- + MIIEowIBAAKCAQEA3sbbmfPDVpngm3V2ytL8p5gxPhvG2ZK27/yG8DLe9gP46AuJ + 4gkjdS81B+R/tRcTwB32d8YzcVwVKuxpdnuiNbdB31+bDBLn2erRFjy54XiykP+w + 7iNZ7cBwLmb2wsH+5rkvi5YGpwEhraJMcarMhjFB8+F9IydwD5iJ40J1wyTTy3fi + 5c9SNdMZyTUjhSsGz8c29KzOKEv3Kwv+0kLimt3WUFTFcn7jhoA1UyWg5Z+vvKWO + BNfEQUKMnF5z7GHY/o5uobv5Nm+nvmh2P2ORVNnYQ02ry1kVCMv6cPZ/Jp9xdagP + caZVP+2b4+2pSxzoLcylkKya2Rgc45zfKSWuXQIDAQABAoIBAG+LBf1pjg7YcRul + jtszFQodK9q7Ma9SxIIY4L932Sc5CfG1hU5F4RkVj+npwP+9FEiHOBb0iGFkB708 + LfoDIU7f+P2M8ybyeGAmMJZ/xVpdgpsTS1WRIb/nMMQrDbaqR6TiaCnt7lrEOEam + Erx/FnTCSRGBf5SQdI16hN/jMO+Jm7yrKNfikkgoT5YF/bhZAuOEtdHFF9WiLLUY + KV64N9CvRTqJBA/ayvoT+35LVtFEKoYAosiSuT+qJVPHye8hL9HAQKiUWnfwW43m + hkzKDzq7kKilUy+T31UGWy6uvkJTAOPrSE6JxcChwG0bI0Ux5x5hwyM08M8uztC0 + ny1CggECgYEA9Zc2uxVVUeISQM1Go1pLQNUjwgfUcpN4DxXIyenh18U4OcFGKvVO + qeGELb0WXM/grKkK1t/Q+JNctBVX2sQT3y9GzjgD0JIhaGxwXJjd0zTpZbryef/J + eUmiBPoRPx8lRDQiQ2pZ7sU6QHZZHhuB4aH4TiuOplSjSCGlGS2RuEECgYEA6DgZ + +t6KHjOPO1Prua2HW0fsfAFXKsznvEZ3yRVdsvdrTa/KWdKvyiNQtgLSg7GtAfeq + b+w0M7e7hcDrj3H1Q8rRFjG6QcquzaJPEf6lWroTdypFChgGA2AgvF1WlWqoAlsY + QvDsSHCv8omPXEvKjNb/kNKKeXxse8Lsjj/YDx0CgYEArYdqCajaP2BQ627gQZ4U + XDv95ySnLUob4/TNijXy+QYGY3C4RrRMH+cUAUmBGpVbuEQ9P3Sn8GwXbbtC93Xi + YKnECwQzVdVbO7CbeCIfYxQY8mO03xUoY8XucDvNod888sMvjpj/8KF1NoUpFQf0 + rtxGWd2XDtnxAY+cW30CYMECgYBmBefcIRN7a1mdTiR9gjC6uOG8JXafdkDFpgwQ + i1OMQkmhav8y8W6MF3HpVVoZw+DnwWDfJV5V7n3zqKuVbRK9gwjTgGt9ZkfgTlQA + 6ujI8IGG1Epawe06ZGxddYCJyr8fsdf56RFWTjPvEeKrQR0vPXIwtV9jOngK87us + T35kPQKBgCc9VNuyDysSwkesHOozhqIBJW/6xDoodXd2QX6Ar+AXnzBkMbIkTJwA + 36y1hxWSj6vB2eJUhh/t8E9inU9fIj+Ak6Q3iAEvxeZZG3+x703Xd5e2YQSkiyDP + ojNzyST6biuKzV8VQKOg4XZju69eK3POH7yJ04u6DpA0Z68E5YuV + -----END RSA PRIVATE KEY----- + +- path: /etc/kubernetes/pki/etcd/ca.crt + owner: root:root + permissions: '0640' + content: | + -----BEGIN CERTIFICATE----- + MIICyzCCAbOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl + cm5ldGVzMB4XDTE5MTIwODA3MjcwNFoXDTI5MTIwNTA3MzIwNFowFTETMBEGA1UE + AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALFE + LSohMu0YznIKMmHq6/NCZXJLWbM2AHUJntnym/i1ynaJO5kNOTxy1DxdlM2GXXrN + tuCeu2s2EgeAa7odDmC1tXCnPbtmKx4UuNol/1ft9KbMu+nJ7tFdR1b4M8uquOUJ + dL2kyF7VLdl2J79TFKaHQs9yqhIQwdksBNBDG1NAE2fZDy3NxZA/WCx64kCWM7PT + Ea7SwKM6lJsLvFQBoJLXcG2DtC5lJN42a1+v/K8impTweJ5niJ1G6vvtr0n7X2ep + sezSTjKE/sEBhduWrI/FZnVI/OOS2vIKE+9NZm8nXwDl0LIOLMAbsxxBLUlE5Hih + PaaYQ2JfE5Ou0q+J9uMCAwEAAaMmMCQwDgYDVR0PAQH/BAQDAgKkMBIGA1UdEwEB + /wQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBAHSdUQ/LCAeEqyVDY7W8XUnt + y4vX/QHob3hVi8CdjkBP5RssotXAN9WBVB0myfo6fulcAQbQsbP5UCn1eJLnnYlX + GAaKbqVdzxz0vyIWgFRDYqWSPWXvFMJRQ1PHOJgh2xlI1QXI6Eu2SLcyl5oULZV4 + J9du4VoESAAHsZ5gwGuXtgepbAknNfdlal1L1ZI40aa6zkpy09gW/WwFFSt97Ps7 + c/NNZozZY5YNV+2riIDpINLw6qN6O8xKbCXZNA9D2yjoIjok3bX9MSvjkF/3voVu + 4lNx5D8KDzbyfSa9FSB5RkYAxwqYldzbnXVWtMXnsCPEhntEFzwfgfHzyterH38= + -----END CERTIFICATE----- + +- path: /etc/kubernetes/pki/etcd/ca.key + owner: root:root + permissions: '0600' + content: | + -----BEGIN RSA PRIVATE KEY----- + MIIEowIBAAKCAQEAsUQtKiEy7RjOcgoyYerr80JlcktZszYAdQme2fKb+LXKdok7 + mQ05PHLUPF2UzYZdes224J67azYSB4Bruh0OYLW1cKc9u2YrHhS42iX/V+30psy7 + 6cnu0V1HVvgzy6q45Ql0vaTIXtUt2XYnv1MUpodCz3KqEhDB2SwE0EMbU0ATZ9kP + Lc3FkD9YLHriQJYzs9MRrtLAozqUmwu8VAGgktdwbYO0LmUk3jZrX6/8ryKalPB4 + nmeInUbq++2vSftfZ6mx7NJOMoT+wQGF25asj8VmdUj845La8goT701mbydfAOXQ + sg4swBuzHEEtSUTkeKE9pphDYl8Tk67Sr4n24wIDAQABAoIBAFCrNeJ2OTask0ZS + XZsxzS5miMP6hleHu62HM0L7pP8Ju5k12mlMGz/nAa5258Cf5Op9szuFAMVJVesU + BfIsRt670lXxxaYCWJzX0Ud1Az6Ai1Vc1icOPiAI0u5BBVEp3/pa00V6N7TwTm0n + 1+65YCxoYX8GL18JQS4TWh1sbIL1Y70ioHha7gBQKJ4oph+B/Rd9VjZB/p3jutUx + T/DpcRbuYseOFXzUmYdtdELXeWc0OCpWTcHdC6xSfFlOoEqSDjXvNrFZ9xg6XO5J + +hAHqNytg5mmUAYW1JF+uMrXrr9rkEdZBT4jdYlnDqSRE14jsIWlpOURDquxOqku + g8zckAECgYEAyozhfeZMOW+rWJ/cWfZscsOHBM8WgjcfwEnqv1ryVGEu6Ksu3HY4 + buvp6ibxhnub6g167CXc2cagnXzYtxFrNcy7dhB+Tq1BGKR5O8qHT8V0Uyp1jybA + F3eqiO7HtdyhMMND+CbYf5d2IWZZyZsXiLj3fOvdhgBcYD/6ZzioLzMCgYEA4AtB + efmJdV6r0Y4efuFw7KsgQkSb7WSrGQwUlWZwPehS4n0+EduFg7bojxch7WxEf1/I + BR8tub7Et7IT7QR90jBgqo0T7PYCEbYEpZX9S3LG6O4snbMmlhWs6fVkyIu0vbwt + 0tn3tHb1kZxceLMyueIVD76pRADGfdAOIMqn2ZECgYEAjfVnJMH/slGdmaOQ4Exh + fZ2kR66Ma/8OdcpYiHzJUnI6Wj9/vGvu6W7WppFcvZBsmgGQoWl3+/nGIBqcGNJQ + fYhb6kds/t9WvFqpzUoIw9Pzz0X21rml78yi7Q3NUpyrLG3ueUFggyw4UvhU/YOB + XwWkDx2QSJaAJ8UvTqDa3xkCgYAQ8+kVcLNdBTfdGHLoSOjBj8VWHljqIqbrs0VP + dLvjg1PaPX3X3hOBPvLj+O0hSQL53GaHNK/CEVRTTg9B7QgUQoLeBOTQFmFbs7kw + SmQrNFkmwPbxS+AMlUFLRr1Ei4W2oamZ/dJHfoTgasN4gp7BAAF1+ohWU/SxCnA3 + PP5scQKBgANe++ZusilLjNG8CET1POf9zt7ZPogWRNeTb6GCqwmjh6/JTjAy9FdV + pAclkp/91usp8RmHnwJP7KzXCCq3qiJhytQl9lgiW3QecnKp5q0t9d5ObZU0vLQH + SJawb/BO0jqgG1R22DgSDqCsggOkqdG8Btm2i/rpuM4mnukUOtTQ + -----END RSA PRIVATE KEY----- + +- path: /etc/kubernetes/pki/front-proxy-ca.crt + owner: root:root + permissions: '0640' + content: | + -----BEGIN CERTIFICATE----- + MIICyzCCAbOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl + cm5ldGVzMB4XDTE5MTIwODA3MjcwM1oXDTI5MTIwNTA3MzIwM1owFTETMBEGA1UE + AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIm + mphoOFDsh6krI6sVGbBmq4K72+Dme7jCP8IMhvLDx5lSB0l4+HPowVd+cYScciEF + uENl4rZB9sWzkrdl/cukvqLd9Vkm57VQWzkDYKYj4Uvukda+9xu/hLv2FItwCzkm + wAeO5yNJKc/ut9qFEMUU45Lv+4929CDfEQaHLIPM7tUFTEKrqS8iyxnrO7DJ9ylC + IJULV3UaYAKsrYl5dU3zzjJOyio0O0BrDfZO7/mI2HYWtA3Y0AjNYWtHNOQ4qVZx + W2CGLgRmgq/9wMVnZtUIqj0ePuiS4VCXdZ5LNH6bOQT9ouO5P8v97/SnxG+dLfTd + PxrkXHd0uyLYujChaSMCAwEAAaMmMCQwDgYDVR0PAQH/BAQDAgKkMBIGA1UdEwEB + /wQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBAILC3rTdBQsDRox+rQnSkg0d + FLRyS5F3icVgE7C5iWLGKzM3kgYtyMAck4tCcEVMR1Omja0zOoRnvvfXKNzP8qf6 + XNXgQEloNd2QFAC5jQrGAZ365Ntop74VL8jigJTyKk7jdgPwuxIC5WLmVRfMspvj + ZoMK5GhbMhM6/IgkFq3tVQBoKnOYw+yeNKcRTraRuvZiEOcWT/9re75kYfOscBBZ + 3cFdvcnSA+qxlrn2PUY2Pfsrm6faTFZJ3ESkeDBLLIq81SsJiXuuK7tzRignP3UN + JL4aJZygxHqkLPFaTXZNYdJQeEOC5Kr6w+Rh/FeYRWVDyTo2+FDRC5v4WbjZqBc= + -----END CERTIFICATE----- + +- path: /etc/kubernetes/pki/front-proxy-ca.key + owner: root:root + permissions: '0600' + content: | + -----BEGIN RSA PRIVATE KEY----- + MIIEpQIBAAKCAQEAwiaamGg4UOyHqSsjqxUZsGargrvb4OZ7uMI/wgyG8sPHmVIH + SXj4c+jBV35xhJxyIQW4Q2XitkH2xbOSt2X9y6S+ot31WSbntVBbOQNgpiPhS+6R + 1r73G7+Eu/YUi3ALOSbAB47nI0kpz+632oUQxRTjku/7j3b0IN8RBocsg8zu1QVM + QqupLyLLGes7sMn3KUIglQtXdRpgAqytiXl1TfPOMk7KKjQ7QGsN9k7v+YjYdha0 + DdjQCM1ha0c05DipVnFbYIYuBGaCr/3AxWdm1QiqPR4+6JLhUJd1nks0fps5BP2i + 47k/y/3v9KfEb50t9N0/GuRcd3S7Iti6MKFpIwIDAQABAoIBAHV3remwhQJRWfM8 + qqQEbDqSpArBZtO4H/s1bCYYNj4DvmxBeS83Bfv7Q5+QXVaC4XqhX09IAdeMKaaM + c6SLDPd9gepAqQV7yI2nRMpGBOAArXunqI1plVEwmzquNNf4vpGhpGXuTGLJDYdF + Q+/uuqrDbL2mfWnb6vFIhaCRgJltAk/wpwfHkPlWdhJOUR2ofozCf4ebzQLZPYMv + NtRp+SsASuAzhVAxRAx2LSgyqjRaJUxi9Hczk0deWyxhZCUicBUfHVaL7F5nco6A + AIenh+ytnagFdYyxSLqiBvEKnR7RcwLAP9mgzyKLlnV/WC19tAZTYgOJc14NLp7w + jbQXmUECgYEAz7VcMyZvwSWMHY9XIab7iNHXCxb+uqVX0kfHf5maRyH3GCjbQ1SS + y36GJa3zwPOoPTs6Eohi68MXT/o/VPFK8DfSfWbegncKC39hl3MrMRqQlqrdlhCZ + TjYCke7YNg4X6kOKSIhr7WiHTeBqT6m3RtUjNtc5Xq7a/MGQ0y0ll0kCgYEA70pN + w0rHdURKrgBPinqmWYtlh5+0AQu8dOjo/Q2keH0rpfHo1qJ0jTzvM3FkiuM+hSIr + /BMVMCbq6jI4tLO+qadKOUfRb4VHfWAPKjoxKN3Jick9MFuPJFejoAfc4w5c0StC + Uih5jfhgcXhTKfQcsWiOXE4G7yuIBgrtsVHnoQsCgYEAzYMYctO5B1ZrSGQnRfi+ + MCXPq/1YlLw8JKc40Rvd9ju5DIKAUCJCDG5ntKhk4akz8UHEt8mSdstVKaDlQNpt + 1zikX8eEqXm2yqGCoiMlvTQXk7wD+xL5XNU7vcCg1Psk1nl1PUcd4ozmMzu0dJXr + 7X29lellrjU8H5I2BXxKEWECgYEAvRX/CuItaMnVJVgTgn7Wso2HZjtZlzX57nwZ + l9VO8PMgKZpV/j0TUoQjFIh3BZT6U2OkVCWUKZ+nI/cZfhlHAXKzIT5SDgjIbBcb + BKhFLmDLn/iIRI0lOHeUUaHE9xrjV1oSpy9alkf9lcyaEt3WSo4vaFsz+YxxiSzb + lLH/Ee8CgYEAzgWSlyV6f+Szs3794uaH+QHtjrAsIGbWnRR8pJw74mqwbzMtEVma + PlwA2dPh5ZHwXzUNHXDMeKflDg+tEZJr7MV60zlKCP5rUHHhtzixkNoyW1b5BxOp + WSmL3tHGg+NxwlifTGFm8a8WmSzZeS4uAwSGQUegvO7H8BClt+XrZk4= + -----END RSA PRIVATE KEY----- + +- path: /etc/kubernetes/pki/sa.pub + owner: root:root + permissions: '0640' + content: | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9sXSwCqczBP5YDJEzv/S + eHB1QinTswKE3NS4hxZ4iPO6q7cwkPd2yCtPNlbwZL+O30VIIKt8HoTwoX2njgfv + 0qHCBEvXK4Z3eVSH5I43IlH5p2l9BlyQHf2l2L+5bAkAxF3LXLBGPVsyQwIVzgKz + oFpx+otC5VI4G2u640Q4R1uG7steEaYVU0NgrBsuKfWpzcK10PjZ6Wr1RD+H4Z3N + UMZSuNQlwsZpL/32IZoOJmzDLwmc1gBeQSWsX8HHNvTiw3rvfR1pP5x4GUcY/m0y + lvH1T2Keo5+aV2YO9DeZzyFl/FRLGFfZ7MyxoRkaPBH4CQJMqYILFbVmGJZdLBrN + xwIDAQAB + -----END PUBLIC KEY----- + +- path: /etc/kubernetes/pki/sa.key + owner: root:root + permissions: '0600' + content: | + -----BEGIN RSA PRIVATE KEY----- + MIIEpAIBAAKCAQEA9sXSwCqczBP5YDJEzv/SeHB1QinTswKE3NS4hxZ4iPO6q7cw + kPd2yCtPNlbwZL+O30VIIKt8HoTwoX2njgfv0qHCBEvXK4Z3eVSH5I43IlH5p2l9 + BlyQHf2l2L+5bAkAxF3LXLBGPVsyQwIVzgKzoFpx+otC5VI4G2u640Q4R1uG7ste + EaYVU0NgrBsuKfWpzcK10PjZ6Wr1RD+H4Z3NUMZSuNQlwsZpL/32IZoOJmzDLwmc + 1gBeQSWsX8HHNvTiw3rvfR1pP5x4GUcY/m0ylvH1T2Keo5+aV2YO9DeZzyFl/FRL + GFfZ7MyxoRkaPBH4CQJMqYILFbVmGJZdLBrNxwIDAQABAoIBAQDkWy+pUEWEvD8a + e6oc+7LibP61WUtj7cLRL7AKogqsgQ6O9JhmrfknRpyopk8SjAlEJzKVuNrEyzR5 + 3HN/BW0yNyn5G81jo61Pg5+1Kr1ubvldqEpf5qwedKhSKSkHBlXFYR5dCWoWQRwD + 0hwnZWyfyNKMjO48k5cEGO76fBiwPfsmucRVuLMzbJH8ivvk/olopUnTwKP68Jhd + kfoJ5wGdhaTN3ec8HjOgMx2cNhpOwPf9lO9xAWxiFQatUpMlO3h227LA3qZRnzeJ + wQ/Hho9A+4eq5+eXEPFRCBD/cHIdMppPwDsp9vn+WxtAJncS39aT2lNZbs96y56v + 5mYGSVEBAoGBAP8IhnYedYEp6iWe9u7azbS1SdtfZ3kpiupAyQct5MQcEFwPY9XL + 6XqR/qyJWU48grX6QTILyRf9KNee9y0TKukPVj0STjtqlGXrBbkA9L14WZI7vdEc + kywxFNmrCRu5eiwEmsM1N/GelfqrIy44a59vzkExq1ugSS5maYVQqQoHAoGBAPe1 + SEL8pjyyhbDnLigo/pROd4rv0pJRG6Qf9o8sQm4iDDXHk0Jn2bvGbvCDU0l88T3M + bAki8TjCgAGmA+HO9xfTLVOmApr3ZOqBbX9CKCIDnCQi86LfS4tX/sAw/Ol3JuL3 + hRL3klYefhA5ziT6aPAoiyppIZddmhq5PKXTDi5BAoGAWOteBl0EVy6IBunv6sO7 + KSB0Sdpvt1z2pEpGTQyBUKcARTjQdECOfT/d7cZNruuGH3DLvmlBie2oidVRFRQz + x+KjDNQPFsRITWiuLBzbOBx4DnZp7jjqT2QlgucRW+tFX4eTdjBd+w5x+F4m+/yL + +aD/5tcsidvO6DCo5eSDq40CgYEA8L0Jbrj2ovTpg4UaUcONr/sWqTvszXDwSTEb + QghEw55peXbuAlLFxH3U4gDv1/MKzgG3WaXRBFXbbD65dSLc+BWv7qTf0VXwiQyi + q1RThsM1Y9VoLXmQhOtPVeAGXKwFaf4fIdDkMrhN0l0roLqRdB4jKmOkVuSMWb5/ + V7XoCUECgYBC2C/PIfGMjcJqAT6+oxKTqtZJxNX/sw0wj5ZyAhqc77beQEkEhopX + VxncpOw3yesIFtZgBZBZMgMkE0Alzxwxxz8v0PUv1MaPpeZGNvuIZ95hRbgaraHI + rKEN7grrzL7sI/DvruWG+JjCKchlD80wlNmDMQmfWetvsyCQm18r9A== + -----END RSA PRIVATE KEY----- + +- path: /tmp/kubeadm.yaml + owner: root:root + permissions: '0640' + content: | + --- + apiServer: + extraArgs: + cloud-provider: external + apiVersion: kubeadm.k8s.io/v1beta2 + certificatesDir: "" + clusterName: sakc4 + controlPlaneEndpoint: "" + controllerManager: + extraArgs: + cloud-provider: external + dns: + type: "" + etcd: {} + imageRepository: registry.k8s.io + kind: ClusterConfiguration + kubernetesVersion: 1.17.11 + networking: + dnsDomain: cluster.local + podSubnet: 100.96.0.0/11 + serviceSubnet: 100.64.0.0/13 + scheduler: {} + + --- + apiVersion: kubeadm.k8s.io/v1beta2 + kind: InitConfiguration + localAPIEndpoint: + advertiseAddress: "" + bindPort: 0 + nodeRegistration: + criSocket: /var/run/containerd/containerd.sock + kubeletExtraArgs: + cloud-provider: external + name: '{{ ds.meta_data.hostname }}' + +runcmd: + - "hostname \"{{ ds.meta_data.hostname }}\"" + - "echo \"::1 ipv6-localhost ipv6-loopback\" >/etc/hosts" + - "echo \"127.0.0.1 localhost {{ ds.meta_data.hostname }}\" >>/etc/hosts" + - "echo \"{{ ds.meta_data.hostname }}\" >/etc/hostname" + - 'kubeadm init --config /tmp/kubeadm.yaml' + +users: + - name: capv + sudo: ALL=(ALL) NOPASSWD:ALL + ssh_authorized_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFIKszT265HYuhwWJ3CwozCKXI3y94bQoocQf1/ERq7XkWJ57W3rkbpMXtM0l1IKfhjnkRzFkXDa5WgRYFvAosh68LeKmYhoJYOKnyvx/nYBT/aYWdLu/edgv8T8GYKG1MiU6RdNvsGsXIAKhknBtcsmTcR2niEwOmXQ5M/P3oMswWk+4WIcWyJU6BWAQbK/alVn5kIRQFas47k6Pkm1Tg7TKv+MOX6JPzv8gOqxvqcXFKoEcTthC2JsKvmRwAOtLrBHh5BMzOKV9G+CnmgzmM/p6qU1nfebvDNuBtzThURP0lTcJGmf+g5WtbJ8vdUd+MAFZGpvoARl1v1s4Ubked capi diff --git a/hack/boxes-flatcar.sh b/hack/boxes-flatcar.sh new file mode 100755 index 0000000..66e217e --- /dev/null +++ b/hack/boxes-flatcar.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +export VAGRANT_VAGRANTFILE=${VAGRANT_VAGRANTFILE:-/tmp/Vagrantfile.builder-flatcar} + +fetch_vagrantfile() { + curl -sSL -o ${VAGRANT_VAGRANTFILE} \ + https://raw.githubusercontent.com/flatcar/flatcar-packer-qemu/builder-ignition/Vagrantfile.builder-flatcar +} + +list_boxes() { + vagrant box list \ + | grep -E '^flatcar-(alpha|beta|stable|edge)-[0-9.]+' \ + | sed 's/flatcar-\(alpha\|beta\|stable\|edge\)-\([0-9.]\+\).*/\1 \2/' +} + +fetch_vagrantfile + +list_boxes | while read -r channel release; do + export FLATCAR_CHANNEL="$channel" + export FLATCAR_VERSION="$release" + + echo "##############################################" + echo "Image:" + virsh vol-info --pool default "flatcar-${channel}-${release}_vagrant_box_image_0.img" + echo "Env:" + echo " export FLATCAR_CHANNEL='$channel'" + echo " export FLATCAR_VERSION='$release'" + echo " export VAGRANT_VAGRANTFILE='$VAGRANT_VAGRANTFILE'" + + # shellcheck disable=SC2016 + vagrant status | grep -v 'Run `vagrant up`' + + [ "$1" = "cleanup" ] && { + echo "#### Cleaning up vagrant VM" + + img_name="flatcar-${channel}-${release}_vagrant_box_image_0.img" + box_name="packer_flatcar-${channel}-${release}_libvirt.box" + vagrant_name="flatcar-$channel-$release" + + vagrant halt + vagrant destroy -f + vagrant box remove "$vagrant_name" + virsh vol-delete --pool=default "$img_name" + + rm -f "$box_name" + } +done diff --git a/hack/convert-cloudstack-image.sh b/hack/convert-cloudstack-image.sh new file mode 100755 index 0000000..0bf150d --- /dev/null +++ b/hack/convert-cloudstack-image.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash + +# Copyright 2022 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +function xen_server_export() { + echo "Creating XenServer Export for $1" + qemu-img convert -f qcow2 -O raw "$1" "$1".raw + vhd-util convert -s 0 -t 1 -i "$1".raw -o "$1".vhd + faketime '2010-01-01' vhd-util convert -s 1 -t 2 -i "$1".vhd -o "$1-xen.vhd" + rm -f *.bak + echo "Created .vhd file, now zipping" + bzip2 "$1-xen.vhd" + chmod +r "$1-xen.vhd.bz2" + echo "$1 exported for XenServer: $1-xen.vhd.bz2" +} + +function vmware_export() { + echo "Creating VMware Export for $1" + qemu-img convert -f qcow2 -O vmdk -o adapter_type=lsilogic,subformat=streamOptimized,compat6 "$1" "$1-vmware.vmdk" + CDIR=$PWD + chmod 666 $1-vmware.vmdk + stage_vmx $1-vmware $1-vmware.vmdk + ovftool $1-vmware.vmx $1-vmware.ova + rm -f $1-vmware*.vmx $1-vmware*.vmdk + cd $CDIR + chmod +r "$1-vmware.ova" + echo "$1 exported for VMware: $1-vmware.ova" +} + +function stage_vmx() { + cat << VMXFILE > "${1}.vmx" +.encoding = "UTF-8" +displayname = "${1}" +annotation = "${1}" +guestos = "otherlinux-64" +virtualHW.version = "11" +config.version = "8" +numvcpus = "2" +cpuid.coresPerSocket = "2" +memsize = "2048" +pciBridge0.present = "TRUE" +pciBridge4.present = "TRUE" +pciBridge4.virtualDev = "pcieRootPort" +pciBridge4.functions = "8" +pciBridge5.present = "TRUE" +pciBridge5.virtualDev = "pcieRootPort" +pciBridge5.functions = "8" +pciBridge6.present = "TRUE" +pciBridge6.virtualDev = "pcieRootPort" +pciBridge6.functions = "8" +pciBridge7.present = "TRUE" +pciBridge7.virtualDev = "pcieRootPort" +pciBridge7.functions = "8" +vmci0.present = "TRUE" +floppy0.present = "FALSE" +ide0:0.clientDevice = "FALSE" +ide0:0.present = "TRUE" +ide0:0.deviceType = "atapi-cdrom" +ide0:0.autodetect = "TRUE" +ide0:0.startConnected = "FALSE" +mks.enable3d = "false" +svga.autodetect = "false" +svga.vramSize = "134217728" +scsi0:0.present = "TRUE" +scsi0:0.deviceType = "disk" +scsi0:0.fileName = "$2" +scsi0:0.mode = "persistent" +scsi0:0.writeThrough = "false" +scsi0.virtualDev = "lsilogic" +scsi0.present = "TRUE" +vmci0.unrestricted = "false" +vcpu.hotadd = "false" +vcpu.hotremove = "false" +firmware = "bios" +mem.hotadd = "false" +VMXFILE +} + +usage() { + echo "Converts a qcow2 image to any of the following formats" + echo " - x : XenServer [vhd]" + echo " - v : VMware [ova]" + echo "Usage: $0 QCOW2_IMAGE FORMAT" 1>&2 +} + +if [ "$1" = "-h" ]; then + usage + exit +fi + +FILE=$1 +if [ -z $FILE ]; then + usage + echo "File not specified. Exiting" + exit 1 +fi + +FORMAT=$2 +if [ -z $FILE ]; then + usage + echo "Format not specified. Exiting" + exit 1 +fi + +if [ ! -f $FILE ]; then + echo "File '$FILE' not found" + exit 1 +fi + +case $FORMAT in + v) + vmware_export $FILE + ;; + x) + xen_server_export $FILE + ;; + all) + vmware_export $FILE + xen_server_export $FILE + ;; + *) + echo "Unknown format. Supported options are [x, v]" + exit 1 +esac diff --git a/hack/ensure-ansible-windows.sh b/hack/ensure-ansible-windows.sh new file mode 100755 index 0000000..4f0241a --- /dev/null +++ b/hack/ensure-ansible-windows.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +source hack/utils.sh + +_version="0.4.2" + +if [[ ${HOSTOS} == "darwin" ]]; then + echo "IMPORTANT: Winrm connection plugin for Ansible on MacOS causes connection issues." + echo "See https://docs.ansible.com/ansible/latest/user_guide/windows_winrm.html#what-is-winrm for more details." + echo "To fix the issue provide the enviroment variable 'no_proxy=*'" + echo "Example call to build Windows images on MacOS: 'no_proxy=* make build-'" +fi + +# Change directories to the parent directory of the one in which this +# script is located. +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +# Disable pip's version check and root user warning +export PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_ROOT_USER_ACTION=ignore + +if pip3 show pywinrm >/dev/null 2>&1; then exit 0; fi + +ensure_py3 +pip3 install --user "pywinrm==${_version}" +if ! pip3 show pywinrm ; then exit 1; fi diff --git a/hack/ensure-ansible.sh b/hack/ensure-ansible.sh new file mode 100755 index 0000000..70f5b7a --- /dev/null +++ b/hack/ensure-ansible.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +source hack/utils.sh + +_version="2.11.5" + +# Change directories to the parent directory of the one in which this +# script is located. +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +# Disable pip's version check and root user warning +export PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_ROOT_USER_ACTION=ignore + +if ! command -v ansible >/dev/null 2>&1; then + ensure_py3 + pip3 install --user "ansible-core==${_version}" + ensure_py3_bin ansible + ensure_py3_bin ansible-playbook +fi + +ansible-galaxy collection install \ + community.general \ + ansible.posix \ + 'ansible.windows:>=1.7.0' \ + community.windows diff --git a/hack/ensure-azure-cli.sh b/hack/ensure-azure-cli.sh new file mode 100755 index 0000000..2d2a200 --- /dev/null +++ b/hack/ensure-azure-cli.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +source hack/utils.sh + +# Change directories to the parent directory of the one in which this +# script is located. +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +if command -v az >/dev/null 2>&1; then exit 0; fi + +# Disable pip's version check and root user warning +export PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_ROOT_USER_ACTION=ignore + +ensure_py3 +pip install -U pip setuptools +pip3 install --user azure-cli +ensure_py3_bin az azure-cli diff --git a/hack/ensure-boskosctl.sh b/hack/ensure-boskosctl.sh new file mode 100755 index 0000000..8492355 --- /dev/null +++ b/hack/ensure-boskosctl.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +if [[ -z "$(command -v boskosctl)" ]]; then + echo "installing boskosctl" + GO111MODULE=on go get sigs.k8s.io/boskos/cmd/boskosctl@master + echo "'boskosctl' has been installed to $GOPATH/bin, make sure this directory is in your \$PATH" +fi + +echo "testing boskosctl" +boskosctl --help diff --git a/hack/ensure-ct.sh b/hack/ensure-ct.sh new file mode 100755 index 0000000..b58474a --- /dev/null +++ b/hack/ensure-ct.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# Copyright 2022 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +_version="v0.9.3" + +# Change directories to the parent directory of the one in which this +# script is located. +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +source hack/utils.sh + +if command -v ct >/dev/null 2>&1; then exit 0; fi + +mkdir -p .local/bin && cd .local/bin + +if [[ ${HOSTOS} == "linux" ]]; then + _binfile="ct-${_version}-x86_64-unknown-linux-gnu" +elif [[ ${HOSTOS} == "darwin" ]]; then + _binfile="ct-${_version}-x86_64-apple-darwin" +fi +_bin_url="https://github.com/flatcar/container-linux-config-transpiler/releases/download/${_version}/${_binfile}" +curl -SsL "${_bin_url}" -o ct +chmod 0755 ct +echo "'ct' has been installed to $(pwd), make sure this directory is in your \$PATH" diff --git a/hack/ensure-go.sh b/hack/ensure-go.sh new file mode 100755 index 0000000..823a4a7 --- /dev/null +++ b/hack/ensure-go.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +# Ensure the go tool exists and is a viable version. +verify_go_version() { + if [[ -z "$(command -v go)" ]]; then + if [[ "${INSTALL_GO:-"true"}" == "true" ]]; then + curl -sSL https://golang.org/dl/go${GO_VERSION:-"1.16.3"}.linux-amd64.tar.gz | tar -C /usr/local -xzf - + export PATH=/usr/local/go/bin:$PATH + export PATH=$(go env GOPATH)/bin:$PATH + else + cat <&2 + return 1 + ;; +esac + +# Check if current binary is latest +if [ -f "${_binfile}" ]; then + current_shasum=$(get_shasum "${_binfile}") + if [ "$current_shasum" != "$_sha256" ]; then + echo "Wrong version of binary present." + else + echo "Right version of binary present" + # Check if binary is executable. + # If not, delete it and proceed. If it is executable, exit 0 + { [ -x "${_binfile}" ] && exit 0; } || rm -f "${_binfile}" + fi +fi + +# download binary, verify shasum, make it executable and clean up trash files. +_bin_dir="$(dirname "${_tarfile}")" +mkdir -p "${_bin_dir}" && cd "${_bin_dir}" +curl -SsL "${_bin_url}" -o "${_tarfile}" +tar -C "${_bin_dir}" -xzf "${_tarfile}" +rm "${_tarfile}" +printf "%s *${_binfile}" "${_sha256}" >"${_binfile}.sha256" +if ! checksum_sha256 "${_binfile}.sha256"; then + _exit_code="${?}" + rm -f "${_binfile}.sha256" + exit "${_exit_code}" +fi +rm -f "${_binfile}.sha256" +chmod 0755 "${_binfile}" diff --git a/hack/ensure-jq.sh b/hack/ensure-jq.sh new file mode 100755 index 0000000..650466c --- /dev/null +++ b/hack/ensure-jq.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +_version="1.6" # earlier versions don't follow the same OS/ARCH patterns + +# Change directories to the parent directory of the one in which this +# script is located. +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +source hack/utils.sh + +if command -v jq >/dev/null 2>&1; then exit 0; fi + +mkdir -p .local/bin && cd .local/bin + +if [[ ${HOSTOS} == "linux" ]]; then + _binfile="jq-linux64" +elif [[ ${HOSTOS} == "darwin" ]]; then + _binfile="jq-osx-amd64" +fi +_bin_url="https://github.com/stedolan/jq/releases/download/jq-${_version}/${_binfile}" +curl -SsL "${_bin_url}" -o jq +chmod 0755 jq +echo "'jq' has been installed to $(pwd), make sure this directory is in your \$PATH" diff --git a/hack/ensure-ovftool.sh b/hack/ensure-ovftool.sh new file mode 100755 index 0000000..f2b1611 --- /dev/null +++ b/hack/ensure-ovftool.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +[[ -z ${IB_OVFTOOL:-} ]] && exit 0 + +source hack/utils.sh + +if command -v ovftool >/dev/null 2>&1; then exit 0; fi + +echo "ovftool must be present to build OVAs. If already installed" >&2 +echo "make sure to add it to the PATH env var. If not installed, please" >&2 +echo "install latest from https://code.vmware.com/tool/ovf." >&2 +exit 1 diff --git a/hack/ensure-packer.sh b/hack/ensure-packer.sh new file mode 100755 index 0000000..00f3bab --- /dev/null +++ b/hack/ensure-packer.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +_version="1.8.5" + +# Change directories to the parent directory of the one in which this +# script is located. +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +source hack/utils.sh + +if command -v packer >/dev/null 2>&1; then exit 0; fi + +mkdir -p .local/bin && cd .local/bin + +SED="sed" +if command -v gsed >/dev/null; then + SED="gsed" +fi +if ! (${SED} --version 2>&1 | grep -q GNU); then + echo "!!! GNU sed is required. If on OS X, use 'brew install gnu-sed'." >&2 + exit 1 +fi + +_chkfile="packer_${_version}_SHA256SUMS" +_chk_url="https://releases.hashicorp.com/packer/${_version}/${_chkfile}" +_zipfile="packer_${_version}_${HOSTOS}_${HOSTARCH}.zip" +_zip_url="https://releases.hashicorp.com/packer/${_version}/${_zipfile}" +curl -SsLO "${_chk_url}" +curl -SsLO "${_zip_url}" +${SED} -i -n "/${HOSTOS}_${HOSTARCH}/p" "${_chkfile}" +checksum_sha256 "${_chkfile}" +unzip -o "${_zipfile}" +rm -f "${_chkfile}" "${_zipfile}" +echo "'packer' has been installed to $(pwd), make sure this directory is in your \$PATH" diff --git a/hack/ensure-powervs.sh b/hack/ensure-powervs.sh new file mode 100755 index 0000000..77cbae5 --- /dev/null +++ b/hack/ensure-powervs.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +# Copyright 2022 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +source hack/utils.sh + +SED="sed" +if command -v gsed >/dev/null; then + SED="gsed" +fi +if ! (${SED} --version 2>&1 | grep -q GNU); then + echo "!!! GNU sed is required. If on OS X, use 'brew install gnu-sed'." >&2 + exit 1 +fi + +_version="0.1.6" +_chkfile="packer-plugin-powervs_v${_version}_SHA256SUMS" +_chk_url="https://github.com/ppc64le-cloud/packer-plugin-powervs/releases/download/v${_version}/${_chkfile}" +_bin_url="https://github.com/ppc64le-cloud/packer-plugin-powervs/releases/download/v${_version}/packer-plugin-powervs_v${_version}_x5.0_${HOSTOS}_${HOSTARCH}.zip" +_zipfile="${HOME}/.packer.d/plugins/packer-plugin-powervs_v${_version}_x5.0_${HOSTOS}_${HOSTARCH}.zip" +_binfile="${HOME}/.packer.d/plugins/packer-plugin-powervs_v${_version}_x5.0_${HOSTOS}_${HOSTARCH}" +_powervs_bin="${HOME}/.packer.d/plugins/packer-plugin-powervs" + +_bin_dir="$(dirname "${_zipfile}")" +mkdir -p "${_bin_dir}" && cd "${_bin_dir}" +curl -SsLO "${_chk_url}" +curl -SsLO "${_bin_url}" +${SED} -i -n "/${HOSTOS}_${HOSTARCH}/p" "${_chkfile}" +checksum_sha256 "${_chkfile}" +rm -f "${_chkfile}" +unzip -o "${_zipfile}" +rm "${_zipfile}" +chmod 0755 "${_binfile}" +mv "${_binfile}" "${_powervs_bin}" diff --git a/hack/ensure-vhdutil.sh b/hack/ensure-vhdutil.sh new file mode 100755 index 0000000..df03c35 --- /dev/null +++ b/hack/ensure-vhdutil.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +# Copyright 2022 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +if ! command -v faketime >/dev/null 2>&1; then + echo "faketime must be present to convert to vhd" >&2 + exit 0 +fi + +if ! command -v vhd-util >/dev/null 2>&1; then + wget http://packages.shapeblue.com.s3-eu-west-1.amazonaws.com/systemvmtemplate/vhd-util + chmod +x vhd-util + wget http://packages.shapeblue.com.s3-eu-west-1.amazonaws.com/systemvmtemplate/libvhd.so.1.0 + echo "'vhd-util' and 'libvhd.so.1.0' has been installed to $(pwd), make sure this directory is in your \$PATH" +fi diff --git a/hack/generate-goss-specs.py b/hack/generate-goss-specs.py new file mode 100755 index 0000000..defcf03 --- /dev/null +++ b/hack/generate-goss-specs.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import itertools +import json +import os +import subprocess +import sys + +root_path = os.path.abspath(os.path.join(sys.argv[0], '..', '..')) + +# Define what OS's are supported on which providers +builds = {'amazon': ['amazon linux', 'centos', 'flatcar', 'ubuntu', 'windows'], + 'azure': ['centos', 'ubuntu', 'windows'], + 'ova': ['centos', 'photon', 'rhel', 'ubuntu', 'windows'], + 'oci':['ubuntu', 'oracle linux']} + +def generate_goss(provider, system, versions, runtime, dryrun=False, save=False): + cmd = ['goss', '-g', 'packer/goss/goss.yaml', '--vars', 'packer/goss/goss-vars.yaml'] + vars = {'OS': system, 'PROVIDER': provider, + 'OS_VERSION': versions['os'], + 'containerd_version': versions['containerd'], + 'docker_ee_version': versions['docker'], + 'distribution_version': versions['os'], + 'kubernetes_version': versions['k8s'], + 'kubernetes_deb_version': versions['k8s_deb'], + 'kubernetes_rpm_version': versions['k8s_rpm'], + 'kubernetes_source_type': 'pkg', + 'kubernetes_cni_version': versions['cni'], + 'kubernetes_cni_deb_version': versions['cni_deb'], + 'kubernetes_cni_rpm_version': versions['cni_rpm'], + 'kubernetes_cni_source_type': 'pkg', + 'runtime': runtime, + 'pause_image': versions['pause']} + + if system == "windows" and versions.get('ssh_url') is not None: + vars['ssh_source_url'] = versions['ssh_url'] + + # Build command + cmd.extend(['--vars-inline', json.dumps(vars), 'render']) + print('\nGenerating os: %s, provider: %s, runtime: %s' % (system, provider, runtime)) + print(cmd) + + # Run command with output going to file + if not dryrun: + if save: + out_dir = os.path.join(root_path, 'packer', 'goss') + out_filename = '%s-%s-%s-goss-spec.yaml' % (provider, + system.replace(' ', '-'), versions['k8s']) + out_filename = os.path.join(out_dir, out_filename) + with open(out_filename, 'w') as f: + subprocess.run(cmd, cwd=root_path, stdout=f, check=True) + else: + subprocess.run(cmd, cwd=root_path, check=True) + + +def read_json_file(filename): + j = None + with open(filename, 'r') as f: + j = json.load(f) + return j + + +def main(): + parser = argparse.ArgumentParser( + description='Generates GOSS specs. By default, generates all ' + 'possible specs to stdout.', + usage='%(prog)s [-h] [--provider {amazon,azure,ova}] ' + '[--os {al2,centos,flatcar,photon,rhel,ubuntu,windows}]') + parser.add_argument('--provider', + choices=['amazon', 'azure', 'ova','oci'], + action='append', + default=None, + help='One provider. Can be used multiple times') + parser.add_argument('--os', + choices=['al2', 'centos', 'flatcar', 'photon', 'rhel', 'ubuntu', 'windows'], + action='append', + default=None, + help='One OS. Can be used multiple times') + parser.add_argument('--dry-run', + action='store_true', + help='Do not run GOSS, just print GOSS commands') + parser.add_argument('--write', + action='store_true', + help='Write GOSS specs to file') + args = parser.parse_args() + + versions = {} + # Load JSON files with Version info + cni = read_json_file(os.path.join(root_path, 'packer', 'config', 'cni.json')) + versions['cni'] = cni['kubernetes_cni_semver'].lstrip('v') + versions['cni_deb'] = cni['kubernetes_cni_deb_version'] + versions['cni_rpm'] = cni['kubernetes_cni_rpm_version'].split('-')[0] + + k8s = read_json_file(os.path.join(root_path, 'packer', 'config', 'kubernetes.json')) + versions['k8s'] = k8s['kubernetes_semver'].lstrip('v') + versions['k8s_deb'] = k8s['kubernetes_deb_version'] + versions['k8s_rpm'] = k8s['kubernetes_rpm_version'].split('-')[0] + + containerd = read_json_file(os.path.join(root_path, 'packer', 'config', 'containerd.json')) + versions['containerd'] = containerd['containerd_version'] + + docker = read_json_file(os.path.join(root_path, 'packer', 'config', 'windows', 'docker.json')) + versions['docker'] = docker['docker_ee_version'] + + wincommon = read_json_file(os.path.join(root_path, 'packer', 'config', 'windows', 'common.json')) + versions['ssh_url'] = wincommon['ssh_source_url'] + + common = read_json_file(os.path.join(root_path, 'packer', 'config', 'common.json')) + versions['pause'] = common['pause_image'] + + providers = builds.keys() + if args.provider is not None: + providers = args.provider + + # Generate a unique list of all possible OS's if a choice wasn't made + oss = args.os + if args.os is None: + oss = [] + for x in list(builds.values()): + for o in x: + oss.append(o) + oss = list(set(oss)) + oss = [sub.replace('al2', 'amazon linux') for sub in oss] + # Generate spec for each valid permutation + for provider, system in itertools.product(providers, oss): + if system in builds[provider]: + if system == 'windows': + runtimes = ["docker-ee","containerd"] + os_versions = ["2019", "2004"] + elif system == 'rhel': + runtimes = ["containerd"] + os_versions = ["7", "8"] + elif system == 'photon': + runtimes = ["containerd"] + os_versions = ["3", "4"] + else: + runtimes = ["containerd"] + os_versions = [""] + for runtime in runtimes: + for version in os_versions: + versions["os"] = version + generate_goss(provider, system, versions, runtime, args.dry_run, args.write) + + +if __name__ == '__main__': + main() diff --git a/hack/image-build-flatcar.sh b/hack/image-build-flatcar.sh new file mode 100755 index 0000000..a598960 --- /dev/null +++ b/hack/image-build-flatcar.sh @@ -0,0 +1,145 @@ +#!/bin/sh -e + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +export VAGRANT_VAGRANTFILE=${VAGRANT_VAGRANTFILE:-/tmp/Vagrantfile.builder-flatcar} +export VAGRANT_SSH_PRIVATE_KEY=${VAGRANT_SSH_PRIVATE_KEY:-/tmp/vagrant-insecure-key} +export VAGRANT_SSH_PUBLIC_KEY=${VAGRANT_SSH_PUBLIC_KEY:-/tmp/vagrant-insecure-key.pub} + +usage() { + echo "Usage: $0 [] []" + echo " is one of: edge alpha beta stable (defaults to" + echo " stable)" + echo " release version to use (defaults to the latest" + echo " release available for )" +} +# -- + +check_for_release() { + channel="$1" + release="$2" + curl -L -s \ + "https://www.flatcar.org/releases-json/releases-$channel.json" \ + | jq -r 'to_entries[] | "\(.key)"' \ + | grep -q "$release" +} +# -- + +fetch_vagrant_ssh_keys() { + curl -sSL -o ${VAGRANT_SSH_PRIVATE_KEY} \ + https://raw.githubusercontent.com/hashicorp/vagrant/master/keys/vagrant + curl -sSL -o ${VAGRANT_SSH_PUBLIC_KEY} \ + https://raw.githubusercontent.com/hashicorp/vagrant/master/keys/vagrant.pub +} +# -- + +fetch_vagrantfile() { + curl -sSL -o ${VAGRANT_VAGRANTFILE} \ + https://raw.githubusercontent.com/flatcar/flatcar-packer-qemu/builder-ignition/Vagrantfile.builder-flatcar +} +# -- + +run_vagrant() { + echo "#### Fetching a test Vagrantfile remotely." + + fetch_vagrantfile + + echo "#### Importing $channel box to vagrant and setting up kubeadm." + + vagrant_name="flatcar-${channel}-${release}" + img_name="flatcar-${channel}-${release}_vagrant_box_image_0.img" + box_name="packer_flatcar_libvirt.box" + + export VAGRANT_VAGRANTFILE="${VAGRANT_VAGRANTFILE:-hack/Vagrantfile.flatcar}" + export VAGRANT_DEFAULT_PROVIDER="libvirt" + + echo "#### Cleaning up previous vagrant VMs" + vagrant halt || true + vagrant destroy -f || true + vagrant box remove "$vagrant_name" || true + virsh vol-delete --pool=default "$img_name" || true + + echo "#### creating and starting VM." + vagrant box add --name="$vagrant_name" "./$box_name" + vagrant up + vagrant ssh -c 'sudo systemctl stop locksmithd' + vagrant ssh -c 'sudo systemctl restart containerd' + + echo "#### Setting up kubeadm" + # shellcheck disable=SC1004 + vagrant ssh -c 'sudo kubeadm init --ignore-preflight-errors=NumCPU \ + --config=/etc/kubeadm.yml' + # shellcheck disable=SC2016 + vagrant ssh -c 'mkdir -p $HOME/.kube + sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config + sudo chown $(id -u):$(id -g) $HOME/.kube/config' + echo + vagrant ssh -c 'kubectl cluster-info' + echo + + echo "------------------------------------------------------------------" + echo "All done." + echo "You can access kubectl via 'vagrant ssh -c 'kubectl '" + echo "e.g." + echo " vagrant ssh -c 'kubectl get pods --all-namespaces'" + echo + echo " Please run:" + echo " export FLATCAR_CHANNEL='$channel'" + echo " export FLATCAR_VERSION='$release'" + echo " export VAGRANT_VAGRANTFILE='$VAGRANT_VAGRANTFILE'" + echo "before using vagrant commands." +} + +CAPI_PROVIDER=${CAPI_PROVIDER:-qemu} + +channel="$1" +case $channel in + edge);; + alpha);; + beta);; + stable);; + "") channel="stable";; + *) echo "Unknown channel '$channel'." + usage + exit 1;; +esac + +release="$2" +if [ -n "$release" ] ; then + check_for_release "$channel" "$release" || { + echo "Unknown release '$release' for channel '$channel'." + usage + exit 1; } +else + release="$(\ + "$(dirname "$0")"/image-grok-latest-flatcar-version.sh "$channel")" +fi + + +echo "#### Building for channel $channel, release $release." + +# set packer /vagrant env vars +FLATCAR_CHANNEL="$channel" +FLATCAR_VERSION="$release" +export FLATCAR_CHANNEL FLATCAR_VERSION + +rm -rf ./output/flatcar-"${channel}-${release}"-kube-* + +if [[ ${CAPI_PROVIDER} = "qemu" ]]; then + FLATCAR_MAKE_OPTS+="FLATCAR_CHANNEL=$channel FLATCAR_VERSION=$release " + FLATCAR_MAKE_OPTS+="SSH_PRIVATE_KEY_FILE=${VAGRANT_SSH_PRIVATE_KEY} " + FLATCAR_MAKE_OPTS+="SSH_PUBLIC_KEY_FILE=${VAGRANT_SSH_PUBLIC_KEY} " + + fetch_vagrant_ssh_keys + make ${FLATCAR_MAKE_OPTS} build-qemu-flatcar + run_vagrant +elif [[ ${CAPI_PROVIDER} = "aws" ]] || [[ ${CAPI_PROVIDER} = "ami" ]]; then + make ${FLATCAR_MAKE_OPTS} build-ami-flatcar +else + echo "Unknown CAPI_PROVIDER=${CAPI_PROVIDER}. exit." + exit 1 +fi + +exit 0 + +# vim:set sts=4 sw=4 et: diff --git a/hack/image-build-ova.py b/hack/image-build-ova.py new file mode 100755 index 0000000..162d6c0 --- /dev/null +++ b/hack/image-build-ova.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 + +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################################################################################ +# usage: image-build-ova.py [FLAGS] ARGS +# This program builds an OVA file from a VMDK and manifest file generated as a +# result of a Packer build. +################################################################################ + +import argparse +import hashlib +import io +import json +import os +import subprocess +from string import Template +import tarfile + + +def main(): + parser = argparse.ArgumentParser( + description="Builds an OVA using the artifacts from a Packer build") + parser.add_argument('--stream_vmdk', + dest='stream_vmdk', + action='store_true', + help='Compress vmdk file') + parser.add_argument('--vmx', + dest='vmx_version', + default='15', + help='The virtual hardware version') + parser.add_argument('--eula_file', + nargs='?', + metavar='EULA', + default='./ovf_eula.txt', + help='Text file containing EULA') + parser.add_argument('--ovf_template', + nargs='?', + metavar='OVF_TEMPLATE', + default='./ovf_template.xml', + help='XML template to build OVF') + parser.add_argument('--vmdk_file', + nargs='?', + metavar='FILE', + default=None, + help='Use FILE as VMDK instead of reading from manifest. ' + 'Must be in BUILD_DIR') + parser.add_argument(dest='build_dir', + nargs='?', + metavar='BUILD_DIR', + default='.', + help='The Packer build directory') + args = parser.parse_args() + + # Read in the EULA + eula = "" + with io.open(args.eula_file, 'r', encoding='utf-8') as f: + eula = f.read() + + # Read in the OVF template + ovf_template = "" + with io.open(args.ovf_template, 'r', encoding='utf-8') as f: + ovf_template = f.read() + + # Change the working directory if one is specified. + os.chdir(args.build_dir) + print("image-build-ova: cd %s" % args.build_dir) + + # Load the packer manifest JSON + data = None + with open('packer-manifest.json', 'r') as f: + data = json.load(f) + + # Get the first build. + build = data['builds'][0] + build_data = build['custom_data'] + + print("image-build-ova: loaded %s-kube-%s" % (build_data['build_name'], + build_data['kubernetes_semver'])) + + if args.vmdk_file is None: + # Get a list of the VMDK files from the packer manifest. + vmdk_files = get_vmdk_files(build['files']) + else: + vmdk_files = [{"name": args.vmdk_file, "size": os.path.getsize(args.vmdk_file)}] + + # Create stream-optimized versions of the VMDK files. + if args.stream_vmdk is True: + stream_optimize_vmdk_files(vmdk_files) + else: + for f in vmdk_files: + f['stream_name'] = f['name'] + f['stream_size'] = os.path.getsize(f['name']) + + # TODO(akutz) Support multiple VMDK files in the OVF/OVA + vmdk = vmdk_files[0] + + OS_id_map = {"vmware-photon-64": {"id": "36", "version": "", "type": "vmwarePhoton64Guest"}, + "centos7-64": {"id": "107", "version": "7", "type": "centos7-64"}, + "centos8-64": {"id": "107", "version": "8", "type": "centos8-64"}, + "rhel7-64": {"id": "80", "version": "7", "type": "rhel7_64guest"}, + "rhel8-64": {"id": "80", "version": "8", "type": "rhel8_64guest"}, + "ubuntu-64": {"id": "94", "version": "", "type": "ubuntu64Guest"}, + "flatcar-64": {"id": "100", "version": "", "type": "linux-64"}, + "Windows2019Server-64": {"id": "112", "version": "", "type": "windows9srv-64"}, + "Windows2004Server-64": {"id": "112", "version": "", "type": "windows9srv-64"}} + + # Create the OVF file. + data = { + 'BUILD_DATE': build_data['build_date'], + 'ARTIFACT_ID': build['artifact_id'], + 'BUILD_TIMESTAMP': build_data['build_timestamp'], + 'EULA': eula, + 'OS_NAME': build_data['os_name'], + 'OS_ID': OS_id_map[build_data['guest_os_type']]['id'], + 'OS_TYPE': OS_id_map[build_data['guest_os_type']]['type'], + 'OS_VERSION': OS_id_map[build_data['guest_os_type']]['version'], + 'IB_VERSION': build_data['ib_version'], + 'DISK_NAME': vmdk['stream_name'], + 'DISK_SIZE': build_data['disk_size'], + 'POPULATED_DISK_SIZE': vmdk['size'], + 'STREAM_DISK_SIZE': vmdk['stream_size'], + 'VMX_VERSION': args.vmx_version, + 'DISTRO_NAME': build_data['distro_name'], + 'DISTRO_VERSION': build_data['distro_version'], + 'DISTRO_ARCH': build_data['distro_arch'], + 'NESTEDHV': "false", + 'FIRMWARE': build_data['firmware'] + } + + capv_url = "https://github.com/kubernetes-sigs/cluster-api-provider-vsphere" + + data['CNI_VERSION'] = build_data['kubernetes_cni_semver'] + data['CONTAINERD_VERSION'] = build_data['containerd_version'] + data['KUBERNETES_SEMVER'] = build_data['kubernetes_semver'] + data['KUBERNETES_SOURCE_TYPE'] = build_data['kubernetes_source_type'] + data['PRODUCT'] = "%s and Kubernetes %s" % ( + build_data['os_name'], build_data['kubernetes_semver']) + data['ANNOTATION'] = "Cluster API vSphere image - %s - %s" % (data['PRODUCT'], capv_url) + data['WAKEONLANENABLED'] = "false" + data['TYPED_VERSION'] = build_data['kubernetes_typed_version'] + + data['PROPERTIES'] = Template(''' + + + + + + + \n''').substitute(data) + + # Check if OVF_CUSTOM_PROPERTIES environment Variable is set. + # If so, load the json file & add the properties to the OVF + + if os.environ.get("OVF_CUSTOM_PROPERTIES"): + with open(os.environ.get("OVF_CUSTOM_PROPERTIES"), 'r') as f: + custom_properties = json.loads(f.read()) + if custom_properties: + for k, v in custom_properties.items(): + data['PROPERTIES'] = data['PROPERTIES'] + \ + f''' \n''' + + if "windows" in OS_id_map[build_data['guest_os_type']]['type']: + if build_data['disable_hypervisor'] != "true": + data['NESTEDHV'] = "true" + + ovf = "%s-%s.ovf" % (build_data['build_name'], data['TYPED_VERSION']) + mf = "%s-%s.mf" % (build_data['build_name'], data['TYPED_VERSION']) + ova = "%s-%s.ova" % (build_data['build_name'], data['TYPED_VERSION']) + + # Create OVF + create_ovf(ovf, data, ovf_template) + + if os.environ.get("IB_OVFTOOL"): + # Create the OVA. + create_ova(ova, ovf, ovftool_args=os.environ.get("IB_OVFTOOL_ARGS", "")) + + else: + # Create the OVA manifest. + create_ova_manifest(mf, [ovf, vmdk['stream_name']]) + + # Create the OVA + create_ova(ova, ovf, ova_files=[mf, vmdk['stream_name']]) + + +def sha256(path): + m = hashlib.sha256() + with open(path, 'rb') as f: + while True: + data = f.read(65536) + if not data: + break + m.update(data) + return m.hexdigest() + + +def create_ova(ova_path, ovf_path, ovftool_args=None, ova_files=None): + if ova_files is None: + cmd = f"ovftool {ovftool_args} {ovf_path} {ova_path}" + + print("image-build-ova: creating OVA from %s using ovftool" % + ovf_path) + subprocess.run(cmd.split(), check=True) + else: + infile_paths = [ovf_path] + infile_paths.extend(ova_files) + print("image-build-ova: creating OVA using tar") + with open(ova_path, 'wb') as f: + with tarfile.open(fileobj=f, mode='w|') as tar: + for infile_path in infile_paths: + tar.add(infile_path) + + chksum_path = "%s.sha256" % ova_path + print("image-build-ova: create ova checksum %s" % chksum_path) + with open(chksum_path, 'w') as f: + f.write(sha256(ova_path)) + + +def create_ovf(path, data, ovf_template): + print("image-build-ova: create ovf %s" % path) + with io.open(path, 'w', encoding='utf-8') as f: + f.write(Template(ovf_template).substitute(data)) + + +def create_ova_manifest(path, infile_paths): + print("image-build-ova: create ova manifest %s" % path) + with open(path, 'w') as f: + for i in infile_paths: + f.write('SHA256(%s)= %s\n' % (i, sha256(i))) + + +def get_vmdk_files(inlist): + outlist = [] + for f in inlist: + if f['name'].endswith('.vmdk'): + outlist.append(f) + return outlist + + +def stream_optimize_vmdk_files(inlist): + for f in inlist: + infile = f['name'] + outfile = infile.replace('.vmdk', '.ova.vmdk', 1) + if os.path.isfile(outfile): + os.remove(outfile) + args = [ + 'vmware-vdiskmanager', + '-r', infile, + '-t', '5', + outfile + ] + print("image-build-ova: stream optimize %s --> %s (1-2 minutes)" % + (infile, outfile)) + subprocess.check_call(args) + f['stream_name'] = outfile + f['stream_size'] = os.path.getsize(outfile) + + +if __name__ == "__main__": + main() diff --git a/hack/image-govc-cloudinit.sh b/hack/image-govc-cloudinit.sh new file mode 100755 index 0000000..c5973ad --- /dev/null +++ b/hack/image-govc-cloudinit.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################################################################################ +# usage: image-govc-cloudinit.sh VM +# This program updates a remote VM with the cloud-init data to ready it for +# testing. This program requires a configured govc. +################################################################################ + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +if [ "${#}" -lt "1" ]; then + echo "usage: ${0} VM" 1>&2 + exit 1 +fi + +if ! command -v govc >/dev/null 2>&1; then + echo "govc binary must be in \$PATH" 1>&2 + exit 1 +fi + +export GOVC_VM="${1-}" + +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +# If the VM has a "new" snapshot then revert to it and delete all other +# snapshots. +snapshots="$(govc snapshot.tree 2>/dev/null)" || true +if [[ ${snapshots} = *new* ]]; then + echo "image-govc-cloudinit: reverting to snapshot 'new'" + govc snapshot.revert new + for s in ${snapshots}; do + if [ "${s}" != "new" ] && [ "${s}" != "." ] ; then + echo "image-govc-cloudinit: removing snapshot '${s}'" + govc snapshot.remove "${s}" + fi + done +else + echo "image-govc-cloudinit: creating snapshot 'new'" + govc snapshot.create new +fi + +echo "image-govc-cloudinit: initializing cloud-init data" +govc vm.change \ + -e "guestinfo.userdata.encoding=base64" \ + -e "guestinfo.metadata.encoding=base64" \ + -e "guestinfo.userdata='$(base64 -w0 = 200 and r.status_code <= 299: + return url + except: + pass + # The URL wasn't valid, so add '.txt' to the end and let's see if the + # URL points to a valid build. + url = f'{url}.txt' + + # Do an HTTP GET on the txt file to get the actual Kubernetes version. + version = requests.get(url).text + version = version.strip() + + if buildID.startswith('ci/'): + version = f'ci/{version}' + url = f'{KUBE_SRC}/{version}' + + return url + + def __read_version_from_kube_tarball(self, url): + url = f'{url}/kubernetes.tar.gz' + r = requests.get(url) + if not r.status_code == 200: + raise Exception(f'HTTP GET {url} failed: {r.status_code}') + b = BytesIO(r.content) + t = tarfile.open(fileobj=b, mode='r') + v = t.extractfile('kubernetes/version') + return v.read().strip().decode('utf-8') + + +if __name__ == '__main__': + import argparse + import textwrap + parser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter, + description='Generates new Kubernetes image config', + epilog=textwrap.dedent(r''' + THE VERSION STRING + ==================================================================== + The version string not only determines what version of Kubernetes + will be installed, but also *how* Kubernetes is installed. + + PLACEHOLDERS + ==================================================================== + The following placeholders are used in the examples below: + + BASE_URI https://dl.k8s.io + K8S_TGZ kubernetes.tar.gz + + PACKAGE MANAGER INSTALLATION + ==================================================================== + If the version string matches the pattern "^\d+\.\d+.\d+\-\d+$", + ex. 1.14.0-0, then Kubernetes is installed using the system's + package manager, such as yum or apt. + + MANUAL INSTALLATION + ==================================================================== + If the string does not match the above pattern then the following + logic is used to resolve the string VALUE to a valid Kubernetes + version along with the URLs for the Kubernetes artifiacts required + for installation: + + 1. If the VALUE begins with "http:" or "https:" then the value is + treated as a URL and the Kubernetes version is obtained by reading + the "kuberentes/version" file from the URL "VALUE/K8S_TGZ". + + 2. If the VALUE matches a semantic version then the value is + treated as a release build and "BASE_URI/release/SEMVER" + is processed like the URL in step one. + + 3. If the VALUE begins with "ci/" then: + + a. If VALUE does not end with ".txt" then a HEAD request is used + to check the existence of "BASE_URI/VALUE/K8S_TGZ": + + i. If the HEAD request is successful then the URL is processed + like the one in step one. + ii. If the HEAD request fails then ".txt" is added to the end + of the URL and is processed by step 3b. + + b. If VALUE *does* end with ".txt" then a GET request is used to + read "BASE_URI/VALUE" in order to get the dereferenced + version string. Then "BASE_URI/ci/DEREF" is processed + like the URL in step one. + + 4. If the VALUE begins with "release/" then the VALUE is processed + like step three, without the "ci/" prefix + + The resolved URL is used to install Kuberentes from the set of + pre-built container images and binaries. + ''')) + parser.add_argument('version', + nargs=1, + help='A Kubernetes version string') + + args = parser.parse_args() + resolver = KubeVersionResolver() + result = resolver.Resolve(args.version[0]) + + data = json.dumps(result, indent=2) + print(data) diff --git a/hack/image-post-create-config.sh b/hack/image-post-create-config.sh new file mode 100755 index 0000000..9582b66 --- /dev/null +++ b/hack/image-post-create-config.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################################################################################ +# usage: image-post-create-config.sh BUILD_DIR +# This program runs after a new image is created and: +# 1. Creates a snapshot of the image named "new" +# 2. Modifies the image to use 2 vCPU +# 3. Creates a snapshot of the image named "2cpu" +# 4. Attaches the ISO build/images/cloudinit/cidata.iso +################################################################################ + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +if [ "${#}" -ne "1" ]; then + echo "usage: ${0} BUILD_DIR" 1>&2 + exit 1 +fi + +VM_RUN="${VM_RUN:-$(command -v vmrun 2>/dev/null)}" +if [ ! -e "${VM_RUN}" ] || [ ! -x "${VM_RUN}" ]; then + echo "vmrun must be in \$PATH or specified by \$VM_RUN" 1>&2 + exit 1 +fi +VM_RUN_DIR="$(dirname "${VM_RUN}")" +export PATH="${VM_RUN_DIR}:${PATH}" + +# Get the path of the VMX file. +VMX_FILE=$(/bin/ls "${1-}"/*.vmx) + +create_snapshot() { + snapshots="$(vmrun listSnapshots "${VMX_FILE}" 2>/dev/null)" + if [[ ${snapshots} = *${1-}* ]]; then + echo "image-post-create-config: skip snapshot '${1-}'; already exists" + else + echo "image-post-create-config: create snapshot '${1-}'" + vmrun snapshot "${VMX_FILE}" "${1-}" + fi +} + +create_snapshot new + +if grep -q 'numvcpus = "2"' "${VMX_FILE}"; then + echo "image-post-create-config: skipping cpu update; already 2" +else + echo "image-post-create-config: update cpu count to 2" + sed -i.bak -e 's/numvcpus = "1"/numvcpus = "2"/' -e 's/cpuid.corespersocket = "1"/cpuid.corespersocket = "2"/' "${VMX_FILE}" + create_snapshot 2cpu +fi + +if grep -q 'guestinfo.userdata' "${VMX_FILE}"; then + echo "image-post-create-config: skipping cloud-init data; already exists" +else + echo "image-post-create-config: insert cloud-init data" + CIDATA_DIR="$(dirname "${BASH_SOURCE[0]}")/../cloudinit" + cat <>"${VMX_FILE}" +guestinfo.userdata = "$({ base64 -w0 || base64; } 2>/dev/null <"${CIDATA_DIR}/user-data")" +guestinfo.userdata.encoding = "base64" +guestinfo.metadata = "$({ base64 -w0 || base64; } 2>/dev/null <"${CIDATA_DIR}/meta-data")" +guestinfo.metadata.encoding = "base64" +EOF + create_snapshot cloudinit +fi diff --git a/hack/image-ssh.sh b/hack/image-ssh.sh new file mode 100755 index 0000000..7a8e442 --- /dev/null +++ b/hack/image-ssh.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################################################################################ +# usage: image-ssh.sh BUILD_DIR [SSH_USER] +# This program uses SSH to connect to an image running locally in VMware +# Workstation or VMware Fusion. +################################################################################ + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +if [ "${#}" -lt "1" ]; then + echo "usage: ${0} BUILD_DIR [SSH_USER]" 1>&2 + exit 1 +fi + +VM_RUN="${VM_RUN:-$(command -v vmrun 2>/dev/null)}" +if [ ! -e "${VM_RUN}" ] || [ ! -x "${VM_RUN}" ]; then + echo "vmrun must be in \$PATH or specified by \$VM_RUN" 1>&2 + exit 1 +fi +VM_RUN_DIR="$(dirname "${VM_RUN}")" +export PATH="${VM_RUN_DIR}:${PATH}" + +# Get the path of the VMX file. +VMX_FILE=$(/bin/ls "${1-}"/*.vmx) + +# Get the SSH user. +SSH_USER="${SSH_USER:-${2-}}" +if [ -z "${SSH_USER}" ]; then + SSH_USER=builder +fi +if [ -z "${SSH_USER}" ]; then + echo "SSH_USER is required" 1>&2 + exit 1 +fi + +# Get the VM's IP address. +IP_ADDR="$(vmrun getGuestIPAddress "${VMX_FILE}")" + +# SSH into the VM with the provided user. +SSH_KEY="$(dirname "${BASH_SOURCE[0]}")/../cloudinit/id_rsa.capi" +echo "image-ssh: ssh -i ${SSH_KEY} ${SSH_USER}@${IP_ADDR}" +exec ssh -o UserKnownHostsFile=/dev/null -i "${SSH_KEY}" "${SSH_USER}"@"${IP_ADDR}" diff --git a/hack/image-upload.py b/hack/image-upload.py new file mode 100755 index 0000000..bf856c5 --- /dev/null +++ b/hack/image-upload.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################################################################################ +# usage: image-upload.py [FLAGS] ARGS +# This program uploads an OVA created from a Packer build +################################################################################ + +import argparse +import atexit +import hashlib +import json +import os +import re +import requests +import subprocess +import string +import sys + + +def main(): + parser = argparse.ArgumentParser( + description="Uploads an OVA created from a Packer build") + parser.add_argument(dest='build_dir', + nargs='?', + metavar='BUILD_DIR', + default='.', + help='The Packer build directory') + parser.add_argument('--key-file', + dest='key_file', + required=True, + nargs='?', + metavar='KEY_FILE', + help='The GCS key file') + args = parser.parse_args() + + # Get the absolute path to the GCS key file. + key_file = os.path.abspath(args.key_file) + + # Change the working directory if one is specified. + os.chdir(args.build_dir) + print("image-upload-ova: cd %s" % args.build_dir) + + # Load the packer manifest JSON + data = None + with open('packer-manifest.json', 'r') as f: + data = json.load(f) + + # Get the first build. + build = data['builds'][0] + build_data = build['custom_data'] + + version = build_data['kubernetes_semver'] + build_name = "%s-kube-%s" % (build_data['build_name'], version) + print("image-upload-ova: loaded %s" % build_name) + + # Get the OVA and its checksum. + ova = "%s.ova" % build_name + ova_sum = "%s.sha256" % ova + + # Determine whether or not this is a release or CI image. + upload_dir = 'ci' + if re.match(r'^v?\d+\.\d+\.\d+(-\d+)?$', version): + upload_dir = 'release' + + # Get the path to the GCS OVA and its checksum. + gcs_ova = "gs://capv-images/%s/%s/%s" % ( + upload_dir, version, ova) + gcs_ova_sum = "%s.sha256" % gcs_ova + + # Get the URL of the OVA and its checksum. + url_ova = string.replace(gcs_ova, "gs://", "http://storage.googleapis.com/") + url_ova_sum = "%s.sha256" % url_ova + + # Compare the remote checksum with the local checksum. + lcl_ova_sum_val = get_local_checksum(ova_sum) + print("image-upload-ova: local sha256 %s" % lcl_ova_sum_val) + rem_ova_sum_val = get_remote_checksum(url_ova_sum) + print("image-upload-ova: remote sha256 %s" % rem_ova_sum_val) + if lcl_ova_sum_val == rem_ova_sum_val: + print("image-upload-ova: skipping upload") + print("image-upload-ova: download from %s" % url_ova) + return + + # Activate the GCS service account. + activate_service_account(key_file) + atexit.register(deactivate_service_account) + + # Upload the OVA and its checksum. + print("image-upload-ova: upload %s" % gcs_ova) + subprocess.check_call(['gsutil', 'cp', ova, gcs_ova]) + print("image-upload-ova: upload %s" % gcs_ova_sum) + subprocess.check_call(['gsutil', 'cp', ova_sum, gcs_ova_sum]) + + print("image-upload-ova: download from %s" % url_ova) + + +def activate_service_account(path): + args = [ + "gcloud", "auth", + "activate-service-account", + "--key-file", path, + ] + subprocess.check_call(args) + + +def deactivate_service_account(): + subprocess.call(["gcloud", "auth", "revoke"]) + + +def get_remote_checksum(url): + r = requests.get(url) + if r.status_code >= 200 and r.status_code <= 299: + return r.text.strip() + return None + + +def get_local_checksum(path): + with open(path, 'r') as f: + return f.readline().strip() + + +if __name__ == "__main__": + main() diff --git a/hack/ovf_eula.txt b/hack/ovf_eula.txt new file mode 100644 index 0000000..f93d5c2 --- /dev/null +++ b/hack/ovf_eula.txt @@ -0,0 +1,273 @@ +VMWARE END USER LICENSE AGREEMENT + + + +PLEASE NOTE THAT THE TERMS OF THIS END USER LICENSE AGREEMENT SHALL GOVERN YOUR USE OF THE SOFTWARE, REGARDLESS OF ANY TERMS THAT MAY APPEAR DURING THE INSTALLATION OF THE SOFTWARE. + +IMPORTANT-READ CAREFULLY: BY DOWNLOADING, INSTALLING, OR USING THE SOFTWARE, YOU (THE INDIVIDUAL OR LEGAL ENTITY) AGREE TO BE BOUND BY THE TERMS OF THIS END USER LICENSE AGREEMENT ("EULA"). IF YOU DO NOT AGREE TO THE TERMS OF THIS EULA, YOU MUST NOT DOWNLOAD, INSTALL, OR USE THE SOFTWARE, AND YOU MUST DELETE OR RETURN THE UNUSED SOFTWARE TO THE VENDOR FROM WHICH YOU ACQUIRED IT WITHIN THIRTY (30) DAYS AND REQUEST A REFUND OF THE LICENSE FEE, IF ANY, THAT YOU PAID FOR THE SOFTWARE. + +EVALUATION LICENSE. If You are licensing the Software for evaluation purposes, Your use of the Software is only permitted in a non-production environment and for the period limited by the License Key. Notwithstanding any other provision in this EULA, an Evaluation License of the Software is provided "AS-IS" without indemnification, support or warranty of any kind, expressed or implied. + + + +1. DEFINITIONS. + + + +1.1. "Affiliate" means, with respect to a party at a given time, an entity that then is directly or indirectly controlled by, is under common control with, or controls that party, and here "control" means an ownership, voting or similar interest representing fifty percent (50%) or more of the total interests then outstanding of that entity. + + + +1.2. "Documentation" means that documentation that is generally provided to You by VMware with the Software, as revised by VMware from time to time, and which may include end user manuals, operation instructions, installation guides, release notes, and on-line help files regarding the use of the Software. + + + +1.3. "Guest Operating Systems" means instances of third-party operating systems licensed by You, installed in a Virtual Machine and run using the Software. + + + +1.4. "Intellectual Property Rights" means all worldwide intellectual property rights, including without limitation, copyrights, trademarks, service marks, trade secrets, know how, inventions, patents, patent applications, moral rights and all other proprietary rights, whether registered or unregistered. + + + +1.5. "License" means a license granted under Section 2.1 (General License Grant). + + + +1.6. "License Key" means a serial number that enables You to activate and use the Software. + + + +1.7. "License Term" means the duration of a License as specified in the Order. + + + +1.8. "License Type" means the type of License applicable to the Software, as more fully described in the Order. + + + +1.9. "Open Source Software" or "OSS" means software components embedded in the Software and provided under separate license terms, which can be found either in the open_source_licenses.txt file (or similar file) provided within the Software or at www.vmware.com/download/open_source.html. + + + +1.10. "Order" means a purchase order, enterprise license agreement, or other ordering document issued by You to VMware or a VMware authorized reseller that references and incorporates this EULA and is accepted by VMware as set forth in Section 4 (Order). + + + +1.11. "Product Guide" means the current version of the VMware Product Guide at the time of Your Order, copies of which are found at www.vmware.com/download/eula. + + + +1.12. "Support Services Terms" means VMware's then-current support policies, copies of which are posted at www.vmware.com/support/policies. + + + +1.13. "Software" means the VMware Tools and the VMware computer programs listed on VMware's commercial price list to which You acquire a license under an Order, together with any software code relating to the foregoing that is provided to You pursuant to a support and subscription service contract and that is not subject to a separate license agreement. + + + +1.14. "Territory" means the country or countries in which You have been invoiced; provided, however, that if You have been invoiced within any of the European Economic Area member states, You may deploy the corresponding Software throughout the European Economic Area. + + + +1.15. "Third Party Agent" means a third party delivering information technology services to You pursuant to a written contract with You. + + + +1.16. "Virtual Machine" means a software container that can run its own operating system and execute applications like a physical machine. + + + +1.17. "VMware" means VMware, Inc., a Delaware corporation, if You are purchasing Licenses or services for use in the United States and VMware International Unlimited Company , a company organized and existing under the laws of Ireland, for all other purchases. + + + +1.18. "VMware Tools" means the suite of utilities and drivers, Licensed by VMware under the "VMware Tools" name, that can be installed in a Guest Operating System to enhance the performance and functionality of a Guest Operating System when running in a Virtual Machine. + + + +2. LICENSE GRANT. + + + +2.1. General License Grant. VMware grants to You a non-exclusive, non-transferable (except as set forth in Section 12.1 (Transfers; Assignment)) license to use the Software and the Documentation during the period of the license and within the Territory, solely for Your internal business operations, and subject to the provisions of the Product Guide. Unless otherwise indicated in the Order, licenses granted to You will be perpetual, will be for use of object code only, and will commence on either delivery of the physical media or the date You are notified of availability for electronic download. + + + +2.2. Third Party Agents. Under the License granted to You in Section 2.1 (General License Grant) above, You may permit Your Third Party Agents to access, use and/or operate the Software on Your behalf for the sole purpose of delivering services to You, provided that You will be fully responsible for Your Third Party Agents' compliance with terms and conditions of this EULA and any breach of this EULA by a Third Party Agent shall be deemed to be a breach by You. + + + +2.3. Copying Permitted. You may copy the Software and Documentation as necessary to install and run the quantity of copies licensed, but otherwise for archival purposes only. + + + +2.4. Benchmarking. You may use the Software to conduct internal performance testing and benchmarking studies. You may only publish or otherwise distribute the results of such studies to third parties as follows: (a) if with respect to VMware's Workstation or Fusion products, only if You provide a copy of Your study to benchmark@vmware.com prior to distribution; (b) if with respect to any other Software, only if VMware has reviewed and approved of the methodology, assumptions and other parameters of the study (please contact VMware at benchmark@vmware.com to request such review and approval) prior to such publication and distribution. + + + +2.5. VMware Tools. You may distribute the VMware Tools to third parties solely when installed in a Guest Operating System within a Virtual Machine. You are liable for compliance by those third parties with the terms and conditions of this EULA. + + + +2.6. Open Source Software. Notwithstanding anything herein to the contrary, Open Source Software is licensed to You under such OSS's own applicable license terms, which can be found in the open_source_licenses.txt file, the Documentation or as applicable, the corresponding source files for the Software available at www.vmware.com/download/open_source.html. These OSS license terms are consistent with the license granted in Section 2 (License Grant), and may contain additional rights benefiting You. The OSS license terms shall take precedence over this EULA to the extent that this EULA imposes greater restrictions on You than the applicable OSS license terms. To the extent the license for any Open Source Software requires VMware to make available to You the corresponding source code and/or modifications (the "Source Files"), You may obtain a copy of the applicable Source Files from VMware's website at www.vmware.com/download/open_source.html or by sending a written request, with Your name and address to: VMware, Inc., 3401 Hillview Avenue, Palo Alto, CA 94304, United States of America. All requests should clearly specify: Open Source Files Request, Attention: General Counsel. This offer to obtain a copy of the Source Files is valid for three years from the date You acquired this Software. + + + +3. RESTRICTIONS; OWNERSHIP. + + + +3.1. License Restrictions. Without VMware's prior written consent, You must not, and must not allow any third party to: (a) use Software in an application services provider, service bureau, or similar capacity for third parties, except that You may use the Software to deliver hosted services to Your Affiliates; (b) disclose to any third party the results of any benchmarking testing or comparative or competitive analyses of VMware's Software done by or on behalf of You, except as specified in Section 2.4 (Benchmarking); (c) make available Software in any form to anyone other than Your employees or contractors reasonably acceptable to VMware and require access to use Software on behalf of You in a matter permitted by this EULA, except as specified in Section 2.2 (Third Party Agents); (d) transfer or sublicense Software or Documentation to an Affiliate or any third party, except as expressly permitted in Section 12.1 (Transfers; Assignment); (e) use Software in conflict with the terms and restrictions of the Software's licensing model and other requirements specified in Product Guide and/or VMware quote; (f) except to the extent permitted by applicable mandatory law, modify, translate, enhance, or create derivative works from the Software, or reverse engineer, decompile, or otherwise attempt to derive source code from the Software, except as specified in Section 3.2 (Decompilation); (g) remove any copyright or other proprietary notices on or in any copies of Software; or (h) violate or circumvent any technological restrictions within the Software or specified in this EULA, such as via software or services. + + + +3.2. Decompilation. Notwithstanding the foregoing, decompiling the Software is permitted to the extent the laws of the Territory give You the express right to do so to obtain information necessary to render the Software interoperable with other software; provided, however, You must first request such information from VMware, provide all reasonably requested information to allow VMware to assess Your claim, and VMware may, in its discretion, either provide such interoperability information to You, impose reasonable conditions, including a reasonable fee, on such use of the Software, or offer to provide alternatives to ensure that VMware's proprietary rights in the Software are protected and to reduce any adverse impact on VMware's proprietary rights. + + + +3.3. Ownership. The Software and Documentation, all copies and portions thereof, and all improvements, enhancements, modifications and derivative works thereof, and all Intellectual Property Rights therein, are and shall remain the sole and exclusive property of VMware and its licensors. Your rights to use the Software and Documentation shall be limited to those expressly granted in this EULA and any applicable Order. No other rights with respect to the Software or any related Intellectual Property Rights are implied. You are not authorized to use (and shall not permit any third party to use) the Software, Documentation or any portion thereof except as expressly authorized by this EULA or the applicable Order. VMware reserves all rights not expressly granted to You. VMware does not transfer any ownership rights in any Software. + + + +3.4. Guest Operating Systems. Certain Software allows Guest Operating Systems and application programs to run on a computer system. You acknowledge that You are responsible for obtaining and complying with any licenses necessary to operate any such third-party software. + + + +4. ORDER. Your Order is subject to this EULA. No Orders are binding on VMware until accepted by VMware. Orders for Software are deemed to be accepted upon VMware's delivery of the Software included in such Order. Orders issued to VMware do not have to be signed to be valid and enforceable. + + + +5. RECORDS AND AUDIT. During the License Term for Software and for two (2) years after its expiration or termination, You will maintain accurate records of Your use of the Software sufficient to show compliance with the terms of this EULA. During this period, VMware will have the right to audit Your use of the Software to confirm compliance with the terms of this EULA. That audit is subject to reasonable notice by VMware and will not unreasonably interfere with Your business activities. VMware may conduct no more than one (1) audit in any twelve (12) month period, and only during normal business hours. You will reasonably cooperate with VMware and any third party auditor and will, without prejudice to other rights of VMware, address any non-compliance identified by the audit by promptly paying additional fees. You will promptly reimburse VMware for all reasonable costs of the audit if the audit reveals either underpayment of more than five (5%) percent of the Software fees payable by You for the period audited, or that You have materially failed to maintain accurate records of Software use. + + + +6. SUPPORT AND SUBSCRIPTION SERVICES. Except as expressly specified in the Product Guide, VMware does not provide any support or subscription services for the Software under this EULA. You have no rights to any updates, upgrades or extensions or enhancements to the Software developed by VMware unless you separately purchase VMware support or subscription services. These support or subscription services are subject to the Support Services Terms. + + + +7. WARRANTIES. + + + +7.1. Software Warranty, Duration and Remedy. VMware warrants to You that the Software will, for a period of ninety (90) days following notice of availability for electronic download or delivery ("Warranty Period"), substantially conform to the applicable Documentation, provided that the Software: (a) has been properly installed and used at all times in accordance with the applicable Documentation; and (b) has not been modified or added to by persons other than VMware or its authorized representative. VMware will, at its own expense and as its sole obligation and Your exclusive remedy for any breach of this warranty, either replace that Software or correct any reproducible error in that Software reported to VMware by You in writing during the Warranty Period. If VMware determines that it is unable to correct the error or replace the Software, VMware will refund to You the amount paid by You for that Software, in which case the License for that Software will terminate. + + + +7.2. Software Disclaimer of Warranty. OTHER THAN THE WARRANTY ABOVE, AND TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, VMWARE AND ITS SUPPLIERS MAKE NO OTHER EXPRESS WARRANTIES UNDER THIS EULA, AND DISCLAIM ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT, AND ANY WARRANTY ARISING BY STATUTE, OPERATION OF LAW, COURSE OF DEALING OR PERFORMANCE, OR USAGE OF TRADE. VMWARE AND ITS LICENSORS DO NOT WARRANT THAT THE SOFTWARE WILL OPERATE UNINTERRUPTED OR THAT IT WILL BE FREE FROM DEFECTS OR THAT IT WILL MEET YOUR REQUIREMENTS. + + + +8. INTELLECTUAL PROPERTY INDEMNIFICATION. + + + +8.1. Defense and Indemnification. Subject to the remainder of this Section 8 (Intellectual Property Indemnification), VMware shall defend You against any third party claim that the Software infringes any patent, trademark or copyright of such third party, or misappropriates a trade secret (but only to the extent that the misappropriation is not a result of Your actions) under the laws of: (a) the United States and Canada; (b) the European Economic Area; (c) Australia; (d) New Zealand; (e) Japan; or (f) the People's Republic of China, to the extent that such countries are part of the Territory for the License ("Infringement Claim") and indemnify You from the resulting costs and damages finally awarded against You to such third party by a court of competent jurisdiction or agreed to in settlement. The foregoing obligations are applicable only if You: (i) promptly notify VMware in writing of the Infringement Claim; (ii) allow VMware sole control over the defense for the claim, any settlement negotiations and any related action challenging the validity of the allegedly infringed patent, trademark, or copyright; and (iii) reasonably cooperate in response to VMware requests for assistance. You may not settle or compromise any Infringement Claim without the prior written consent of VMware. + + + +8.2. Remedies. If the alleged infringing Software become, or in VMware's opinion be likely to become, the subject of an Infringement Claim, VMware will, at VMware's option and expense, do one of the following: (a) procure the rights necessary for You to make continued use of the affected Software; (b) replace or modify the affected Software to make it non-infringing; or (c) terminate the License to the affected Software and discontinue the related support services, and, upon Your certified deletion of the affected Software, refund: (i) the fees paid by You for the License to the affected Software, less straight-line depreciation over a three (3) year useful life beginning on the date such Software was delivered; and (ii) any pre-paid service fee attributable to related support services to be delivered after the date such service is stopped. Nothing in this Section 8.2 (Remedies) shall limit VMware's obligation under Section 8.1 (Defense and Indemnification) to defend and indemnify You, provided that You replace the allegedly infringing Software upon VMware's making alternate Software available to You and/or You discontinue using the allegedly infringing Software upon receiving VMware's notice terminating the affected License. + + + +8.3. Exclusions. Notwithstanding the foregoing, VMware will have no obligation under this Section 8 (Intellectual Property Indemnification) or otherwise with respect to any claim based on: (a) a combination of Software with non-VMware products (other than non-VMware products that are listed on the Order and used in an unmodified form); (b) use for a purpose or in a manner for which the Software was not designed; (c) use of any older version of the Software when use of a newer VMware version would have avoided the infringement; (d) any modification to the Software made without VMware's express written approval; (e) any claim that relates to open source software or freeware technology or any derivatives or other adaptations thereof that is not embedded by VMware into Software listed on VMware's commercial price list; or (f) any Software provided on a no charge, beta or evaluation basis. THIS SECTION 8 (INTELLECTUAL PROPERTY INDEMNIFICATION) STATES YOUR SOLE AND EXCLUSIVE REMEDY AND VMWARE'S ENTIRE LIABILITY FOR ANY INFRINGEMENT CLAIMS OR ACTIONS. + + + +9. LIMITATION OF LIABILITY. + + + +9.1. Limitation of Liability. TO THE MAXIMUM EXTENT MANDATED BY LAW, IN NO EVENT WILL VMWARE AND ITS LICENSORS BE LIABLE FOR ANY LOST PROFITS OR BUSINESS OPPORTUNITIES, LOSS OF USE, LOSS OF REVENUE, LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOSS OF DATA, OR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES UNDER ANY THEORY OF LIABILITY, WHETHER BASED IN CONTRACT, TORT, NEGLIGENCE, PRODUCT LIABILITY, OR OTHERWISE. BECAUSE SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE PRECEDING LIMITATION MAY NOT APPLY TO YOU. VMWARE'S AND ITS LICENSORS' LIABILITY UNDER THIS EULA WILL NOT, IN ANY EVENT, REGARDLESS OF WHETHER THE CLAIM IS BASED IN CONTRACT, TORT, STRICT LIABILITY, OR OTHERWISE, EXCEED THE GREATER OF THE LICENSE FEES YOU PAID FOR THE SOFTWARE GIVING RISE TO THE CLAIM OR $5000. THE FOREGOING LIMITATIONS SHALL APPLY REGARDLESS OF WHETHER VMWARE OR ITS LICENSORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND REGARDLESS OF WHETHER ANY REMEDY FAILS OF ITS ESSENTIAL PURPOSE. + + + +9.2. Further Limitations. VMware's licensors shall have no liability of any kind under this EULA and VMware's liability with respect to any third party software embedded in the Software shall be subject to Section 9.1 (Limitation of Liability). You may not bring a claim under this EULA more than eighteen (18) months after the cause of action arises. + + + +10. TERMINATION. + + + +10.1. EULA Term. The term of this EULA begins on the notice of availability for electronic download or delivery of the Software and continues until this EULA is terminated in accordance with this Section 10. + + + +10.2. Termination for Breach. VMware may terminate this EULA effective immediately upon written notice to You if: (a) You fail to pay any portion of the fees under an applicable Order within ten (10) days after receiving written notice from VMware that payment is past due; or (b) You breach any other provision of this EULA and fail to cure within thirty (30) days after receipt of VMware's written notice thereof. + + + +10.3. Termination for Insolvency. VMware may terminate this EULA effective immediately upon written notice to You if You: (a) terminate or suspend your business; (b) become insolvent, admit in writing Your inability to pay Your debts as they mature, make an assignment for the benefit of creditors; or become subject to control of a trustee, receiver or similar authority; or (c) become subject to any bankruptcy or insolvency proceeding. + + + +10.4. Effect of Termination. Upon VMware's termination of this EULA: (a) all Licensed rights to all Software granted to You under this EULA will immediately cease; and (b) You must cease all use of all Software, and return or certify destruction of all Software and License Keys (including copies) to VMware, and return, or if requested by VMware, destroy, any related VMware Confidential Information in Your possession or control and certify in writing to VMware that You have fully complied with these requirements. Any provision will survive any termination or expiration if by its nature and context it is intended to survive, including Sections 1 (Definitions), 2.6 (Open Source Software), 3 (Restrictions; Ownership), 5 (Records and Audit), 7.2 (Software Disclaimer of Warranty), 9 (Limitation of Liability), 10 (Termination), 11 (Confidential Information) and 12 (General). + + + +11. CONFIDENTIAL INFORMATION. + + + +11.1. Definition. "Confidential Information" means information or materials provided by one party ("Discloser") to the other party ("Recipient") which are in tangible form and labelled "confidential" or the like, or, information which a reasonable person knew or should have known to be confidential. The following information shall be considered Confidential Information whether or not marked or identified as such: (a) License Keys; (b) information regarding VMware's pricing, product roadmaps or strategic marketing plans; and (c) non-public materials relating to the Software. + + + +11.2. Protection. Recipient may use Confidential Information of Discloser; (a) to exercise its rights and perform its obligations under this EULA; or (b) in connection with the parties' ongoing business relationship. Recipient will not use any Confidential Information of Discloser for any purpose not expressly permitted by this EULA, and will disclose the Confidential Information of Discloser only to the employees or contractors of Recipient who have a need to know such Confidential Information for purposes of this EULA and who are under a duty of confidentiality no less restrictive than Recipient's duty hereunder. Recipient will protect Confidential Information from unauthorized use, access, or disclosure in the same manner as Recipient protects its own confidential or proprietary information of a similar nature but with no less than reasonable care. + + + +11.3. Exceptions. Recipient's obligations under Section 11.2 (Protection) with respect to any Confidential Information will terminate if Recipient can show by written records that such information: (a) was already known to Recipient at the time of disclosure by Discloser; (b) was disclosed to Recipient by a third party who had the right to make such disclosure without any confidentiality restrictions; (c) is, or through no fault of Recipient has become, generally available to the public; or (d) was independently developed by Recipient without access to, or use of, Discloser's Information. In addition, Recipient will be allowed to disclose Confidential Information to the extent that such disclosure is required by law or by the order of a court of similar judicial or administrative body, provided that Recipient notifies Discloser of such required disclosure promptly and in writing and cooperates with Discloser, at Discloser's request and expense, in any lawful action to contest or limit the scope of such required disclosure. + + + +11.4. Data Privacy. You agree that VMware may process technical and related information about Your use of the Software which may include internet protocol address, hardware identification, operating system, application software, peripheral hardware, and non-personally identifiable Software usage statistics to facilitate the provisioning of updates, support, invoicing or online services and may transfer such information to other companies in the VMware worldwide group of companies from time to time. To the extent that this information constitutes personal data, VMware shall be the controller of such personal data. To the extent that it acts as a controller, each party shall comply at all times with its obligations under applicable data protection legislation. + + + +12. GENERAL. + + + +12.1. Transfers; Assignment. Except to the extent transfer may not legally be restricted or as permitted by VMware's transfer and assignment policies, in all cases following the process set forth at www.vmware.com/support/policies/licensingpolicies.html, You will not assign this EULA, any Order, or any right or obligation herein or delegate any performance without VMware's prior written consent, which consent will not be unreasonably withheld. Any other attempted assignment or transfer by You will be void. VMware may use its Affiliates or other sufficiently qualified subcontractors to provide services to You, provided that VMware remains responsible to You for the performance of the services. + + + +12.2. Notices. Any notice delivered by VMware to You under this EULA will be delivered via mail, email or fax. + + + +12.3. Waiver. Failure to enforce a provision of this EULA will not constitute a waiver. + + + +12.4. Severability. If any part of this EULA is held unenforceable, the validity of all remaining parts will not be affected. + + + +12.5. Compliance with Laws; Export Control; Government Regulations. Each party shall comply with all laws applicable to the actions contemplated by this EULA. You acknowledge that the Software is of United States origin, is provided subject to the U.S. Export Administration Regulations, may be subject to the export control laws of the applicable territory, and that diversion contrary to applicable export control laws is prohibited. You represent that (1) you are not, and are not acting on behalf of, (a) any person who is a citizen, national, or resident of, or who is controlled by the government of any country to which the United States has prohibited export transactions; or (b) any person or entity listed on the U.S. Treasury Department list of Specially Designated Nationals and Blocked Persons, or the U.S. Commerce Department Denied Persons List or Entity List; and (2) you will not permit the Software to be used for, any purposes prohibited by law, including, any prohibited development, design, manufacture or production of missiles or nuclear, chemical or biological weapons. The Software and accompanying documentation are deemed to be "commercial computer software" and "commercial computer software documentation", respectively, pursuant to DFARS Section 227.7202 and FAR Section 12.212(b), as applicable. Any use, modification, reproduction, release, performing, displaying or disclosing of the Software and documentation by or for the U.S. Government shall be governed solely by the terms and conditions of this EULA. + + + +12.6. Construction. The headings of sections of this EULA are for convenience and are not to be used in interpreting this EULA. As used in this EULA, the word ‘including' means "including but not limited to". + + + +12.7. Governing Law. This EULA is governed by the laws of the State of California, United States of America (excluding its conflict of law rules), and the federal laws of the United States. To the extent permitted by law, the state and federal courts located in Santa Clara County, California will be the exclusive jurisdiction for disputes arising out of or in connection with this EULA. The U.N. Convention on Contracts for the International Sale of Goods does not apply. + + + +12.8. Third Party Rights. Other than as expressly set out in this EULA, this EULA does not create any rights for any person who is not a party to it, and no person who is not a party to this EULA may enforce any of its terms or rely on any exclusion or limitation contained in it. + + + +12.9. Order of Precedence. In the event of conflict or inconsistency among the Product Guide, this EULA and the Order, the following order of precedence shall apply unless otherwise set forth in an enterprise license agreement: (a) the Product Guide, (b) this EULA and (c) the Order. With respect to any inconsistency between this EULA and an Order, the terms of this EULA shall supersede and control over any conflicting or additional terms and conditions of any purchase order, acknowledgement or confirmation or other document issued by You. + + + +12.10. Entire Agreement. This EULA, including accepted Orders and any amendments hereto, and the Product Guide contain the entire agreement of the parties with respect to the subject matter of this EULA and supersede all previous or contemporaneous communications, representations, proposals, commitments, understandings and agreements, whether written or oral, between the parties regarding the subject matter hereof. This EULA may be amended only in writing signed by authorized representatives of both parties. + + + +12.11. Contact Information. Please direct legal notices or other correspondence to VMware, Inc., 3401 Hillview Avenue, Palo Alto, California 94304, United States of America, Attention: Legal Department. diff --git a/hack/ovf_template.xml b/hack/ovf_template.xml new file mode 100644 index 0000000..316427e --- /dev/null +++ b/hack/ovf_template.xml @@ -0,0 +1,170 @@ + + + + + + + Virtual disk information + + + + The list of logical networks + + Please select a network + + + + A virtual machine + ${ARTIFACT_ID} + + A human-readable annotation + ${ANNOTATION} + + + The operating system installed + + + Virtual hardware requirements + + Virtual Hardware Family + 0 + ${ARTIFACT_ID} + vmx-${VMX_VERSION} + + + hertz * 10^6 + Number of Virtual CPUs + 2 virtual CPU(s) + 1 + 3 + 2 + 2 + + + byte * 2^20 + Memory Size + 2048MB of memory + 2 + 4 + 2048 + + + 0 + SCSI Controller + SCSI controller 0 + 3 + VirtualSCSI + 6 + + + + 1 + IDE Controller + IDE 1 + 4 + 5 + + + 0 + IDE Controller + IDE 0 + 5 + 5 + + + false + Video card + 6 + 24 + + + + + + + + false + VMCI device + 7 + vmware.vmci + 1 + + + + + 0 + Hard disk 1 + ovf:/disk/vmdisk1 + 8 + 3 + 17 + + + + 7 + true + nic0 + VmxNet3 ethernet adapter + Network adapter 1 + 9 + VmxNet3 + 10 + + + + + + 0 + false + CD/DVD drive 1 + 10 + 5 + vmware.cdrom.remotepassthrough + 15 + + + + + + + + + + + + + + + + + + + + + + + + + Virtual hardware device boot order + + + An end-user license agreement + +${EULA} + + + + Information about the installed software + ${PRODUCT} + VMware Inc. + ${TYPED_VERSION} + ${TYPED_VERSION} + https://vmware.com + Cluster API Provider (CAPI) + + + + ${PROPERTIES} + + + diff --git a/hack/utils.sh b/hack/utils.sh new file mode 100755 index 0000000..cfea5db --- /dev/null +++ b/hack/utils.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash + +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +case "${OSTYPE}" in +linux*) + HOSTOS=linux + ;; +darwin*) + HOSTOS=darwin + ;; +*) + echo "unsupported HOSTOS=${OSTYPE}" 1>&2 + exit 1 + ;; +esac + +_hostarch=$(uname -m) +case "${_hostarch}" in +*64*) + HOSTARCH=amd64 + ;; +*386*) + HOSTARCH=386 + ;; +*686*) + HOSTARCH=386 + ;; +*) + echo "unsupported HOSTARCH=${_hostarch}" 1>&2 + exit 1 + ;; +esac + +checksum_sha256() { + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 -c "${1}" + elif command -v sha256sum >/dev/null 2>&1; then + sha256sum -c "${1}" + else + echo "missing shasum tool" 1>&2 + return 1 + fi +} + +get_shasum() { + local present_shasum='' + if command -v shasum >/dev/null 2>&1; then + present_shasum=$(shasum -a 256 "${1}"| awk -F' ' '{print $1}') + elif command -v sha256sum >/dev/null 2>&1; then + present_shasum=$(sha256sum "${1}" | awk -F' ' '{print $1}') + else + echo "missing shasum tool" 1>&2 + return 1 + fi + echo "$present_shasum" +} + +ensure_py3_bin() { + # If given executable is not available, the user Python bin dir is not in path + # This function assumes the executable to be checked was installed with + # pip3 install --user ... + if ! command -v "${1}" >/dev/null 2>&1; then + echo "User's Python3 binary directory must be in \$PATH" 1>&2 + echo "Location of package is:" 1>&2 + pip3 show --disable-pip-version-check ${2:-$1} | grep "Location" + echo "\$PATH is currently: $PATH" 1>&2 + exit 1 + fi +} + +ensure_py3() { + if ! command -v python3 >/dev/null 2>&1; then + echo "python3 binary must be in \$PATH" 1>&2 + exit 1 + fi + if ! command -v pip3 >/dev/null 2>&1; then + curl -SsL https://bootstrap.pypa.io/get-pip.py -o get-pip.py + python3 get-pip.py --user + rm -f get-pip.py + ensure_py3_bin pip3 + fi +} diff --git a/hack/windows-ova-unattend.py b/hack/windows-ova-unattend.py new file mode 100755 index 0000000..4c75aa5 --- /dev/null +++ b/hack/windows-ova-unattend.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import xml.etree.ElementTree as ET +import os +import argparse +import json + +def set_xmlstring(root, location, key, value): + setting = root.find(location) + setting.find(key).text = value + return setting + +def main(): + parser = argparse.ArgumentParser( + description="Updates select variables in autounattend.xml") + parser.add_argument(dest='build_dir', + nargs='?', + metavar='BUILD_DIR', + default='.', + help='The Packer build directory') + parser.add_argument('--var-file', + dest='var_file', + nargs='?', + metavar='VARIABLES_FILE', + required=False, + default='./packer_cache/unattend.json', + help='The file that containers the unattend variables') + parser.add_argument('--unattend-file', + dest='unattend_file', + required=False, + nargs='?', + metavar='UNATTEND_FILE', + help='The Unattend file') + args = parser.parse_args() + + print("windows-ova-unattend: cd %s" % args.build_dir) + + # Load the packer manifest JSON + data = None + with open(args.var_file, 'r') as f: + data = json.load(f) + + modified=0 + os.chdir(args.build_dir) + unattend=ET.parse(args.unattend_file) + ET.register_namespace('', "urn:schemas-microsoft-com:unattend") + ET.register_namespace('wcm', "http://schemas.microsoft.com/WMIConfig/2002/State") + ET.register_namespace('xsi', "http://www.w3.org/2001/XMLSchema-instance") + + root = unattend.getroot() + + if data["unattend_timezone"] != "": + modified=1 + setting = set_xmlstring(root, ".//*[@pass='oobeSystem']/*[@name='Microsoft-Windows-Shell-Setup']",'{urn:schemas-microsoft-com:unattend}TimeZone', data["unattend_timezone"]) + print("windows-ova-unattend: Setting Timezone to %s" % data["unattend_timezone"]) + + if modified == 1: + print("windows-ova-unattend: Updating %s ..." % args.unattend_file) + unattend.write(args.unattend_file) + + if modified == 0: + print("windows-ova-unattend: skipping...") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/kubevirt-Dockerfile b/kubevirt-Dockerfile new file mode 100644 index 0000000..4d6ec14 --- /dev/null +++ b/kubevirt-Dockerfile @@ -0,0 +1,5 @@ +FROM registry.access.redhat.com/ubi8/ubi:latest AS builder +ADD --chown=107:107 /disk/image.qcow2 + +FROM scratch +COPY --from=builder /disk/* /disk/ diff --git a/packer/.gitignore b/packer/.gitignore new file mode 100644 index 0000000..4cab851 --- /dev/null +++ b/packer/.gitignore @@ -0,0 +1,3 @@ +/packer_cache/ +/output-*/ +/output/ \ No newline at end of file diff --git a/packer/ami/OWNERS b/packer/ami/OWNERS new file mode 100644 index 0000000..f131739 --- /dev/null +++ b/packer/ami/OWNERS @@ -0,0 +1,4 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - cluster-api-aws-maintainers diff --git a/packer/ami/amazon-2.json b/packer/ami/amazon-2.json new file mode 100644 index 0000000..1fb106e --- /dev/null +++ b/packer/ami/amazon-2.json @@ -0,0 +1,11 @@ +{ + "ami_filter_name": "amzn2-ami-hvm-2*", + "ami_filter_owners": "amazon", + "build_name": "amazon-2", + "distribution": "Amazon Linux", + "distribution_release": "Amazon Linux 2", + "distribution_version": "2", + "root_device_name": "/dev/xvda", + "source_ami": "", + "ssh_username": "ec2-user" +} diff --git a/packer/ami/centos-7.json b/packer/ami/centos-7.json new file mode 100644 index 0000000..ff952ad --- /dev/null +++ b/packer/ami/centos-7.json @@ -0,0 +1,11 @@ +{ + "ami_filter_name": "CentOS Linux 7 x86_64 HVM EBS ENA*", + "ami_filter_owners": "461800378586", + "build_name": "centos-7", + "distribution": "CentOS", + "distribution_release": "Core", + "distribution_version": "7", + "root_device_name": "/dev/sda1", + "source_ami": "", + "ssh_username": "centos" +} diff --git a/packer/ami/flatcar.json b/packer/ami/flatcar.json new file mode 100644 index 0000000..cb73125 --- /dev/null +++ b/packer/ami/flatcar.json @@ -0,0 +1,17 @@ +{ + "ami_filter_name": "Flatcar*{{env `FLATCAR_CHANNEL`}}*", + "ami_filter_owners": "075585003325", + "ansible_extra_vars": "ansible_python_interpreter=/opt/bin/python", + "build_name": "flatcar-{{env `FLATCAR_CHANNEL`}}", + "crictl_source_type": "http", + "distribution": "flatcar", + "kubernetes_cni_source_type": "http", + "kubernetes_source_type": "http", + "python_path": "/opt/bin/builder-env/site-packages", + "root_device_name": "/dev/xvda", + "ssh_username": "core", + "systemd_prefix": "/etc/systemd", + "sysusr_prefix": "/opt", + "sysusrlocal_prefix": "/opt", + "user_data": "" +} diff --git a/packer/ami/packer-windows.json b/packer/ami/packer-windows.json new file mode 100644 index 0000000..f0373dc --- /dev/null +++ b/packer/ami/packer-windows.json @@ -0,0 +1,208 @@ +{ + "builders": [ + { + "access_key": "{{user `aws_access_key`}}", + "ami_description": "{{user `ami_description`}}", + "ami_groups": "{{user `ami_groups`}}", + "ami_name": "capa-ami-{{user `build_name`}}-{{user `kubernetes_semver` | clean_resource_name}}-{{user `build_timestamp`}}", + "ami_product_codes": "", + "ami_regions": "{{user `ami_regions`}}", + "ami_users": "{{user `ami_users`}}", + "associate_public_ip_address": true, + "communicator": "winrm", + "disable_stop_instance": true, + "encrypt_boot": "{{user `encrypted`}}", + "iam_instance_profile": "{{user `iam_instance_profile`}}", + "instance_type": "{{user `builder_instance_type`}}", + "kms_key_id": "{{user `kms_key_id`}}", + "launch_block_device_mappings": [ + { + "delete_on_termination": true, + "device_name": "{{ user `root_device_name` }}", + "throughput": "{{ user `throughput` }}", + "volume_size": "{{ user `volume_size` }}", + "volume_type": "{{ user `volume_type` }}" + } + ], + "name": "{{user `build_name`}}", + "profile": "{{ user `aws_profile`}}", + "region": "{{ user `aws_region` }}", + "secret_key": "{{user `aws_secret_key`}}", + "security_group_ids": "{{user `aws_security_group_ids`}}", + "skip_create_ami": "{{ user `skip_create_ami`}}", + "skip_profile_validation": "{{user `skip_profile_validation`}}", + "snapshot_groups": "{{user `snapshot_groups`}}", + "snapshot_users": "{{user `snapshot_users`}}", + "source_ami": "{{user `source_ami`}}", + "source_ami_filter": { + "filters": { + "architecture": "x86_64", + "name": "{{user `ami_filter_name`}}", + "root-device-type": "ebs", + "virtualization-type": "hvm" + }, + "most_recent": true, + "owners": "{{user `ami_filter_owners`}}" + }, + "ssh_keypair_name": "{{user `ssh_keypair_name`}}", + "ssh_private_key_file": "{{user `ssh_private_key_file`}}", + "subnet_id": "{{ user `subnet_id` }}", + "tags": { + "build_date": "{{isotime}}", + "build_timestamp": "{{user `build_timestamp`}}", + "containerd_version": "{{user `containerd_version`}}", + "distribution": "{{user `distribution`}}", + "distribution_version": "{{user `distribution_version`}}", + "image_builder_version": "{{user `ib_version`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}", + "source_ami": "{{user `source_ami`}}" + }, + "temporary_security_group_source_cidrs": "{{ user `temporary_security_group_source_cidrs` }}", + "token": "{{ user `aws_session_token` }}", + "type": "amazon-ebs", + "user_data_file": "packer/ami/scripts/winrm_bootstrap.txt", + "vpc_id": "{{ user `vpc_id` }}", + "winrm_insecure": true, + "winrm_timeout": "6m", + "winrm_use_ssl": true, + "winrm_username": "Administrator" + } + ], + "post-processors": [ + { + "custom_data": { + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}" + }, + "output": "{{user `manifest_output`}}", + "type": "manifest" + } + ], + "provisioners": [ + { + "extra_arguments": [ + "-e", + "ansible_winrm_server_cert_validation=ignore", + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}" + ], + "playbook_file": "ansible/windows/node_windows.yml", + "type": "ansible", + "use_proxy": false, + "user": "Administrator" + }, + { + "restart_timeout": "10m", + "type": "windows-restart" + }, + { + "arch": "{{user `goss_arch`}}", + "download_path": "{{user `goss_download_path`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "remote_folder": "{{user `goss_remote_folder`}}", + "remote_path": "{{user `goss_remote_path`}}", + "skip_install": "{{user `goss_skip_install`}}", + "target_os": "Windows", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": false, + "vars_env": { + "GOSS_MAX_CONCURRENT": "1", + "GOSS_USE_ALPHA": "1" + }, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "OS": "{{user `distribution` | lower}}", + "PROVIDER": "amazon", + "containerd_version": "{{user `containerd_version`}}", + "distribution_version": "{{user `distribution_version`}}", + "docker_ee_version": "{{user `docker_ee_version`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}", + "pause_image": "{{user `pause_image`}}", + "runtime": "{{user `runtime`}}", + "ssh_source_url": "{{user `ssh_source_url`}}" + }, + "version": "{{user `goss_version`}}" + }, + { + "inline": [ + "rm -Force -Recurse C:\\var\\log\\kubelet\\*" + ], + "type": "powershell" + }, + { + "elevated_password": "{{.WinRMPassword}}", + "elevated_user": "Administrator", + "script": "packer/ami/scripts/sysprep_prerequisites.ps1", + "type": "powershell" + }, + { + "inline": [ + "C:/ProgramData/Amazon/EC2-Windows/Launch/Scripts/SysprepInstance.ps1" + ], + "type": "powershell" + } + ], + "variables": { + "additional_debug_files": null, + "ami_description": "Cluster API base image designed for {{user `kubernetes_semver`}}", + "ami_groups": "all", + "ami_regions": "ap-south-1,eu-west-3,eu-west-2,eu-west-1,ap-northeast-2,ap-northeast-1,sa-east-1,ca-central-1,ap-southeast-1,ap-southeast-2,eu-central-1,us-east-1,us-east-2,us-west-1,us-west-2", + "ami_users": "", + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_user_vars": "", + "aws_access_key": "", + "aws_profile": "", + "aws_region": "us-east-1", + "aws_secret_key": "", + "aws_security_group_ids": "", + "aws_session_token": "", + "build_name": null, + "build_timestamp": "{{timestamp}}", + "builder_instance_type": "t3.large", + "cloudbase_init_url": "https://github.com/cloudbase/cloudbase-init/releases/download/{{user `cloudbase_init_version`}}/CloudbaseInitSetup_{{user `cloudbase_init_version` | replace_all `.` `_` }}_x64.msi", + "cloudbase_metadata_services": "cloudbaseinit.metadata.services.ec2service.EC2Service", + "cloudbase_metadata_services_unattend": "cloudbaseinit.metadata.services.base.EmptyMetadataService", + "cloudbase_plugins": "cloudbaseinit.plugins.windows.createuser.CreateUserPlugin, cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin, cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin, cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin, cloudbaseinit.plugins.common.mtu.MTUPlugin, cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin, cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin", + "cloudbase_plugins_unattend": "cloudbaseinit.plugins.common.mtu.MTUPlugin", + "containerd_sha256": null, + "containerd_url": "", + "containerd_version": null, + "encrypted": "false", + "iam_instance_profile": "", + "ib_version": "{{env `IB_VERSION`}}", + "kms_key_id": "", + "kubernetes_base_url": "https://kubernetesreleases.blob.core.windows.net/kubernetes/{{user `kubernetes_semver`}}/binaries/node/windows/{{user `kubernetes_goarch`}}", + "manifest_output": "manifest.json", + "nssm_url": null, + "prepull": null, + "skip_create_ami": "false", + "skip_profile_validation": "false", + "snapshot_groups": "all", + "snapshot_users": "", + "ssh_keypair_name": "", + "ssh_private_key_file": "", + "subnet_id": "", + "temporary_security_group_source_cidrs": "", + "throughput": "125", + "volume_size": "40", + "volume_type": "gp3", + "vpc_id": "", + "windows_service_manager": null, + "windows_updates_kbs": null, + "wins_url": "https://github.com/rancher/wins/releases/download/v{{user `wins_version`}}/wins.exe" + } +} diff --git a/packer/ami/packer.json b/packer/ami/packer.json new file mode 100644 index 0000000..7b957a8 --- /dev/null +++ b/packer/ami/packer.json @@ -0,0 +1,210 @@ +{ + "builders": [ + { + "access_key": "{{user `aws_access_key`}}", + "ami_description": "{{user `ami_description`}}", + "ami_groups": "{{user `ami_groups`}}", + "ami_name": "capa-ami-{{user `build_name`}}-{{user `kubernetes_semver` | clean_resource_name}}-{{user `build_timestamp`}}", + "ami_product_codes": "", + "ami_regions": "{{user `ami_regions`}}", + "ami_users": "{{user `ami_users`}}", + "encrypt_boot": "{{user `encrypted`}}", + "iam_instance_profile": "{{user `iam_instance_profile`}}", + "instance_type": "{{user `builder_instance_type`}}", + "kms_key_id": "{{user `kms_key_id`}}", + "launch_block_device_mappings": [ + { + "delete_on_termination": true, + "device_name": "{{ user `root_device_name` }}", + "iops": "{{ user `iops`}}", + "throughput": "{{ user `throughput` }}", + "volume_size": "{{ user `volume_size` }}", + "volume_type": "{{ user `volume_type` }}" + } + ], + "name": "{{user `build_name`}}", + "profile": "{{ user `aws_profile`}}", + "region": "{{ user `aws_region` }}", + "secret_key": "{{user `aws_secret_key`}}", + "security_group_ids": "{{user `aws_security_group_ids`}}", + "skip_create_ami": "{{ user `skip_create_ami`}}", + "skip_profile_validation": "{{user `skip_profile_validation`}}", + "snapshot_groups": "{{user `snapshot_groups`}}", + "snapshot_users": "{{user `snapshot_users`}}", + "source_ami": "{{user `source_ami`}}", + "source_ami_filter": { + "filters": { + "architecture": "x86_64", + "name": "{{user `ami_filter_name`}}", + "root-device-type": "ebs", + "virtualization-type": "hvm" + }, + "most_recent": true, + "owners": "{{user `ami_filter_owners`}}" + }, + "ssh_keypair_name": "{{user `ssh_keypair_name`}}", + "ssh_private_key_file": "{{user `ssh_private_key_file`}}", + "ssh_username": "{{user `ssh_username`}}", + "subnet_id": "{{ user `subnet_id` }}", + "tags": { + "build_date": "{{isotime}}", + "build_timestamp": "{{user `build_timestamp`}}", + "containerd_version": "{{user `containerd_version`}}", + "distribution": "{{user `distribution`}}", + "distribution_release": "{{user `distribution_release`}}", + "distribution_version": "{{user `distribution_version`}}", + "image_builder_version": "{{user `ib_version`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}", + "source_ami": "{{user `source_ami`}}" + }, + "temporary_security_group_source_cidrs": "{{ user `temporary_security_group_source_cidrs` }}", + "token": "{{ user `aws_session_token` }}", + "type": "amazon-ebs", + "user_data": "{{ user `user_data` }}", + "vpc_id": "{{ user `vpc_id` }}" + } + ], + "post-processors": [ + { + "custom_data": { + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}" + }, + "output": "{{user `manifest_output`}}", + "type": "manifest" + } + ], + "provisioners": [ + { + "environment_vars": [ + "BUILD_NAME={{user `build_name`}}" + ], + "inline": [ + "if [ $BUILD_NAME != \"ubuntu-1804\" ]; then exit 0; fi", + "while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done", + "sudo apt-get -qq update && sudo DEBIAN_FRONTEND=noninteractive apt-get -qqy install python python-pip" + ], + "type": "shell" + }, + { + "execute_command": "BUILD_NAME={{user `build_name`}}; if [[ \"${BUILD_NAME}\" == *\"flatcar\"* ]]; then sudo {{.Vars}} -S -E bash '{{.Path}}'; fi", + "script": "./packer/files/flatcar/scripts/bootstrap-flatcar.sh", + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/node.yml", + "type": "ansible" + }, + { + "arch": "{{user `goss_arch`}}", + "download_path": "{{user `goss_download_path`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "remote_folder": "{{user `goss_remote_folder`}}", + "remote_path": "{{user `goss_remote_path`}}", + "skip_install": "{{user `goss_skip_install`}}", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": true, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "ARCH": "amd64", + "OS": "{{user `distribution` | lower}}", + "OS_VERSION": "{{user `distribution_version` | lower}}", + "PROVIDER": "amazon", + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_deb_version": "{{ user `kubernetes_cni_deb_version` }}", + "kubernetes_cni_rpm_version": "{{ split (user `kubernetes_cni_rpm_version`) \"-\" 0 }}", + "kubernetes_cni_source_type": "{{user `kubernetes_cni_source_type`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver` | replace \"v\" \"\" 1}}", + "kubernetes_deb_version": "{{ user `kubernetes_deb_version` }}", + "kubernetes_rpm_version": "{{ split (user `kubernetes_rpm_version`) \"-\" 0 }}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "kubernetes_version": "{{user `kubernetes_semver` | replace \"v\" \"\" 1}}" + }, + "version": "{{user `goss_version`}}" + } + ], + "variables": { + "amazon_ssm_agent_rpm": "https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm", + "ami_description": "Cluster API base image designed for {{user `kubernetes_semver`}}", + "ami_groups": "all", + "ami_regions": "ap-south-1,eu-west-3,eu-west-2,eu-west-1,ap-northeast-2,ap-northeast-1,sa-east-1,ca-central-1,ap-southeast-1,ap-southeast-2,eu-central-1,us-east-1,us-east-2,us-west-1,us-west-2", + "ami_users": "", + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_scp_extra_args": "", + "ansible_user_vars": "", + "aws_access_key": "", + "aws_profile": "", + "aws_region": "us-east-1", + "aws_secret_key": "", + "aws_security_group_ids": "", + "aws_session_token": "", + "build_timestamp": "{{timestamp}}", + "builder_instance_type": "t3.small", + "containerd_sha256": null, + "containerd_url": "https://github.com/containerd/containerd/releases/download/v{{user `containerd_version`}}/cri-containerd-cni-{{user `containerd_version`}}-linux-amd64.tar.gz", + "containerd_version": null, + "crictl_url": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-amd64.tar.gz", + "crictl_version": null, + "encrypted": "false", + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "iam_instance_profile": "", + "ib_version": "{{env `IB_VERSION`}}", + "iops": "3000", + "kms_key_id": "", + "kubernetes_cni_deb_version": null, + "kubernetes_cni_http_source": null, + "kubernetes_cni_rpm_version": null, + "kubernetes_cni_semver": null, + "kubernetes_cni_source_type": null, + "kubernetes_container_registry": null, + "kubernetes_deb_gpg_key": null, + "kubernetes_deb_repo": null, + "kubernetes_deb_version": null, + "kubernetes_http_source": null, + "kubernetes_load_additional_imgs": null, + "kubernetes_rpm_gpg_check": null, + "kubernetes_rpm_gpg_key": null, + "kubernetes_rpm_repo": null, + "kubernetes_rpm_version": null, + "kubernetes_semver": null, + "kubernetes_source_type": null, + "manifest_output": "manifest.json", + "python_path": "", + "skip_create_ami": "false", + "skip_profile_validation": "false", + "snapshot_groups": "all", + "snapshot_users": "", + "ssh_keypair_name": "", + "ssh_private_key_file": "", + "subnet_id": "", + "temporary_security_group_source_cidrs": "", + "throughput": "125", + "user_data": "#cloud-config\nrepo_upgrade: none", + "volume_size": "8", + "volume_type": "gp3", + "vpc_id": "" + } +} diff --git a/packer/ami/rhel-8.json b/packer/ami/rhel-8.json new file mode 100644 index 0000000..cf86b63 --- /dev/null +++ b/packer/ami/rhel-8.json @@ -0,0 +1,15 @@ +{ + "ami_filter_name": "RHEL-8.6.0_HVM-*", + "ami_filter_owners": "309956199498", + "build_name": "rhel-8", + "builder_instance_type": "m5.large", + "distribution": "rhel", + "distribution_release": "Enterprise", + "distribution_version": "8", + "epel_rpm_gpg_key": "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm", + "root_device_name": "/dev/sda1", + "source_ami": "", + "ssh_username": "ec2-user", + "volume_size": "10" +} diff --git a/packer/ami/rockylinux-8.json b/packer/ami/rockylinux-8.json new file mode 100644 index 0000000..fc1fd16 --- /dev/null +++ b/packer/ami/rockylinux-8.json @@ -0,0 +1,14 @@ +{ + "ami_filter_name": "Rocky-8-ec2-8.5-*", + "ami_filter_owners": "679593333241", + "build_name": "rockylinux-8", + "distribution": "rockylinux", + "distribution_release": "Core", + "distribution_version": "8", + "epel_rpm_gpg_key": "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm", + "root_device_name": "/dev/sda1", + "source_ami": "", + "ssh_username": "rocky", + "volume_size": "10" +} diff --git a/packer/ami/scripts/sysprep_prerequisites.ps1 b/packer/ami/scripts/sysprep_prerequisites.ps1 new file mode 100644 index 0000000..0321e63 --- /dev/null +++ b/packer/ami/scripts/sysprep_prerequisites.ps1 @@ -0,0 +1,29 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Write-Output 'Removing default unattend.xml file...' +if( Test-Path $Env:SystemRoot\system32\Sysprep\unattend.xml ) { + Remove-Item $Env:SystemRoot\system32\Sysprep\unattend.xml -Force +} + +# Schedule InitializeInstance to run on next boot +& $Env:ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule + +$unattendedXml = "$ENV:ProgramFiles\Cloudbase Solutions\Cloudbase-Init\conf\Unattend.xml" +$FileExists = Test-Path $unattendedXml +If ($FileExists -eq $True) { + # Use the Cloudbase-init provided unattend file during install + Write-Output "Using cloudbase-init unattend file for sysprep: $unattendedXml" + Copy-Item -Force 'C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\Unattend.xml' $Env:ProgramData\Amazon\EC2-Windows\Launch\Sysprep\Unattend.xml +} diff --git a/packer/ami/scripts/winrm_bootstrap.txt b/packer/ami/scripts/winrm_bootstrap.txt new file mode 100644 index 0000000..1da0612 --- /dev/null +++ b/packer/ami/scripts/winrm_bootstrap.txt @@ -0,0 +1,47 @@ + + +# MAKE SURE IN YOUR PACKER CONFIG TO SET: +# +# +# "winrm_username": "Administrator", +# "winrm_insecure": true, +# "winrm_use_ssl": true, +# +# + + +write-output "Running User Data Script" +write-host "(host) Running User Data Script" + +Set-ExecutionPolicy Unrestricted -Scope LocalMachine -Force -ErrorAction Ignore + +# Don't set this before Set-ExecutionPolicy as it throws an error +$ErrorActionPreference = "stop" + +# Remove HTTP listener +Remove-Item -Path WSMan:\Localhost\listener\listener* -Recurse + +# Create a self-signed certificate to let ssl work +$Cert = New-SelfSignedCertificate -CertstoreLocation Cert:\LocalMachine\My -DnsName "packer" +New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $Cert.Thumbprint -Force + +# WinRM +write-output "Setting up WinRM" +write-host "(host) setting up WinRM" + +cmd.exe /c winrm quickconfig -q +cmd.exe /c winrm set "winrm/config" '@{MaxTimeoutms="1800000"}' +cmd.exe /c winrm set "winrm/config/winrs" '@{MaxMemoryPerShellMB="1024"}' +cmd.exe /c winrm set "winrm/config/service" '@{AllowUnencrypted="true"}' +cmd.exe /c winrm set "winrm/config/client" '@{AllowUnencrypted="true"}' +cmd.exe /c winrm set "winrm/config/service/auth" '@{Basic="true"}' +cmd.exe /c winrm set "winrm/config/client/auth" '@{Basic="true"}' +cmd.exe /c winrm set "winrm/config/service/auth" '@{CredSSP="true"}' +cmd.exe /c winrm set "winrm/config/listener?Address=*+Transport=HTTPS" "@{Port=`"5986`";Hostname=`"packer`";CertificateThumbprint=`"$($Cert.Thumbprint)`"}" +cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes +cmd.exe /c netsh firewall add portopening TCP 5986 "Port 5986" +cmd.exe /c net stop winrm +cmd.exe /c sc config winrm start= auto +cmd.exe /c net start winrm + + \ No newline at end of file diff --git a/packer/ami/ubuntu-1804.json b/packer/ami/ubuntu-1804.json new file mode 100644 index 0000000..08d013b --- /dev/null +++ b/packer/ami/ubuntu-1804.json @@ -0,0 +1,11 @@ +{ + "ami_filter_name": "ubuntu/images/*ubuntu-bionic-18.04-amd64-server-*", + "ami_filter_owners": "099720109477", + "build_name": "ubuntu-18.04", + "distribution": "Ubuntu", + "distribution_release": "bionic", + "distribution_version": "18.04", + "root_device_name": "/dev/sda1", + "source_ami": "", + "ssh_username": "ubuntu" +} diff --git a/packer/ami/ubuntu-2004.json b/packer/ami/ubuntu-2004.json new file mode 100644 index 0000000..707fd1b --- /dev/null +++ b/packer/ami/ubuntu-2004.json @@ -0,0 +1,11 @@ +{ + "ami_filter_name": "ubuntu/images/*ubuntu-focal-20.04-amd64-server-*", + "ami_filter_owners": "099720109477", + "build_name": "ubuntu-20.04", + "distribution": "Ubuntu", + "distribution_release": "focal", + "distribution_version": "20.04", + "root_device_name": "/dev/sda1", + "source_ami": "", + "ssh_username": "ubuntu" +} diff --git a/packer/ami/ubuntu-2204.json b/packer/ami/ubuntu-2204.json new file mode 100644 index 0000000..785ddba --- /dev/null +++ b/packer/ami/ubuntu-2204.json @@ -0,0 +1,11 @@ +{ + "ami_filter_name": "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*", + "ami_filter_owners": "099720109477", + "build_name": "ubuntu-22.04", + "distribution": "Ubuntu", + "distribution_release": "jammy", + "distribution_version": "22.04", + "root_device_name": "/dev/sda1", + "source_ami": "", + "ssh_username": "ubuntu" +} diff --git a/packer/ami/windows-2004.json b/packer/ami/windows-2004.json new file mode 100644 index 0000000..0d8c80f --- /dev/null +++ b/packer/ami/windows-2004.json @@ -0,0 +1,10 @@ +{ + "ami_filter_name": "Windows_Server-2004-English-Core-ContainersLatest-*", + "ami_filter_owners": "amazon", + "build_name": "windows-2004", + "distribution": "windows", + "distribution_version": "2004", + "root_device_name": "/dev/sda1", + "source_ami": "", + "windows_updates_kbs": "KB4588962 KB2267602" +} diff --git a/packer/ami/windows-2019.json b/packer/ami/windows-2019.json new file mode 100644 index 0000000..e647114 --- /dev/null +++ b/packer/ami/windows-2019.json @@ -0,0 +1,10 @@ +{ + "ami_filter_name": "Windows_Server-2019-English-Full-HyperV-*", + "ami_filter_owners": "amazon", + "build_name": "windows-2019", + "distribution": "windows", + "distribution_version": "2019", + "root_device_name": "/dev/sda1", + "source_ami": "", + "windows_updates_kbs": "KB4588962 KB2267602" +} diff --git a/packer/azure/.pipelines/build-vhd.yaml b/packer/azure/.pipelines/build-vhd.yaml new file mode 100644 index 0000000..009477d --- /dev/null +++ b/packer/azure/.pipelines/build-vhd.yaml @@ -0,0 +1,50 @@ +# Required pipeline variables: +# - BUILD_POOL - Azure DevOps build pool to use +# - CONTAINER_IMAGE - Dev container image URL to use. Should have Azure CLI, Packer and Ansible. +# - AZURE_TENANT_ID - tenant ID +# - AZURE_CLIENT_ID - Service principal ID +# - AZURE_CLIENT_SECRET - Service principal secret +# - AZURE_SUBSCRIPTION_ID - Subscription ID used by the pipeline +# - KUBERNETES_VERSION - version of Kubernetes to build the image with, e.g. `1.16.2` +# - OS - target of build e.g. `Ubuntu/Windows` +# - OS_VERSION - target of build e.g. `18.04/2004/2019` + +jobs: +- job: build_vhd + container: $[ variables['CONTAINER_IMAGE'] ] + timeoutInMinutes: 120 + strategy: + maxParallel: 0 + pool: + name: $(BUILD_POOL) + steps: + - template: k8s-config.yaml + - script: | + set -o pipefail + make deps-azure + os=$(echo "${OS}" | tr '[:upper:]' '[:lower:]') + version=$(echo "${OS_VERSION}" | tr '[:upper:]' '[:lower:]' | tr -d .) + export RESOURCE_GROUP_NAME="cluster-api-images" + + # timestamp is in RFC-3339 format to match kubetest + export TIMESTAMP="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + export JOB_NAME="${JOB_NAME:-"image-builder-vhd"}" + export TAGS="creationTimestamp=${TIMESTAMP} jobName=${JOB_NAME}" + printf "${TAGS}" | tee packer/azure/tags.out + make build-azure-vhd-$os-$version | tee packer/azure/packer.out + displayName: Building VHD + workingDirectory: '$(system.defaultWorkingDirectory)/images/capi' + env: + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) + - template: generate-sas.yaml + - template: vhd-publishing-info.yaml + - task: PublishPipelineArtifact@1 + inputs: + artifact: 'publishing-info' + path: '$(system.defaultWorkingDirectory)/images/capi/packer/azure/vhd-publishing-info.json' + condition: eq(variables.CLEANUP, 'False') + - template: delete-storage-account.yaml + - script: | + chown -R $USER:$USER . + displayName: cleanup - chown all files in work directory + condition: always() diff --git a/packer/azure/.pipelines/create-disk-version.yaml b/packer/azure/.pipelines/create-disk-version.yaml new file mode 100644 index 0000000..9317ce1 --- /dev/null +++ b/packer/azure/.pipelines/create-disk-version.yaml @@ -0,0 +1,38 @@ +# Required pipeline variables: +# - BUILD_POOL - Azure DevOps build pool to use +# - CONTAINER_IMAGE - Dev container image URL to use. Should have Azure CLI, Packer and Ansible. +# - AZURE_TENANT_ID - tenant ID +# - AZURE_CLIENT_ID - Service principal ID +# - AZURE_CLIENT_SECRET - Service principal secret +# - OS - target of build e.g. `Ubuntu/Windows` +# - OS_VERSION - target of build e.g. `18.04/2004/2019` + +jobs: +- job: create_disk_version + container: $[ variables['CONTAINER_IMAGE'] ] + timeoutInMinutes: 120 + strategy: + maxParallel: 0 + pool: + name: $(BUILD_POOL) + steps: + - task: DownloadPipelineArtifact@2 + inputs: + source: current + artifact: publishing-info + path: $(system.defaultWorkingDirectory)/images/capi/packer/azure/vhd/ + - task: DownloadPipelineArtifact@2 + inputs: + source: current + artifact: sku-info + path: $(system.defaultWorkingDirectory)/images/capi/packer/azure/sku/ + - script: | + ./scripts/new-disk-version.sh + displayName: Create a new marketplace SKU + workingDirectory: '$(system.defaultWorkingDirectory)/images/capi/packer/azure' + env: + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) + - task: PublishPipelineArtifact@1 + inputs: + artifact: 'version_info' + path: '$(system.defaultWorkingDirectory)/images/capi/packer/azure/version.json' diff --git a/packer/azure/.pipelines/create-sku.yaml b/packer/azure/.pipelines/create-sku.yaml new file mode 100644 index 0000000..149a091 --- /dev/null +++ b/packer/azure/.pipelines/create-sku.yaml @@ -0,0 +1,34 @@ +# Required pipeline variables: +# - BUILD_POOL - Azure DevOps build pool to use +# - CONTAINER_IMAGE - Dev container image URL to use. Should have Azure CLI, Packer and Ansible. +# - AZURE_CLIENT_ID - Service principal ID +# - AZURE_CLIENT_SECRET - Service principal secret +# - AZURE_TENANT_ID - tenant ID +# - KUBERNETES_VERSION - version of Kubernetes to create the sku for, e.g. `1.16.2` +# - OFFER - the name of the offer to create the sku for +# - OS - target of build e.g. `Ubuntu/Windows` +# - OS_VERSION - target of build e.g. `18.04/2004/2019/2022-containerd` +# - PUBLISHER - the name of the publisher to create the sku for +# - SKU_TEMPLATE_FILE - the base template file to use for the sku +# - VM_GENERATION - VM generation to use, e.g. `gen2` + +jobs: +- job: create_sku + container: $[ variables['CONTAINER_IMAGE'] ] + timeoutInMinutes: 120 + strategy: + maxParallel: 0 + pool: + name: $(BUILD_POOL) + steps: + - script: | + ./scripts/new-sku.sh + displayName: Create a new marketplace SKU + workingDirectory: '$(system.defaultWorkingDirectory)/images/capi/packer/azure' + env: + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) + - task: PublishPipelineArtifact@1 + inputs: + artifact: 'sku-info' + path: '$(system.defaultWorkingDirectory)/images/capi/packer/azure/sku-publishing-info.json' + \ No newline at end of file diff --git a/packer/azure/.pipelines/delete-storage-account.yaml b/packer/azure/.pipelines/delete-storage-account.yaml new file mode 100644 index 0000000..eacda22 --- /dev/null +++ b/packer/azure/.pipelines/delete-storage-account.yaml @@ -0,0 +1,13 @@ +steps: +- script: | + set -o pipefail + RESOURCE_GROUP_NAME=$(jq -r '.builds[-1].custom_data.resource_group_name' manifest.json | cut -d ":" -f2) + STORAGE_ACCOUNT_NAME=$(jq -r '.builds[-1].custom_data.storage_account_name' manifest.json | cut -d ":" -f2) + az login --service-principal -u ${AZURE_CLIENT_ID} -p ${AZURE_CLIENT_SECRET} --tenant ${AZURE_TENANT_ID} + az account set -s ${AZURE_SUBSCRIPTION_ID} + az storage account delete -n ${STORAGE_ACCOUNT_NAME} -g ${RESOURCE_GROUP_NAME} --yes + displayName: cleanup - delete storage account + workingDirectory: '$(system.defaultWorkingDirectory)/images/capi' + condition: eq(variables.CLEANUP, 'True') + env: + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) diff --git a/packer/azure/.pipelines/generate-sas.yaml b/packer/azure/.pipelines/generate-sas.yaml new file mode 100644 index 0000000..0cddefd --- /dev/null +++ b/packer/azure/.pipelines/generate-sas.yaml @@ -0,0 +1,21 @@ +steps: +- script: | + set -o pipefail + RESOURCE_GROUP_NAME=$(jq -r '.builds[-1].custom_data.resource_group_name' manifest.json | cut -d ":" -f2) + STORAGE_ACCOUNT_NAME=$(jq -r '.builds[-1].custom_data.storage_account_name' manifest.json | cut -d ":" -f2) + OS_DISK_URI=$(cat packer/azure/packer.out | grep "OSDiskUri:" -m 1 | cut -d " " -f 2) + printf "${STORAGE_ACCOUNT_NAME}" | tee packer/azure/storage-account-name.out + printf "${OS_DISK_URI}" | tee packer/azure/vhd-base-url.out + printf "${OS_DISK_URI}?" | tee packer/azure/vhd-url.out + printf "${RESOURCE_GROUP_NAME}" | tee packer/azure/resource-group-name.out + az login --service-principal -u ${AZURE_CLIENT_ID} -p ${AZURE_CLIENT_SECRET} --tenant ${AZURE_TENANT_ID} + az account set -s ${AZURE_SUBSCRIPTION_ID} + ACCOUNT_KEY=$(az storage account keys list -g ${RESOURCE_GROUP_NAME} --subscription ${AZURE_SUBSCRIPTION_ID} --account-name ${STORAGE_ACCOUNT_NAME} --query '[0].value') + start_date=$(date +"%Y-%m-%dT00:00Z" -d "-1 day") + expiry_date=$(date +"%Y-%m-%dT00:00Z" -d "+1 year") + az storage container generate-sas --name system --permissions lr --account-name ${STORAGE_ACCOUNT_NAME} --account-key ${ACCOUNT_KEY} --start $start_date --expiry $expiry_date | tr -d '\"' | tee -a packer/azure/vhd-url.out + displayName: Getting OS VHD URL + workingDirectory: '$(system.defaultWorkingDirectory)/images/capi' + condition: eq(variables.CLEANUP, 'False') + env: + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) diff --git a/packer/azure/.pipelines/k8s-config.yaml b/packer/azure/.pipelines/k8s-config.yaml new file mode 100644 index 0000000..1a154ef --- /dev/null +++ b/packer/azure/.pipelines/k8s-config.yaml @@ -0,0 +1,15 @@ +steps: +- script: | + KUBERNETES_RELEASE=$(echo ${KUBERNETES_VERSION} | cut -d "." -f -2) + sed -i "s/.*kubernetes_series.*/ \"kubernetes_series\": \"v${KUBERNETES_RELEASE}\",/g" kubernetes.json + sed -i "s/.*kubernetes_semver.*/ \"kubernetes_semver\": \"v${KUBERNETES_VERSION}\",/g" kubernetes.json + if [[ "${KUBERNETES_VERSION:-}" == "1.16.11" || "${KUBERNETES_VERSION:-}" == "1.17.7" || "${KUBERNETES_VERSION:-}" == "1.18.4" ]]; then + sed -i "s/.*kubernetes_rpm_version.*/ \"kubernetes_rpm_version\": \"${KUBERNETES_VERSION}-1\",/g" kubernetes.json + sed -i "s/.*kubernetes_deb_version.*/ \"kubernetes_deb_version\": \"${KUBERNETES_VERSION}-01\",/g" kubernetes.json + else + sed -i "s/.*kubernetes_rpm_version.*/ \"kubernetes_rpm_version\": \"${KUBERNETES_VERSION}-0\",/g" kubernetes.json + sed -i "s/.*kubernetes_deb_version.*/ \"kubernetes_deb_version\": \"${KUBERNETES_VERSION}-00\",/g" kubernetes.json + fi + cat kubernetes.json + displayName: Write configuration files + workingDirectory: '$(system.defaultWorkingDirectory)/images/capi/packer/config' diff --git a/packer/azure/.pipelines/smoke-test.yaml b/packer/azure/.pipelines/smoke-test.yaml new file mode 100644 index 0000000..a321034 --- /dev/null +++ b/packer/azure/.pipelines/smoke-test.yaml @@ -0,0 +1,59 @@ +# Required pipeline variables: +# - BUILD_POOL - Azure DevOps build pool to use +# - CONTAINER_IMAGE - Dev container image URL to use. Should have Azure CLI, Packer and Ansible. +# - AZURE_TENANT_ID_VHD - tenant ID to build the vhd +# - AZURE_CLIENT_ID_VHD - Service principal ID to build the vhd +# - AZURE_CLIENT_SECRET_VHD - Service principal secret to build the vhd +# - AZURE_SUBSCRIPTION_ID_VHD - Subscription ID to build the vhd +# - KUBERNETES_VERSION - version of Kubernetes to create the sku for, e.g. `1.21.3` +# - CLEANUP - whether or not to clean up resources created in the run + +trigger: none + +schedules: + - cron: "0 1 * * *" + displayName: "nightly build" + always: true + branches: + include: + - master + +stages: + - stage: vhd + jobs: + - job: + container: $[ variables['CONTAINER_IMAGE'] ] + timeoutInMinutes: 120 + pool: + name: $(BUILD_POOL) + steps: + - template: k8s-config.yaml + - script: | + set -o pipefail + make deps-azure + os=$(echo "$OS" | tr '[:upper:]' '[:lower:]') + version=$(echo "$OS_VERSION" | tr '[:upper:]' '[:lower:]' | tr -d .) + make build-azure-vhd-$os-$version | tee packer/azure/packer.out + displayName: Building VHD + workingDirectory: '$(system.defaultWorkingDirectory)/images/capi' + env: + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) + - template: delete-storage-account.yaml + - script: | + chown -R $USER:$USER . + displayName: cleanup - chown all files in work directory + condition: always() + strategy: + maxParallel: 0 + matrix: + Windows: + OS: Windows + OS_VERSION: 2019 + Linux: + OS: Ubuntu + OS_VERSION: 2004 + variables: + AZURE_TENANT_ID: $(AZURE_TENANT_ID_VHD) + AZURE_CLIENT_ID: $(AZURE_CLIENT_ID_VHD) + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET_VHD) + AZURE_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID_VHD) diff --git a/packer/azure/.pipelines/stages.yaml b/packer/azure/.pipelines/stages.yaml new file mode 100644 index 0000000..e83ec1c --- /dev/null +++ b/packer/azure/.pipelines/stages.yaml @@ -0,0 +1,57 @@ +# Required pipeline variables: +# - BUILD_POOL - Azure DevOps build pool to use +# - CONTAINER_IMAGE - Dev container image URL to use. Should have Azure CLI, Packer and Ansible. +# - AZURE_TENANT_ID_VHD - tenant ID to build the vhd +# - AZURE_CLIENT_ID_VHD - Service principal ID to build the vhd +# - AZURE_CLIENT_SECRET_VHD - Service principal secret to build the vhd +# - AZURE_SUBSCRIPTION_ID_VHD - Subscription ID to build the vhd +# - AZURE_TENANT_ID_SKU - tenant ID to PUT the SKU +# - AZURE_CLIENT_ID_SKU - Service principal ID to PUT the SKU +# - AZURE_CLIENT_SECRET_SKU - Service principal secret to PUT the SKU +# - KUBERNETES_VERSION - version of Kubernetes to create the sku for, e.g. `1.16.2` +# - PUBLISHER - the name of the publisher to create the sku for +# - OFFER - the name of the offer to create the sku for +# - SKU_TEMPLATE_FILE - the base template file to use for the sku +# - OS - target of build e.g. `Ubuntu/Windows` +# - OS_VERSION - target of build e.g. `18.04/2004/2019` + +trigger: none +pr: none + +stages: + - stage: vhd + jobs: + - template: build-vhd.yaml + variables: + AZURE_TENANT_ID: $(AZURE_TENANT_ID_VHD) + AZURE_CLIENT_ID: $(AZURE_CLIENT_ID_VHD) + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET_VHD) + AZURE_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID_VHD) + + - stage: test + condition: and(succeeded(), eq(variables.CLEANUP, 'False')) + jobs: + - template: test-vhd.yaml + variables: + AZURE_TENANT_ID: $(AZURE_TENANT_ID_VHD) + AZURE_CLIENT_ID: $(AZURE_CLIENT_ID_VHD) + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET_VHD) + AZURE_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID_VHD) + + - stage: sku + condition: and(succeeded(), eq(variables.CLEANUP, 'False')) + jobs: + - template: create-sku.yaml + variables: + AZURE_TENANT_ID: $(AZURE_TENANT_ID_SKU) + AZURE_CLIENT_ID: $(AZURE_CLIENT_ID_SKU) + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET_SKU) + + - stage: disk_version + condition: and(succeeded(), eq(variables.CLEANUP, 'False')) + jobs: + - template: create-disk-version.yaml + variables: + AZURE_TENANT_ID: $(AZURE_TENANT_ID_SKU) + AZURE_CLIENT_ID: $(AZURE_CLIENT_ID_SKU) + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET_SKU) diff --git a/packer/azure/.pipelines/test-vhd.yaml b/packer/azure/.pipelines/test-vhd.yaml new file mode 100644 index 0000000..8c0a282 --- /dev/null +++ b/packer/azure/.pipelines/test-vhd.yaml @@ -0,0 +1,143 @@ +# Required pipeline variables: +# - BUILD_POOL - Azure DevOps build pool to use +# - CONTAINER_IMAGE - Dev container image URL to use. Should have Azure CLI, Packer, and Ansible. +# - AZ_CAPI_EXTENSION_URL - URL to the Azure CAPI extension build. +# - AZURE_TENANT_ID - tenant ID +# - AZURE_CLIENT_ID - Service principal ID +# - AZURE_CLIENT_SECRET - Service principal secret +# - AZURE_SUBSCRIPTION_ID - Subscription ID used by the pipeline +# - KUBERNETES_VERSION - version of Kubernetes to build the image with, e.g. `1.16.2` +# - OS - target of build e.g. `Ubuntu/Windows` +# - OS_VERSION - target of build e.g. `18.04/2004/2019` + +jobs: +- job: test_vhd + container: $[ variables['CONTAINER_IMAGE'] ] + timeoutInMinutes: 120 + strategy: + maxParallel: 0 + pool: + name: $(BUILD_POOL) + steps: + - task: DownloadPipelineArtifact@2 + inputs: + source: current + artifact: publishing-info + path: $(system.defaultWorkingDirectory)/images/capi/packer/azure/vhd/ + - script: | + set -x + set -e -o pipefail + + VHD_RESOURCE_ID=$(jq -r .vhd_base_url $(system.defaultWorkingDirectory)/images/capi/packer/azure/vhd/vhd-publishing-info.json) + STORAGE_ACCOUNT_NAME=$(jq -r .storage_account_name $(system.defaultWorkingDirectory)/images/capi/packer/azure/vhd/vhd-publishing-info.json) + TAGS=$(jq -r .tags $(system.defaultWorkingDirectory)/images/capi/packer/azure/vhd/vhd-publishing-info.json) + + echo "##vso[task.setvariable variable=VHD_RESOURCE_ID]$VHD_RESOURCE_ID" + echo "##vso[task.setvariable variable=STORAGE_ACCOUNT_NAME]$STORAGE_ACCOUNT_NAME" + echo "##vso[task.setvariable variable=TAGS;]$TAGS" + displayName: Import variables from build vhd job + env: + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) + - script: | + set -x + set -e -o pipefail + + RANDOM=$(bash -c 'echo $RANDOM') + RESOURCE_GROUP="capi-testvmimage-${RANDOM}" + echo "${RESOURCE_GROUP}" is the group + + # Azure CLI login + az login -u $AZURE_CLIENT_ID -p $AZURE_CLIENT_SECRET --service-principal --tenant $AZURE_TENANT_ID + + # Find the VHD blob location from its storage account + AZURE_LOCATION=$(az storage account show --name "${STORAGE_ACCOUNT_NAME}" --query '[location]' -o tsv) + + # Create the resource group + az group create --name "${RESOURCE_GROUP}" --location "${AZURE_LOCATION}" --tags "${TAGS}" + + # Create a managed image from the VHD blob + OS_TYPE="Linux" + if [ "$OS" == "Windows" ]; then + OS_TYPE="Windows" + fi + az image create -n testvmimage -g "${RESOURCE_GROUP}" --os-type "${OS_TYPE}" --source "${VHD_RESOURCE_ID}" + + # Pass the managed image resource ID on to the next step + IMAGE_ID=$(az image show -g "${RESOURCE_GROUP}" -n testvmimage --query '[id]' --output tsv) + echo "##vso[task.setvariable variable=RESOURCE_GROUP;]$RESOURCE_GROUP" + echo "##vso[task.setvariable variable=MANAGED_IMAGE_ID;]$IMAGE_ID" + echo "##vso[task.setvariable variable=AZURE_LOCATION;]$AZURE_LOCATION" + displayName: promote VHD blob to managed image + env: + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) + - template: k8s-config.yaml + - script: | + set -x + set -e -o pipefail + + export PATH=${PATH}:.local/bin + ./packer/azure/scripts/ensure-kustomize.sh + + # Generate cluster template with kustomize + if [ "$OS" == "Windows" ]; then + kustomize build --load-restrictor LoadRestrictionsNone $(system.defaultWorkingDirectory)/images/capi/packer/azure/scripts/test-templates/windows/ > $(system.defaultWorkingDirectory)/images/capi/packer/azure/scripts/test-templates/cluster-template.yaml + else + kustomize build --load-restrictor LoadRestrictionsNone $(system.defaultWorkingDirectory)/images/capi/packer/azure/scripts/test-templates/linux/ > $(system.defaultWorkingDirectory)/images/capi/packer/azure/scripts/test-templates/cluster-template.yaml + fi + TEST_TEMPLATE=$(system.defaultWorkingDirectory)/images/capi/packer/azure/scripts/test-templates/cluster-template.yaml + echo "##vso[task.setvariable variable=TEST_TEMPLATE;]$TEST_TEMPLATE" + displayName: generate cluster template + workingDirectory: '$(system.defaultWorkingDirectory)/images/capi' + env: + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) + - script: | + set -x + set -e -o pipefail + + os=$(echo "$OS" | tr '[:upper:]' '[:lower:]') + + # Set up the Azure CLI Cluster API extension + # https://github.com/Azure/azure-capi-cli-extension/releases/download/az-capi-nightly/capi-0.0.vnext-py2.py3-none-any.whl + az extension add --yes --source "${AZ_CAPI_EXTENSION_URL}" + + # Install required binaries + mkdir ~/test-binaries + export PATH=${PATH}:~/test-binaries + az capi install -a -ip ~/test-binaries + + echo "##vso[task.setvariable variable=PATH;]$PATH" + displayName: Install and configure az capi extension + env: + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) + - script: | + params=() + if [ "$OS" == "Windows" ]; then + params+=(--windows) + fi + + # Create a cluster + az capi create \ + --yes \ + --debug \ + --name testvm \ + --kubernetes-version="${KUBERNETES_VERSION}" \ + --location="${AZURE_LOCATION}" \ + --resource-group="${RESOURCE_GROUP}" \ + --management-cluster-resource-group-name="${RESOURCE_GROUP}" \ + --control-plane-machine-count=1 \ + --node-machine-count=1 \ + --template="${TEST_TEMPLATE}" \ + --tags="${TAGS}" \ + --wait-for-nodes=2 \ + "${params[@]}" + displayName: Create a cluster + env: + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) + - script: | + set -x + set -e -o pipefail + + # Clean up the test resource group + az group delete -n "${RESOURCE_GROUP}" --yes --no-wait + displayName: Clean up test resource group + condition: always() diff --git a/packer/azure/.pipelines/vhd-publishing-info.yaml b/packer/azure/.pipelines/vhd-publishing-info.yaml new file mode 100644 index 0000000..d0928c3 --- /dev/null +++ b/packer/azure/.pipelines/vhd-publishing-info.yaml @@ -0,0 +1,19 @@ +steps: +- script: | + VHD_BASE_URL="$(cat packer/azure/vhd-base-url.out)" + VHD_URL="$(cat packer/azure/vhd-url.out)" + STORAGE_ACCOUNT_NAME="$(cat packer/azure/storage-account-name.out)" + RESOURCE_GROUP_NAME="$(cat packer/azure/resource-group-name.out)" + TAGS="$(cat packer/azure/tags.out)" + cat < packer/azure/vhd-publishing-info.json + { + "vhd_base_url": "${VHD_BASE_URL}", + "vhd_url": "${VHD_URL}", + "storage_account_name": "${STORAGE_ACCOUNT_NAME}", + "resource_group_name": "${RESOURCE_GROUP_NAME}", + "tags": "${TAGS}" + } + EOF + displayName: Generating publishing info for VHD + workingDirectory: '$(system.defaultWorkingDirectory)/images/capi' + condition: eq(variables.CLEANUP, 'False') diff --git a/packer/azure/OWNERS b/packer/azure/OWNERS new file mode 100644 index 0000000..6c303be --- /dev/null +++ b/packer/azure/OWNERS @@ -0,0 +1,8 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - cluster-api-azure-maintainers + +reviewers: + - cluster-api-azure-maintainers + - image-builder-azure-reviewers diff --git a/packer/azure/azure-config.json b/packer/azure/azure-config.json new file mode 100644 index 0000000..c914195 --- /dev/null +++ b/packer/azure/azure-config.json @@ -0,0 +1,8 @@ +{ + "azure_location": "{{env `AZURE_LOCATION`}}", + "client_id": "{{env `AZURE_CLIENT_ID`}}", + "client_secret": "{{env `AZURE_CLIENT_SECRET`}}", + "containerd_wasm_shims_runtimes": "spin,slight", + "subscription_id": "{{env `AZURE_SUBSCRIPTION_ID`}}", + "vm_size": "Standard_B2ms" +} diff --git a/packer/azure/azure-sig-gen2.json b/packer/azure/azure-sig-gen2.json new file mode 100644 index 0000000..74bada0 --- /dev/null +++ b/packer/azure/azure-sig-gen2.json @@ -0,0 +1,7 @@ +{ + "image_name": "capi-{{user `distribution`}}-{{user `distribution_version`}}-gen2", + "replication_regions": "{{env `AZURE_LOCATION`}}", + "resource_group_name": "{{env `RESOURCE_GROUP_NAME`}}", + "shared_image_gallery_name": "{{env `GALLERY_NAME`}}", + "sig_image_version": "0.3.{{user `build_timestamp`}}" +} diff --git a/packer/azure/azure-sig.json b/packer/azure/azure-sig.json new file mode 100644 index 0000000..5eeaae9 --- /dev/null +++ b/packer/azure/azure-sig.json @@ -0,0 +1,7 @@ +{ + "image_name": "capi-{{user `distribution`}}-{{user `distribution_version`}}", + "replication_regions": "{{env `AZURE_LOCATION`}}", + "resource_group_name": "{{env `RESOURCE_GROUP_NAME`}}", + "shared_image_gallery_name": "{{env `GALLERY_NAME`}}", + "sig_image_version": "0.3.{{user `build_timestamp`}}" +} diff --git a/packer/azure/azure-vhd.json b/packer/azure/azure-vhd.json new file mode 100644 index 0000000..20296cb --- /dev/null +++ b/packer/azure/azure-vhd.json @@ -0,0 +1,5 @@ +{ + "capture_container_name": "cluster-api-vhds", + "resource_group_name": "{{env `RESOURCE_GROUP_NAME`}}", + "storage_account_name": "{{env `STORAGE_ACCOUNT_NAME`}}" +} diff --git a/packer/azure/centos-7-gen2.json b/packer/azure/centos-7-gen2.json new file mode 100644 index 0000000..39791c9 --- /dev/null +++ b/packer/azure/centos-7-gen2.json @@ -0,0 +1,9 @@ +{ + "build_name": "centos-7-gen2", + "distribution": "centos", + "distribution_release": "centos-7", + "distribution_version": "7", + "image_offer": "CentOS", + "image_publisher": "OpenLogic", + "image_sku": "7_7-gen2" +} diff --git a/packer/azure/centos-7.json b/packer/azure/centos-7.json new file mode 100644 index 0000000..5d02cea --- /dev/null +++ b/packer/azure/centos-7.json @@ -0,0 +1,9 @@ +{ + "build_name": "centos-7", + "distribution": "centos", + "distribution_release": "centos-7", + "distribution_version": "7", + "image_offer": "CentOS", + "image_publisher": "OpenLogic", + "image_sku": "7.7" +} diff --git a/packer/azure/flatcar-gen2.json b/packer/azure/flatcar-gen2.json new file mode 100644 index 0000000..6141216 --- /dev/null +++ b/packer/azure/flatcar-gen2.json @@ -0,0 +1,23 @@ +{ + "ansible_extra_vars": "ansible_python_interpreter=/opt/pypy/bin/pypy", + "build_name": "flatcar-gen2", + "crictl_source_type": "http", + "distribution": "flatcar", + "distribution_release": "{{env `FLATCAR_CHANNEL`}}", + "distribution_version": "{{env `FLATCAR_CHANNEL`}}-{{env `FLATCAR_VERSION`}}", + "image_offer": "flatcar-container-linux-free", + "image_publisher": "kinvolk", + "image_sku": "{{env `FLATCAR_CHANNEL`}}-gen2", + "image_version": "{{env `FLATCAR_VERSION` }}", + "kubernetes_cni_source_type": "http", + "kubernetes_source_type": "http", + "plan_image_offer": "{{user `image_offer`}}", + "plan_image_publisher": "{{user `image_publisher`}}", + "plan_image_sku": "{{user `image_sku`}}", + "python_path": "/opt/pypy/site-packages", + "root_device_name": "/dev/sda", + "ssh_username": "core", + "systemd_prefix": "/etc/systemd", + "sysusr_prefix": "/opt", + "sysusrlocal_prefix": "/opt" +} diff --git a/packer/azure/flatcar.json b/packer/azure/flatcar.json new file mode 100644 index 0000000..4b75526 --- /dev/null +++ b/packer/azure/flatcar.json @@ -0,0 +1,23 @@ +{ + "ansible_extra_vars": "ansible_python_interpreter=/opt/pypy/bin/pypy", + "build_name": "flatcar", + "crictl_source_type": "http", + "distribution": "flatcar", + "distribution_release": "{{env `FLATCAR_CHANNEL`}}", + "distribution_version": "{{env `FLATCAR_CHANNEL`}}-{{env `FLATCAR_VERSION`}}", + "image_offer": "flatcar-container-linux-free", + "image_publisher": "kinvolk", + "image_sku": "{{env `FLATCAR_CHANNEL`}}", + "image_version": "{{env `FLATCAR_VERSION` }}", + "kubernetes_cni_source_type": "http", + "kubernetes_source_type": "http", + "plan_image_offer": "{{user `image_offer`}}", + "plan_image_publisher": "{{user `image_publisher`}}", + "plan_image_sku": "{{user `image_sku`}}", + "python_path": "/opt/pypy/site-packages", + "root_device_name": "/dev/sda", + "ssh_username": "core", + "systemd_prefix": "/etc/systemd", + "sysusr_prefix": "/opt", + "sysusrlocal_prefix": "/opt" +} diff --git a/packer/azure/packer-windows.json b/packer/azure/packer-windows.json new file mode 100644 index 0000000..ae5d815 --- /dev/null +++ b/packer/azure/packer-windows.json @@ -0,0 +1,244 @@ +{ + "builders": [ + { + "azure_tags": { + "build_date": "{{isotime}}", + "build_timestamp": "{{user `build_timestamp`}}", + "creationTimestamp": "{{isotime \"2006-01-02T15:04:05Z\"}}", + "image_builder_version": "{{user `ib_version`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}", + "os_version": "{{user `image_sku`}}" + }, + "capture_container_name": "{{user `capture_container_name`}}", + "capture_name_prefix": "capi-{{user `build_timestamp`}}", + "client_id": "{{user `client_id`}}", + "client_secret": "{{user `client_secret`}}", + "communicator": "winrm", + "image_offer": "{{user `image_offer` }}", + "image_publisher": "{{user `image_publisher` }}", + "image_sku": "{{user `image_sku`}}", + "image_version": "{{user `image_version`}}", + "location": "{{user `azure_location`}}", + "name": "vhd-{{user `build_name`}}", + "os_disk_size_gb": "{{user `os_disk_size_gb`}}", + "os_type": "Windows", + "private_virtual_network_with_public_ip": "{{user `private_virtual_network_with_public_ip`}}", + "resource_group_name": "{{user `resource_group_name`}}", + "storage_account": "{{user `storage_account_name`}}", + "subscription_id": "{{user `subscription_id`}}", + "type": "azure-arm", + "virtual_network_name": "{{user `virtual_network_name`}}", + "virtual_network_resource_group_name": "{{user `virtual_network_resource_group_name`}}", + "virtual_network_subnet_name": "{{user `virtual_network_subnet_name`}}", + "vm_size": "{{user `vm_size`}}", + "winrm_insecure": true, + "winrm_timeout": "10m", + "winrm_use_ssl": true, + "winrm_username": "packer" + }, + { + "azure_tags": { + "build_date": "{{isotime}}", + "build_timestamp": "{{user `build_timestamp`}}", + "creationTimestamp": "{{isotime \"2006-01-02T15:04:05Z\"}}", + "image_builder_version": "{{user `ib_version`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}", + "os_version": "{{user `image_sku`}}" + }, + "client_id": "{{user `client_id`}}", + "client_secret": "{{user `client_secret`}}", + "communicator": "winrm", + "image_offer": "{{user `image_offer` }}", + "image_publisher": "{{user `image_publisher` }}", + "image_sku": "{{user `image_sku`}}", + "image_version": "{{user `image_version`}}", + "location": "{{user `azure_location`}}", + "managed_image_name": "{{user `image_name`}}-{{user `runtime`}}-{{user `build_timestamp`}}", + "managed_image_resource_group_name": "{{user `resource_group_name`}}", + "managed_image_storage_account_type": "{{user `storage_account_type`}}", + "name": "sig-{{user `build_name`}}", + "os_disk_size_gb": "{{user `os_disk_size_gb`}}", + "os_type": "Windows", + "private_virtual_network_with_public_ip": "{{user `private_virtual_network_with_public_ip`}}", + "shared_gallery_image_version_exclude_from_latest": "{{ user `exclude_from_latest` }}", + "shared_image_gallery": { + "community_gallery_image_id": "{{ user `community_gallery_image_id` }}", + "direct_shared_gallery_image_id": "{{ user `direct_shared_gallery_image_id` }}", + "gallery_name": "{{user `source_sig_name`}}", + "image_name": "{{user `source_sig_image_name`}}", + "image_version": "{{user `source_sig_image_version`}}", + "resource_group": "{{user `source_sig_resource_group_name`}}", + "subscription": "{{user `source_sig_subscription_id`}}" + }, + "shared_image_gallery_destination": { + "gallery_name": "{{user `shared_image_gallery_name`}}", + "image_name": "{{user `image_name`}}-{{user `runtime`}}", + "image_version": "{{user `sig_image_version`}}", + "replication_regions": "{{user `replication_regions`}}", + "resource_group": "{{user `resource_group_name`}}", + "storage_account_type": "{{user `storage_account_type`}}" + }, + "subscription_id": "{{user `subscription_id`}}", + "type": "azure-arm", + "virtual_network_name": "{{user `virtual_network_name`}}", + "virtual_network_resource_group_name": "{{user `virtual_network_resource_group_name`}}", + "virtual_network_subnet_name": "{{user `virtual_network_subnet_name`}}", + "vm_size": "{{user `vm_size`}}", + "winrm_insecure": true, + "winrm_timeout": "10m", + "winrm_use_ssl": true, + "winrm_username": "packer" + } + ], + "post-processors": [ + { + "custom_data": { + "build_date": "{{isotime}}", + "build_name": "{{user `build_name`}}", + "build_timestamp": "{{user `build_timestamp`}}", + "build_type": "node", + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_semver": "{{user `kubernetes_cni_semver`}}", + "kubernetes_semver": "{{user `kubernetes_semver`}}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "os_name": "{{user `distro_name`}}", + "resource_group_name": "{{user `resource_group_name`}}", + "storage_account_name": "{{user `storage_account_name`}}" + }, + "output": "{{user `manifest_output`}}", + "strip_path": true, + "type": "manifest" + } + ], + "provisioners": [ + { + "elevated_password": "{{.WinRMPassword}}", + "elevated_user": "packer", + "script": "ansible/windows/ansible_winrm.ps1", + "type": "powershell" + }, + { + "extra_arguments": [ + "-e", + "ansible_winrm_server_cert_validation=ignore ansible_winrm_operation_timeout_sec=120 ansible_winrm_read_timeout_sec=150", + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `azure_extra_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--extra-vars", + "gmsa_keyvault_url={{user `gmsa_keyvault_url`}}" + ], + "max_retries": 5, + "pause_before": "15s", + "playbook_file": "ansible/windows/node_windows.yml", + "type": "ansible", + "use_proxy": false, + "user": "packer" + }, + { + "restart_timeout": "10m", + "type": "windows-restart" + }, + { + "arch": "{{user `goss_arch`}}", + "download_path": "{{user `goss_download_path`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "remote_folder": "{{user `goss_remote_folder`}}", + "remote_path": "{{user `goss_remote_path`}}", + "skip_install": "{{user `goss_skip_install`}}", + "target_os": "Windows", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": false, + "vars_env": { + "GOSS_MAX_CONCURRENT": "1", + "GOSS_USE_ALPHA": "1" + }, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "OS": "{{user `distribution` | lower}}", + "PROVIDER": "azure", + "containerd_version": "{{user `containerd_version`}}", + "distribution_version": "{{user `distribution_version`}}", + "docker_ee_version": "{{user `docker_ee_version`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}", + "pause_image": "{{user `pause_image`}}", + "runtime": "{{user `runtime`}}", + "ssh_source_url": "{{user `ssh_source_url`}}" + }, + "version": "{{user `goss_version`}}" + }, + { + "inline": [ + "rm -Force -Recurse C:\\var\\log\\kubelet\\*" + ], + "type": "powershell" + }, + { + "elevated_password": "{{.WinRMPassword}}", + "elevated_user": "packer", + "script": "packer/azure/scripts/sysprep.ps1", + "type": "powershell" + } + ], + "variables": { + "additional_debug_files": null, + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_user_vars": "", + "azure_extra_vars": "wire_server_users={{user `wire_server_users`}}", + "azure_location": null, + "build_name": null, + "build_timestamp": "{{timestamp}}", + "client_id": null, + "client_secret": null, + "cloudbase_init_url": "https://github.com/cloudbase/cloudbase-init/releases/download/{{user `cloudbase_init_version`}}/CloudbaseInitSetup_{{user `cloudbase_init_version` | replace_all `.` `_` }}_x64.msi", + "cloudbase_logging_serial_port": "COM2,115200,N,8", + "cloudbase_metadata_services": "cloudbaseinit.metadata.services.azureservice.AzureService", + "cloudbase_metadata_services_unattend": "cloudbaseinit.metadata.services.base.EmptyMetadataService", + "cloudbase_plugins": "cloudbaseinit.plugins.windows.createuser.CreateUserPlugin, cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin, cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin, cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin, cloudbaseinit.plugins.windows.azureguestagent.AzureGuestAgentPlugin, cloudbaseinit.plugins.common.mtu.MTUPlugin, cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin", + "cloudbase_plugins_unattend": "cloudbaseinit.plugins.common.mtu.MTUPlugin", + "community_gallery_image_id": "", + "containerd_url": "", + "containerd_version": null, + "direct_shared_gallery_image_id": "", + "exclude_from_latest": "false", + "gmsa_keyvault_url": "https://kubernetesartifacts.azureedge.net/ccgakvplugin/v1.1.4/binaries/windows-gmsa-ccgakvplugin-v1.1.4.zip", + "ib_version": "{{env `IB_VERSION`}}", + "image_offer": "", + "image_publisher": "", + "image_sku": "", + "image_version": "latest", + "kubernetes_base_url": "https://kubernetesreleases.blob.core.windows.net/kubernetes/{{user `kubernetes_semver`}}/binaries/node/windows/{{user `kubernetes_goarch`}}", + "manifest_output": "manifest.json", + "nssm_url": null, + "os_disk_size_gb": "", + "prepull": null, + "private_virtual_network_with_public_ip": "", + "source_sig_image_name": "", + "source_sig_image_version": "", + "source_sig_name": "", + "source_sig_resource_group_name": "", + "source_sig_subscription_id": "", + "storage_account_type": "", + "subscription_id": null, + "virtual_network_name": "", + "virtual_network_resource_group_name": "", + "virtual_network_subnet_name": "", + "vm_size": "", + "windows_service_manager": null, + "windows_updates_kbs": null, + "wins_url": "https://github.com/rancher/wins/releases/download/v{{user `wins_version`}}/wins.exe", + "wire_server_users": "" + } +} diff --git a/packer/azure/packer.json b/packer/azure/packer.json new file mode 100644 index 0000000..0447350 --- /dev/null +++ b/packer/azure/packer.json @@ -0,0 +1,276 @@ +{ + "builders": [ + { + "azure_tags": { + "build_date": "{{isotime}}", + "build_timestamp": "{{user `build_timestamp`}}", + "creationTimestamp": "{{isotime \"2006-01-02T15:04:05Z\"}}", + "distribution": "{{user `distribution`}}", + "distribution_release": "{{user `distribution_release`}}", + "distribution_version": "{{user `distribution_version`}}", + "image_builder_version": "{{user `ib_version`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}" + }, + "capture_container_name": "{{user `capture_container_name`}}", + "capture_name_prefix": "capi-{{user `build_timestamp`}}", + "client_id": "{{user `client_id`}}", + "client_secret": "{{user `client_secret`}}", + "image_offer": "{{user `image_offer` }}", + "image_publisher": "{{user `image_publisher` }}", + "image_sku": "{{user `image_sku`}}", + "image_version": "{{user `image_version`}}", + "location": "{{user `azure_location`}}", + "name": "vhd-{{user `build_name`}}", + "os_disk_size_gb": "{{user `os_disk_size_gb`}}", + "os_type": "Linux", + "private_virtual_network_with_public_ip": "{{user `private_virtual_network_with_public_ip`}}", + "resource_group_name": "{{user `resource_group_name`}}", + "ssh_username": "packer", + "storage_account": "{{user `storage_account_name`}}", + "subscription_id": "{{user `subscription_id`}}", + "type": "azure-arm", + "virtual_network_name": "{{user `virtual_network_name`}}", + "virtual_network_resource_group_name": "{{user `virtual_network_resource_group_name`}}", + "virtual_network_subnet_name": "{{user `virtual_network_subnet_name`}}", + "vm_size": "{{user `vm_size`}}" + }, + { + "azure_tags": { + "build_date": "{{isotime}}", + "build_timestamp": "{{user `build_timestamp`}}", + "creationTimestamp": "{{isotime \"2006-01-02T15:04:05Z\"}}", + "distribution": "{{user `distribution`}}", + "distribution_release": "{{user `distribution_release`}}", + "distribution_version": "{{user `distribution_version`}}", + "image_builder_version": "{{user `ib_version`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}" + }, + "client_id": "{{user `client_id`}}", + "client_secret": "{{user `client_secret`}}", + "image_offer": "{{user `image_offer` }}", + "image_publisher": "{{user `image_publisher` }}", + "image_sku": "{{user `image_sku`}}", + "image_version": "{{user `image_version`}}", + "location": "{{user `azure_location`}}", + "managed_image_name": "{{user `image_name`}}-{{user `build_timestamp`}}", + "managed_image_resource_group_name": "{{user `resource_group_name`}}", + "managed_image_storage_account_type": "{{user `storage_account_type`}}", + "name": "sig-{{user `build_name`}}", + "os_disk_size_gb": "{{user `os_disk_size_gb`}}", + "os_type": "Linux", + "plan_info": { + "plan_name": "{{user `plan_image_sku`}}", + "plan_product": "{{user `plan_image_offer`}}", + "plan_publisher": "{{user `plan_image_publisher`}}" + }, + "private_virtual_network_with_public_ip": "{{user `private_virtual_network_with_public_ip`}}", + "shared_gallery_image_version_exclude_from_latest": "{{ user `exclude_from_latest` }}", + "shared_image_gallery": { + "community_gallery_image_id": "{{ user `community_gallery_image_id` }}", + "direct_shared_gallery_image_id": "{{ user `direct_shared_gallery_image_id` }}", + "gallery_name": "{{user `source_sig_name`}}", + "image_name": "{{user `source_sig_image_name`}}", + "image_version": "{{user `source_sig_image_version`}}", + "resource_group": "{{user `source_sig_resource_group_name`}}", + "subscription": "{{user `source_sig_subscription_id`}}" + }, + "shared_image_gallery_destination": { + "gallery_name": "{{user `shared_image_gallery_name`}}", + "image_name": "{{user `image_name`}}", + "image_version": "{{user `sig_image_version`}}", + "replication_regions": "{{user `replication_regions`}}", + "resource_group": "{{user `resource_group_name`}}", + "storage_account_type": "{{user `storage_account_type`}}" + }, + "ssh_username": "packer", + "subscription_id": "{{user `subscription_id`}}", + "type": "azure-arm", + "virtual_network_name": "{{user `virtual_network_name`}}", + "virtual_network_resource_group_name": "{{user `virtual_network_resource_group_name`}}", + "virtual_network_subnet_name": "{{user `virtual_network_subnet_name`}}", + "vm_size": "{{user `vm_size`}}" + } + ], + "post-processors": [ + { + "custom_data": { + "build_date": "{{isotime}}", + "build_name": "{{user `build_name`}}", + "build_timestamp": "{{user `build_timestamp`}}", + "build_type": "node", + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_semver": "{{user `kubernetes_cni_semver`}}", + "kubernetes_semver": "{{user `kubernetes_semver`}}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "os_name": "{{user `distro_name`}}", + "resource_group_name": "{{user `resource_group_name`}}", + "storage_account_name": "{{user `storage_account_name`}}" + }, + "output": "{{user `manifest_output`}}", + "strip_path": true, + "type": "manifest" + } + ], + "provisioners": [ + { + "environment_vars": [ + "BUILD_NAME={{user `build_name`}}" + ], + "inline": [ + "if [ $BUILD_NAME != \"ubuntu-1804\" ] && [ $BUILD_NAME != \"ubuntu-1804-gen2\" ]; then exit 0; fi", + "while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done", + "sudo apt-get -qq update && sudo DEBIAN_FRONTEND=noninteractive apt-get -qqy install python python-pip" + ], + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/python.yml", + "type": "ansible", + "user": "packer" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/node.yml", + "type": "ansible", + "user": "packer" + }, + { + "arch": "{{user `goss_arch`}}", + "download_path": "{{user `goss_download_path`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "remote_folder": "{{user `goss_remote_folder`}}", + "remote_path": "{{user `goss_remote_path`}}", + "skip_install": "{{user `goss_skip_install`}}", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": true, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "ARCH": "amd64", + "OS": "{{user `distribution` | lower}}", + "OS_VERSION": "{{user `distribution_version` | lower}}", + "PROVIDER": "azure", + "containerd_version": "{{user `containerd_version`}}", + "containerd_wasm_shims_runtimes": "{{user `containerd_wasm_shims_runtimes` }}", + "kubernetes_cni_deb_version": "{{ user `kubernetes_cni_deb_version` }}", + "kubernetes_cni_rpm_version": "{{ split (user `kubernetes_cni_rpm_version`) \"-\" 0 }}", + "kubernetes_cni_source_type": "{{user `kubernetes_cni_source_type`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver` | replace \"v\" \"\" 1}}", + "kubernetes_deb_version": "{{ user `kubernetes_deb_version` }}", + "kubernetes_rpm_version": "{{ split (user `kubernetes_rpm_version`) \"-\" 0 }}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "kubernetes_version": "{{user `kubernetes_semver` | replace \"v\" \"\" 1}}" + }, + "version": "{{user `goss_version`}}" + }, + { + "environment_vars": [ + "BUILD_NAME={{user `build_name`}}" + ], + "inline": [ + "if [[ $BUILD_NAME != \"flatcar\"* ]]; then exit 0; fi", + "sudo bash -c \"/usr/share/oem/python/bin/python /usr/share/oem/bin/waagent -force -deprovision+user && sync\"" + ], + "inline_shebang": "/bin/bash -x", + "remote_folder": "{{user `provisioner_remote_folder`}}", + "type": "shell" + } + ], + "variables": { + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_scp_extra_args": "", + "ansible_user_vars": "", + "azure_location": null, + "build_name": null, + "build_resource_group_name": "{{ env `BUILD_RESOURCE_GROUP_NAME` }}", + "build_timestamp": "{{timestamp}}", + "client_id": null, + "client_secret": null, + "community_gallery_image_id": "", + "containerd_sha256": null, + "containerd_url": "https://github.com/containerd/containerd/releases/download/v{{user `containerd_version`}}/cri-containerd-cni-{{user `containerd_version`}}-linux-amd64.tar.gz", + "containerd_version": null, + "containerd_wasm_shims_runtimes": null, + "crictl_url": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-amd64.tar.gz", + "crictl_version": null, + "direct_shared_gallery_image_id": "", + "distribution": null, + "distribution_release": null, + "distribution_version": null, + "exclude_from_latest": "false", + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "ib_version": "{{env `IB_VERSION`}}", + "image_offer": "", + "image_publisher": "", + "image_sku": "", + "image_version": "latest", + "kubernetes_cni_deb_version": null, + "kubernetes_cni_http_source": null, + "kubernetes_cni_rpm_version": null, + "kubernetes_cni_semver": null, + "kubernetes_cni_source_type": null, + "kubernetes_container_registry": null, + "kubernetes_deb_gpg_key": null, + "kubernetes_deb_repo": null, + "kubernetes_deb_version": null, + "kubernetes_http_source": null, + "kubernetes_load_additional_imgs": null, + "kubernetes_rpm_gpg_check": null, + "kubernetes_rpm_gpg_key": null, + "kubernetes_rpm_repo": null, + "kubernetes_rpm_version": null, + "kubernetes_semver": null, + "kubernetes_series": null, + "kubernetes_source_type": null, + "manifest_output": "manifest.json", + "os_disk_size_gb": "", + "plan_image_offer": "", + "plan_image_publisher": "", + "plan_image_sku": "", + "private_virtual_network_with_public_ip": "", + "provisioner_remote_folder": "/tmp", + "source_sig_image_name": "", + "source_sig_image_version": "", + "source_sig_name": "", + "source_sig_resource_group_name": "", + "source_sig_subscription_id": "", + "storage_account_type": "", + "subscription_id": null, + "virtual_network_name": "", + "virtual_network_resource_group_name": "", + "virtual_network_subnet_name": "", + "vm_size": "" + } +} diff --git a/packer/azure/rhel-8.json b/packer/azure/rhel-8.json new file mode 100644 index 0000000..6796dc4 --- /dev/null +++ b/packer/azure/rhel-8.json @@ -0,0 +1,11 @@ +{ + "build_name": "rhel-8", + "distribution": "rhel", + "distribution_release": "rhel-8", + "distribution_version": "8", + "epel_rpm_gpg_key": "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8", + "image_offer": "RHEL", + "image_publisher": "RedHat", + "image_sku": "8_7", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm" +} diff --git a/packer/azure/scripts/delete-unused-storage.sh b/packer/azure/scripts/delete-unused-storage.sh new file mode 100755 index 0000000..74ec66a --- /dev/null +++ b/packer/azure/scripts/delete-unused-storage.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script deletes unused Azure storage accounts created in the process of +# building CAPZ reference images. It also archives existing accounts into one +# main storage account to reduce the limited number of accounts in use. +# Usage: +# delete-unused-storage.sh +# +# The `pub` tool (https://github.com/devigned/pub) and the `az` CLI tool +# (https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) must be found +# in the PATH. +# +# In order to run this script, log in to the publishing account with the +# `az account set -s ` command. Then export these environment +# variables to enable access to the storage accounts: +# AZURE_CLIENT_ID +# AZURE_CLIENT_SECRET +# AZURE_SUBSCRIPTION_ID +# AZURE_TENANT_ID +# +# By default, the script will not modify any resources. Pass the environment variable +# DRYRUN=false to enable the script to archive and to delete the storage accounts. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +RESOURCE_GROUP=${RESOURCE_GROUP:-cluster-api-images} +PUBLISHER=${PUBLISHER:-cncf-upstream} +OFFERS=${OFFERS:-capi capi-windows} +PREFIX=${PREFIX:-capi} +LONG_PREFIX=${LONG_PREFIX:-${PREFIX}[0-9]{10\}} +ARCHIVE_STORAGE_ACCOUNT=${ARCHIVE_STORAGE_ACCOUNT:-${PREFIX}archive} +DRYRUN=${DRYRUN:-true} +RED='\033[0;31m' +NC='\033[0m' + +if ${DRYRUN}; then + echo "DRYRUN: This script will not copy or delete any resources." + ECHO=echo +else + ECHO= +fi + +which pub &> /dev/null || (echo "Please install pub from https://github.com/devigned/pub/releases" && exit 1) + +# Get URLs in use by the marketplace offers +URLS="" +for name in ${OFFERS}; do + echo "Getting URLs for ${name}..." + offer=$(pub offers show -p "$PUBLISHER" -o "$name") + # Capture "label" as well as "osVhdUrl" so we can archive storage accounts with something readable. + urls=$(echo "${offer}" | jq -r '.definition["plans"][]."microsoft-azure-corevm.vmImagesPublicAzure"[] | [.label, .osVhdUrl] | @csv') + if [[ -z $URLS ]]; then + URLS=${urls} + else + URLS=${URLS}$'\n'${urls} + fi +done +NOW=$(date +%s) + +# ensure the existence of the archive storage account +if ! az storage account show -g "${RESOURCE_GROUP}" -n "${ARCHIVE_STORAGE_ACCOUNT}" &> /dev/null; then + echo "Creating archive storage account ${ARCHIVE_STORAGE_ACCOUNT}..." + $ECHO az storage account create -g "${RESOURCE_GROUP}" -n "${ARCHIVE_STORAGE_ACCOUNT}" --access-tier Cool --allow-blob-public-access false +fi + +IFS=$'\n' +archived=0 +deleted=0 +# For each storage account in the subscription, +for account in $(az storage account list -g "${RESOURCE_GROUP}" -o tsv --query "[?starts_with(name, '${PREFIX}')].[name,creationTime]"); do + IFS=$'\t' read -r storage_account creation_time <<< "$account" + created=$(date -d "${creation_time}" +%s 2>/dev/null || date -j -f "%F" "${creation_time}" +%s 2>/dev/null) + age=$(( (NOW - created) / 86400 )) + # if it's older than a month + if [[ $age -gt 30 ]]; then + # and it has the right naming pattern + if [[ ${storage_account} =~ ^${LONG_PREFIX} ]]; then + # but isn't referenced in the offer osVhdUrls + if [[ ! ${URLS} =~ ${storage_account} ]]; then + # delete it. + echo "Deleting unreferenced storage account ${storage_account} that is ${age} days old" + ${ECHO} az storage account delete -g "${RESOURCE_GROUP}" -n "${storage_account}" -y + deleted=$((deleted+1)) + else + # archive it. + for URL in ${URLS}; do + IFS=$',' read -r label url <<< "${URL}" + # container names are somewhat strict, so transform the label into a valid container name + # See https://github.com/MicrosoftDocs/azure-docs/blob/master/includes/storage-container-naming-rules-include.md + dest_label=${label//[ .]/-} + dest_label=${dest_label//[^a-zA-Z0-9-]/} + dest_label=$(echo "${dest_label}" | tr '[:upper:]' '[:lower:]') + if [[ ${url} =~ ${storage_account} ]]; then + echo "Archiving storage account ${storage_account} (${label}) that is ${age} days old" + # create a destination container + if [[ $(az storage container exists --account-name "${ARCHIVE_STORAGE_ACCOUNT}" -n "${dest_label}" -o tsv 2>/dev/null) != "True" ]]; then + ${ECHO} az storage container create --only-show-errors --public-access=container \ + -n ${dest_label} -g "${RESOURCE_GROUP}" --account-name "${ARCHIVE_STORAGE_ACCOUNT}" 2>/dev/null + fi + # for each source container + for container in $(az storage container list --only-show-errors --account-name ${storage_account} --query "[].name" -o tsv 2>/dev/null); do + # copy it to the destination container + ${ECHO} az storage blob copy start-batch \ + --account-name ${ARCHIVE_STORAGE_ACCOUNT} \ + --destination-container ${dest_label} \ + --destination-path ${container} \ + --source-container ${container} \ + --source-account-name ${storage_account} \ + --pattern '*capi-*' \ + 2>/dev/null + done + # poll the target container until all blobs have "succeeded" copy status + for target in $(az storage blob list --account-name ${ARCHIVE_STORAGE_ACCOUNT} -c ${dest_label} --query '[].name' -o tsv 2>/dev/null); do + while true; do + status=$(az storage blob show --account-name ${ARCHIVE_STORAGE_ACCOUNT} --container-name ${dest_label} --name $target -o tsv --query 'properties.copy.status' 2>/dev/null) + if [[ ${status} == "success" ]]; then + echo "Copied ${dest_label}/${target}" + break + else + echo "Copying ${dest_label}/${target} ..." + sleep 20 + fi + done + done + echo "Deleting source storage account ${storage_account}..." + ${ECHO} az storage account delete -g "${RESOURCE_GROUP}" -n "${storage_account}" -y + archived=$((archived+1)) + fi + done + echo -e "Pausing for 10 seconds. ${RED}Hit Ctrl-C to stop.${NC}" + sleep 10 + echo + fi + fi + fi +done + +echo "Deleted ${deleted} storage accounts." +echo "Archived ${archived} storage accounts." diff --git a/packer/azure/scripts/disable-windows-prepull.json b/packer/azure/scripts/disable-windows-prepull.json new file mode 100644 index 0000000..ab76d7a --- /dev/null +++ b/packer/azure/scripts/disable-windows-prepull.json @@ -0,0 +1,3 @@ +{ + "prepull": "false" +} diff --git a/packer/azure/scripts/ensure-kustomize.sh b/packer/azure/scripts/ensure-kustomize.sh new file mode 100755 index 0000000..e1c8404 --- /dev/null +++ b/packer/azure/scripts/ensure-kustomize.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# Copyright 2022 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +# Change directories to the parent directory of the one in which this +# script is located. +CAPI_ROOT=$(dirname "${BASH_SOURCE[0]}")/../../.. +cd "${CAPI_ROOT}" || exit 1 + +source hack/utils.sh + +if command -v kustomize >/dev/null 2>&1; then exit 0; fi + +mkdir -p .local/bin && cd .local/bin + +KUSTOMIZE_VERSION=4.5.2 +_binfile="kustomize-v${KUSTOMIZE_VERSION}.tar.gz" + +echo "installing kustomize" +curl -sLo "${_binfile}" "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_${HOSTOS}_${HOSTARCH}.tar.gz" +tar -zvxf "${_binfile}" -C "./" +chmod +x "./kustomize" +rm "${_binfile}" +echo "'kustomize' has been installed to $(pwd), make sure this directory is in your \$PATH" diff --git a/packer/azure/scripts/init-sig.sh b/packer/azure/scripts/init-sig.sh new file mode 100755 index 0000000..087c44f --- /dev/null +++ b/packer/azure/scripts/init-sig.sh @@ -0,0 +1,100 @@ +#!/bin/bash + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +tracestate="$(shopt -po xtrace)" +set +o xtrace +az login --service-principal -u ${AZURE_CLIENT_ID} -p ${AZURE_CLIENT_SECRET} --tenant ${AZURE_TENANT_ID} >/dev/null 2>&1 +az account set -s ${AZURE_SUBSCRIPTION_ID} >/dev/null 2>&1 +eval "$tracestate" + +export RESOURCE_GROUP_NAME="${RESOURCE_GROUP_NAME:-cluster-api-images}" +export AZURE_LOCATION="${AZURE_LOCATION:-southcentralus}" +if ! az group show -n ${RESOURCE_GROUP_NAME} -o none 2>/dev/null; then + az group create -n ${RESOURCE_GROUP_NAME} -l ${AZURE_LOCATION} --tags ${TAGS:-} +fi +CREATE_TIME="$(date +%s)" +RANDOM_SUFFIX="$(head /dev/urandom | LC_ALL=C tr -dc a-z | head -c 4 ; echo '')" +export GALLERY_NAME="${GALLERY_NAME:-ClusterAPI${CREATE_TIME}${RANDOM_SUFFIX}}" + +# Hack to set only build_resource_group_name or location, a better solution is welcome +# https://developer.hashicorp.com/packer/plugins/builders/azure/arm#build_resource_group_name +PACKER_FILE_PATH=packer/azure/ +TMP_PACKER_FILE=$PACKER_FILE_PATH"packer.json.tmp" +PACKER_FILE=$PACKER_FILE_PATH"packer.json" +if [ ${BUILD_RESOURCE_GROUP_NAME} ]; then + if ! az group show -n ${BUILD_RESOURCE_GROUP_NAME} -o none 2>/dev/null; then + az group create -n ${BUILD_RESOURCE_GROUP_NAME} -l ${AZURE_LOCATION} --tags ${TAGS:-} + fi + jq '(.builders | map(if .name | contains("sig") then del(.location) + {"build_resource_group_name": "{{user `build_resource_group_name`}}"} else . end)) as $updated | .builders = $updated' $PACKER_FILE > $TMP_PACKER_FILE + mv $TMP_PACKER_FILE $PACKER_FILE +fi + +packer validate -syntax-only $PACKER_FILE || exit 1 + +az sig create --resource-group ${RESOURCE_GROUP_NAME} --gallery-name ${GALLERY_NAME} + +create_image_definition() { + az sig image-definition create \ + --resource-group ${RESOURCE_GROUP_NAME} \ + --gallery-name ${GALLERY_NAME} \ + --gallery-image-definition capi-${1} \ + --publisher capz \ + --offer capz-demo \ + --sku ${2} \ + --hyper-v-generation ${3} \ + --os-type ${4} +} + +SIG_TARGET=$1 + +case ${SIG_TARGET} in + ubuntu-1804) + create_image_definition ${SIG_TARGET} "18.04-LTS" "V1" "Linux" + ;; + ubuntu-2004) + create_image_definition ${SIG_TARGET} "20_04-lts" "V1" "Linux" + ;; + ubuntu-2204) + create_image_definition ${SIG_TARGET} "22_04-lts" "V1" "Linux" + ;; + centos-7) + create_image_definition "centos-7" "centos-7" "V1" "Linux" + ;; + rhel-8) + create_image_definition "rhel-8" "rhel-8" "V1" "Linux" + ;; + windows-2019) + create_image_definition "windows-2019-docker-ee" "win-2019-docker-ee" "V1" "Windows" + ;; + windows-2019-containerd) + create_image_definition ${SIG_TARGET} "win-2019-containerd" "V1" "Windows" + ;; + windows-2022-containerd) + create_image_definition ${SIG_TARGET} "win-2022-containerd" "V1" "Windows" + ;; + flatcar) + SKU="flatcar-${FLATCAR_CHANNEL}-${FLATCAR_VERSION}" + create_image_definition ${SKU} ${SKU} "V1" "Linux" + ;; + ubuntu-1804-gen2) + create_image_definition ${SIG_TARGET} "18.04-lts-gen2" "V2" "Linux" + ;; + ubuntu-2004-gen2) + create_image_definition ${SIG_TARGET} "20_04-lts-gen2" "V2" "Linux" + ;; + ubuntu-2204-gen2) + create_image_definition ${SIG_TARGET} "22_04-lts-gen2" "V2" "Linux" + ;; + centos-7-gen2) + create_image_definition "centos-7-gen2" "centos-7-gen2" "V2" "Linux" + ;; + flatcar-gen2) + SKU="flatcar-${FLATCAR_CHANNEL}-${FLATCAR_VERSION}-gen2" + create_image_definition "${SKU}" "${SKU}" "V2" "Linux" + ;; + *) + >&2 echo "Unsupported SIG target: '${SIG_TARGET}'" + exit 1 + ;; +esac diff --git a/packer/azure/scripts/init-vhd.sh b/packer/azure/scripts/init-vhd.sh new file mode 100755 index 0000000..e963f9e --- /dev/null +++ b/packer/azure/scripts/init-vhd.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +echo "Sign into Azure" +tracestate="$(shopt -po xtrace)" +set +o xtrace +az login --service-principal -u ${AZURE_CLIENT_ID} -p ${AZURE_CLIENT_SECRET} --tenant ${AZURE_TENANT_ID} >/dev/null 2>&1 +az account set -s ${AZURE_SUBSCRIPTION_ID} >/dev/null 2>&1 +eval "$tracestate" + +echo "Create storage account" +export RESOURCE_GROUP_NAME="${RESOURCE_GROUP_NAME:-cluster-api-images}" +export AZURE_LOCATION="${AZURE_LOCATION:-southcentralus}" +if ! az group show -n ${RESOURCE_GROUP_NAME} -o none 2>/dev/null; then + az group create -n ${RESOURCE_GROUP_NAME} -l ${AZURE_LOCATION} --tags ${TAGS:-} +fi +CREATE_TIME="$(date +%s)" +RANDOM_SUFFIX="$(head /dev/urandom | LC_ALL=C tr -dc a-z | head -c 4 ; echo '')" +get_random_region() { + local REGIONS=("canadacentral" "eastus" "eastus2" "northeurope" "uksouth" "westeurope" "westus2" "westus3") + echo "${REGIONS[${RANDOM} % ${#REGIONS[@]}]}" +} +RANDOMIZE_STORAGE_ACCOUNT="${RANDOMIZE_STORAGE_ACCOUNT:-"false"}" +if [ "$RANDOMIZE_STORAGE_ACCOUNT" == "true" ]; then + export AZURE_LOCATION="$(get_random_region)" +fi +export STORAGE_ACCOUNT_NAME="${STORAGE_ACCOUNT_NAME:-capi${CREATE_TIME}${RANDOM_SUFFIX}}" +az storage account check-name --name ${STORAGE_ACCOUNT_NAME} +az storage account create -n ${STORAGE_ACCOUNT_NAME} -g ${RESOURCE_GROUP_NAME} -l ${AZURE_LOCATION} --allow-blob-public-access false + +echo "done" diff --git a/packer/azure/scripts/new-disk-version.sh b/packer/azure/scripts/new-disk-version.sh new file mode 100755 index 0000000..d3f1e8c --- /dev/null +++ b/packer/azure/scripts/new-disk-version.sh @@ -0,0 +1,107 @@ +#!/bin/bash -e + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +echo "PWD: $PWD" + +OS=${OS:-"Ubuntu"} +OS_VERSION=${OS_VERSION:-"18.04"} +PUB_VERSION=${PUB_VERSION:-"v0.3.3"} + +required_env_vars=( + "AZURE_CLIENT_ID" + "AZURE_CLIENT_SECRET" + "AZURE_TENANT_ID" + "OS" + "OS_VERSION" + "PUB_VERSION" +) + +for v in "${required_env_vars[@]}" +do + if [ -z "${!v}" ]; then + echo "$v was not set!" + exit 1 + fi +done + +SKU_INFO="sku/sku-publishing-info.json" +VHD_INFO="vhd/vhd-publishing-info.json" + +required_files=( + "SKU_INFO" + "VHD_INFO" +) + +for f in "${required_files[@]}" +do + if [ ! -f "${!f}" ]; then + echo "could not find file: ${!f}" + exit 1 + fi +done + +echo "Getting pub..." +(set -x ; curl -fsSL https://github.com/devigned/pub/releases/download/${PUB_VERSION}/pub_${PUB_VERSION}_linux_amd64.tar.gz -o pub; tar -xzf pub) + +echo "SKU publishing info:" +cat $SKU_INFO +echo + +echo "VHD publishing info:" +cat $VHD_INFO +echo + + +# get Kubernetes version and split into major, minor, and patch +k8s_version=$(< $SKU_INFO jq -r ".k8s_version") +IFS='.' # set period (.) as delimiter +read -ra ADDR <<< "${k8s_version}" # str is read into an array as tokens separated by IFS +IFS=' ' # reset to default value after usage +major=${ADDR[0]} +minor=${ADDR[1]} +patch=${ADDR[2]} + +# generate image version +image_version=${major}${minor}.${patch}.$(date +"%Y%m%d") + +# generate media name +sku_id=$(< $SKU_INFO jq -r ".sku_id") +media_name="${sku_id}-${image_version}" + +# generate published date +published_date=$(date +"%m/%d/%Y") + +# get vhd url +vhd_url=$(< $VHD_INFO jq -r ".vhd_url") + +label="Kubernetes $k8s_version $OS $OS_VERSION" +description="Kubernetes $k8s_version $OS $OS_VERSION" + +# create version.json +cat < version.json +{ + "$image_version" : { + "mediaName": "$media_name", + "showInGui": false, + "publishedDate": "$published_date", + "label": "$label", + "description": "$description", + "osVHdUrl": "$vhd_url" + } +} +EOF + +echo "Version info:" +cat version.json + +publisher=$(< $SKU_INFO jq -r ".publisher") +offer=$(< $SKU_INFO jq -r ".offer") +sku=$(< $SKU_INFO jq -r ".sku_id") + +# TODO: Update pub versions put to take in version.json as a file +echo "Create new disk version" +set -x +./pub_linux_amd64 versions put corevm -p $publisher -o $offer -s $sku --version $image_version --vhd-uri $vhd_url --media-name $media_name --label "$label" --desc "$description" --published-date "$published_date" +set +x +echo -e "\nCreated disk version" diff --git a/packer/azure/scripts/new-sku.sh b/packer/azure/scripts/new-sku.sh new file mode 100755 index 0000000..9b9a13f --- /dev/null +++ b/packer/azure/scripts/new-sku.sh @@ -0,0 +1,80 @@ +#!/bin/bash -e + +OS=${OS:-"Ubuntu"} +OS_VERSION=${OS_VERSION:-"18.04"} +PUB_VERSION=${PUB_VERSION:-"v0.3.3"} +VM_GENERATION=${VM_GENERATION:-"gen1"} +[[ -n ${DEBUG:-} ]] && set -o xtrace + +required_env_vars=( + "AZURE_CLIENT_ID" + "AZURE_CLIENT_SECRET" + "AZURE_TENANT_ID" + "KUBERNETES_VERSION" + "OFFER" + "OS" + "OS_VERSION" + "PUB_VERSION" + "PUBLISHER" + "SKU_TEMPLATE_FILE" + "VM_GENERATION" +) + +for v in "${required_env_vars[@]}" +do + if [ -z "${!v}" ]; then + echo "$v was not set!" + exit 1 + fi +done + +if [ ! -f "$SKU_TEMPLATE_FILE" ]; then + echo "Could not find sku template file: ${SKU_TEMPLATE_FILE}!" + exit 1 +fi + +os=$(echo "$OS" | tr '[:upper:]' '[:lower:]') +version=$(echo "$OS_VERSION" | tr '[:upper:]' '[:lower:]' | tr -d .) +sku_id="${os}-${version}-${VM_GENERATION}" + +if [ "$OS" == "Ubuntu" ]; then + os_type="Ubuntu" + os_family="Linux" +elif [ "$OS" == "Windows" ]; then + os_type="Other" + os_family="Windows" +else + echo "Cannot configure unknown OS: ${OS}!" + exit 1 +fi + +< $SKU_TEMPLATE_FILE sed s/{{ID}}/"$sku_id"/ \ + | sed s/{{KUBERNETES_VERSION}}/"$KUBERNETES_VERSION/" \ + | sed s/{{OS}}/"$OS/" \ + | sed s/{{OS_VERSION}}/"$OS_VERSION/" \ + | sed s/{{OS_TYPE}}/"$os_type/" \ + | sed s/{{OS_FAMILY}}/"$os_family/" \ + > sku.json +cat sku.json + +echo +echo "Getting pub..." +(set -x ; curl -fsSL https://github.com/devigned/pub/releases/download/${PUB_VERSION}/pub_${PUB_VERSION}_linux_amd64.tar.gz -o pub; tar -xzf pub) + +echo "Creating new SKU" +set -x +./pub_linux_amd64 skus put -p $PUBLISHER -o "$OFFER" -f sku.json +set +x +echo -e "\nCreated sku" + +echo "Writing publishing info" +cat < sku-publishing-info.json +{ + "publisher" : "$PUBLISHER", + "offer" : "$OFFER", + "sku_id" : "$sku_id", + "k8s_version" : "$KUBERNETES_VERSION" +} +EOF + +cat sku-publishing-info.json diff --git a/packer/azure/scripts/parse-prow-creds.sh b/packer/azure/scripts/parse-prow-creds.sh new file mode 100755 index 0000000..6d2cfab --- /dev/null +++ b/packer/azure/scripts/parse-prow-creds.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail +set +o xtrace + +parse_cred() { + grep -E -o "\b$1[[:blank:]]*=[[:blank:]]*\"[^[:space:]\"]+\"" | cut -d '"' -f 2 +} + + +# for Prow we use the provided AZURE_CREDENTIALS file. +# the file is expected to be in toml format. +if [[ -n "${AZURE_CREDENTIALS:-}" ]]; then + export AZURE_SUBSCRIPTION_ID="$(cat ${AZURE_CREDENTIALS} | parse_cred SubscriptionID)" + export AZURE_TENANT_ID="$(cat ${AZURE_CREDENTIALS} | parse_cred TenantID)" + export AZURE_CLIENT_ID="$(cat ${AZURE_CREDENTIALS} | parse_cred ClientID)" + export AZURE_CLIENT_SECRET="$(cat ${AZURE_CREDENTIALS} | parse_cred ClientSecret)" +fi diff --git a/packer/azure/scripts/sysprep.ps1 b/packer/azure/scripts/sysprep.ps1 new file mode 100644 index 0000000..a540be4 --- /dev/null +++ b/packer/azure/scripts/sysprep.ps1 @@ -0,0 +1,46 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Modified from https://docs.microsoft.com/en-us/azure/virtual-machines/linux/image-builder-troubleshoot#sysprep-command-windows +# The Windows Azure Guest Agent is required for sysprep: https://www.packer.io/docs/builders/azure/arm#windows +Write-Output '>>> Waiting for GA Service (RdAgent) to start ...' +while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 } +Write-Output '>>> Waiting for GA Service (WindowsAzureTelemetryService) to start ...' +while ((Get-Service WindowsAzureTelemetryService) -and ((Get-Service WindowsAzureTelemetryService).Status -ne 'Running')) { Start-Sleep -s 5 } +Write-Output '>>> Waiting for GA Service (WindowsAzureGuestAgent) to start ...' +while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 } +Write-Output '>>> Sysprepping VM ...' +if( Test-Path $Env:SystemRoot\system32\Sysprep\unattend.xml ) { + Remove-Item $Env:SystemRoot\system32\Sysprep\unattend.xml -Force +} + +$unattendedXml = "$ENV:ProgramFiles\Cloudbase Solutions\Cloudbase-Init\conf\Unattend.xml" +$FileExists = Test-Path $unattendedXml +If ($FileExists -eq $True) { + # Use the Cloudbase-init provided unattend file during install + Write-Output "Using cloudbase-init unattend file for sysprep: $unattendedXml" + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /quit /quiet /unattend:$unattendedXml +}else { + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /quit /quiet +} + +# Wait for the image to be reset +while($true) { + $imageState = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\State).ImageState + Write-Output $imageState + if ($imageState -eq 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { break } + Start-Sleep -s 5 +} + +Write-Output '>>> Sysprep complete ...' diff --git a/packer/azure/scripts/test-templates/linux/kustomization.yaml b/packer/azure/scripts/test-templates/linux/kustomization.yaml new file mode 100644 index 0000000..775b2d0 --- /dev/null +++ b/packer/azure/scripts/test-templates/linux/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - https://github.com/kubernetes-sigs/cluster-api-provider-azure/releases/download/v1.6.0/cluster-template.yaml +patchesStrategicMerge: +- ../patches/azuremachinetemplate-controlplane.yaml +- ../patches/azuremachinetemplate-workload.yaml \ No newline at end of file diff --git a/packer/azure/scripts/test-templates/patches/azuremachinetemplate-controlplane.yaml b/packer/azure/scripts/test-templates/patches/azuremachinetemplate-controlplane.yaml new file mode 100644 index 0000000..53e8216 --- /dev/null +++ b/packer/azure/scripts/test-templates/patches/azuremachinetemplate-controlplane.yaml @@ -0,0 +1,11 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-control-plane + namespace: default +spec: + template: + spec: + image: + id: ${MANAGED_IMAGE_ID} +--- \ No newline at end of file diff --git a/packer/azure/scripts/test-templates/patches/azuremachinetemplate-windows.yaml b/packer/azure/scripts/test-templates/patches/azuremachinetemplate-windows.yaml new file mode 100644 index 0000000..2abc884 --- /dev/null +++ b/packer/azure/scripts/test-templates/patches/azuremachinetemplate-windows.yaml @@ -0,0 +1,11 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-md-win + namespace: default +spec: + template: + spec: + image: + id: ${MANAGED_IMAGE_ID} +--- \ No newline at end of file diff --git a/packer/azure/scripts/test-templates/patches/azuremachinetemplate-workload.yaml b/packer/azure/scripts/test-templates/patches/azuremachinetemplate-workload.yaml new file mode 100644 index 0000000..1bc33e3 --- /dev/null +++ b/packer/azure/scripts/test-templates/patches/azuremachinetemplate-workload.yaml @@ -0,0 +1,11 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + template: + spec: + image: + id: ${MANAGED_IMAGE_ID} +--- \ No newline at end of file diff --git a/packer/azure/scripts/test-templates/patches/kubeadmcontrolplane-windows.yaml b/packer/azure/scripts/test-templates/patches/kubeadmcontrolplane-windows.yaml new file mode 100644 index 0000000..8a040a4 --- /dev/null +++ b/packer/azure/scripts/test-templates/patches/kubeadmcontrolplane-windows.yaml @@ -0,0 +1,8 @@ +apiVersion: controlplane.cluster.x-k8s.io/v1beta1 +kind: KubeadmControlPlane +metadata: + name: ${CLUSTER_NAME}-control-plane + namespace: default +spec: + version: ${KUBERNETES_BOOTSTRAP_VERSION} +--- \ No newline at end of file diff --git a/packer/azure/scripts/test-templates/patches/machinedeployment-windows.yaml b/packer/azure/scripts/test-templates/patches/machinedeployment-windows.yaml new file mode 100644 index 0000000..1c66ce2 --- /dev/null +++ b/packer/azure/scripts/test-templates/patches/machinedeployment-windows.yaml @@ -0,0 +1,8 @@ +apiVersion: cluster.x-k8s.io/v1beta1 +kind: MachineDeployment +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + replicas: 0 +--- \ No newline at end of file diff --git a/packer/azure/scripts/test-templates/windows/kustomization.yaml b/packer/azure/scripts/test-templates/windows/kustomization.yaml new file mode 100644 index 0000000..f65b590 --- /dev/null +++ b/packer/azure/scripts/test-templates/windows/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - https://github.com/kubernetes-sigs/cluster-api-provider-azure/releases/download/v1.6.0/cluster-template-windows.yaml +patchesStrategicMerge: +- ../patches/azuremachinetemplate-windows.yaml +- ../patches/kubeadmcontrolplane-windows.yaml +- ../patches/machinedeployment-windows.yaml \ No newline at end of file diff --git a/packer/azure/sku-template.json b/packer/azure/sku-template.json new file mode 100644 index 0000000..242184a --- /dev/null +++ b/packer/azure/sku-template.json @@ -0,0 +1,41 @@ +{ + "microsoft-azure-corevm.cloudAvailability": [ + "PublicAzure", + "Mooncake", + "Fairfax" + ], + "microsoft-azure-corevm.defaultImageSizeGB": "30", + "microsoft-azure-corevm.deploymentModels": [ + "ARM" + ], + "microsoft-azure-corevm.freeTierEligible": true, + "microsoft-azure-corevm.generation": "1", + "microsoft-azure-corevm.hardened": false, + "microsoft-azure-corevm.hideSKUForSolutionTemplate": true, + "microsoft-azure-corevm.imageType": "VmImage", + "microsoft-azure-corevm.imageVisibility": true, + "microsoft-azure-corevm.isPremiumThirdParty": false, + "microsoft-azure-corevm.largeLogo": "https://capiofferlogos.blob.core.windows.net/logos/large216x216", + "microsoft-azure-corevm.mediumLogo": "https://capiofferlogos.blob.core.windows.net/logos/medium90x90", + "microsoft-azure-corevm.migratedOffer": false, + "microsoft-azure-corevm.operatingSystemFamily": "{{OS_FAMILY}}", + "microsoft-azure-corevm.osType": "{{OS_TYPE}}", + "microsoft-azure-corevm.privacyURL": "https://github.com/cncf/foundation/blob/master/copyright-notices.md", + "microsoft-azure-corevm.skuDescriptionFairfax": "Base Image for building Kubernetes cluster on Azure with Cluster API. This image is not intended for use outside of https://github.com/kubernetes-sigs/cluster-api-provider-azure.", + "microsoft-azure-corevm.skuDescriptionMooncake": "Base Image for building Kubernetes cluster on Azure with Cluster API. This image is not intended for use outside of https://github.com/kubernetes-sigs/cluster-api-provider-azure.", + "microsoft-azure-corevm.skuDescriptionPublicAzure": "Base Image for building Kubernetes cluster on Azure with Cluster API. This image is not intended for use outside of https://github.com/kubernetes-sigs/cluster-api-provider-azure.", + "microsoft-azure-corevm.skuLongSummary": "Cluster API Kubernetes {{OS}} {{OS_VERSION}} Base Image", + "microsoft-azure-corevm.skuSummary": "Cluster API Kubernetes {{OS}} {{OS_VERSION}} Base Image", + "microsoft-azure-corevm.skuTitle": "Kubernetes {{OS}} {{OS_VERSION}}", + "microsoft-azure-corevm.smallLogo": "https://capiofferlogos.blob.core.windows.net/logos/small48x48", + "microsoft-azure-corevm.supportsAADLogin": false, + "microsoft-azure-corevm.supportsBackup": false, + "microsoft-azure-corevm.supportsClientHub": false, + "microsoft-azure-corevm.supportsHub": false, + "microsoft-azure-corevm.supportsHubOnOffSwitch": false, + "microsoft-azure-corevm.supportsSriov": false, + "microsoft-azure-corevm.termsOfUseURL": "https://github.com/cncf/foundation/blob/master/copyright-notices.md", + "microsoft-azure-corevm.vmImagesPublicAzure": {}, + "microsoft-azure-corevm.wideLogo": "https://capiofferlogos.blob.core.windows.net/logos/wide255x115", + "planId": "{{ID}}" +} diff --git a/packer/azure/ubuntu-1804-gen2.json b/packer/azure/ubuntu-1804-gen2.json new file mode 100644 index 0000000..6f4ba3f --- /dev/null +++ b/packer/azure/ubuntu-1804-gen2.json @@ -0,0 +1,9 @@ +{ + "build_name": "ubuntu-1804-gen2", + "distribution": "ubuntu", + "distribution_release": "bionic", + "distribution_version": "1804", + "image_offer": "UbuntuServer", + "image_publisher": "Canonical", + "image_sku": "18_04-lts-gen2" +} diff --git a/packer/azure/ubuntu-1804.json b/packer/azure/ubuntu-1804.json new file mode 100644 index 0000000..92a3e2f --- /dev/null +++ b/packer/azure/ubuntu-1804.json @@ -0,0 +1,9 @@ +{ + "build_name": "ubuntu-1804", + "distribution": "ubuntu", + "distribution_release": "bionic", + "distribution_version": "1804", + "image_offer": "UbuntuServer", + "image_publisher": "Canonical", + "image_sku": "18.04-LTS" +} diff --git a/packer/azure/ubuntu-2004-gen2.json b/packer/azure/ubuntu-2004-gen2.json new file mode 100644 index 0000000..4f79e02 --- /dev/null +++ b/packer/azure/ubuntu-2004-gen2.json @@ -0,0 +1,9 @@ +{ + "build_name": "ubuntu-2004-gen2", + "distribution": "ubuntu", + "distribution_release": "focal", + "distribution_version": "2004", + "image_offer": "0001-com-ubuntu-server-focal", + "image_publisher": "Canonical", + "image_sku": "20_04-lts-gen2" +} diff --git a/packer/azure/ubuntu-2004.json b/packer/azure/ubuntu-2004.json new file mode 100644 index 0000000..00ee76f --- /dev/null +++ b/packer/azure/ubuntu-2004.json @@ -0,0 +1,9 @@ +{ + "build_name": "ubuntu-2004", + "distribution": "ubuntu", + "distribution_release": "focal", + "distribution_version": "2004", + "image_offer": "0001-com-ubuntu-server-focal", + "image_publisher": "Canonical", + "image_sku": "20_04-lts" +} diff --git a/packer/azure/ubuntu-2204-gen2.json b/packer/azure/ubuntu-2204-gen2.json new file mode 100644 index 0000000..78442cb --- /dev/null +++ b/packer/azure/ubuntu-2204-gen2.json @@ -0,0 +1,9 @@ +{ + "build_name": "ubuntu-2204-gen2", + "distribution": "ubuntu", + "distribution_release": "jammy", + "distribution_version": "2204", + "image_offer": "0001-com-ubuntu-server-jammy", + "image_publisher": "Canonical", + "image_sku": "22_04-lts-gen2" +} diff --git a/packer/azure/ubuntu-2204.json b/packer/azure/ubuntu-2204.json new file mode 100644 index 0000000..d32ba3e --- /dev/null +++ b/packer/azure/ubuntu-2204.json @@ -0,0 +1,9 @@ +{ + "build_name": "ubuntu-2204", + "distribution": "ubuntu", + "distribution_release": "jammy", + "distribution_version": "2204", + "image_offer": "0001-com-ubuntu-server-jammy", + "image_publisher": "Canonical", + "image_sku": "22_04-lts" +} diff --git a/packer/azure/windows-2004.json b/packer/azure/windows-2004.json new file mode 100644 index 0000000..039919c --- /dev/null +++ b/packer/azure/windows-2004.json @@ -0,0 +1,10 @@ +{ + "build_name": "windows-2004", + "distribution": "windows", + "distribution_version": "2004", + "image_offer": "WindowsServer", + "image_publisher": "MicrosoftWindowsServer", + "image_sku": "Datacenter-Core-2004-with-Containers-smalldisk", + "vm_size": "Standard_D4s_v3", + "windows_updates_kbs": "" +} diff --git a/packer/azure/windows-2019-containerd.json b/packer/azure/windows-2019-containerd.json new file mode 100644 index 0000000..bd01bc0 --- /dev/null +++ b/packer/azure/windows-2019-containerd.json @@ -0,0 +1,16 @@ +{ + "additional_registry_images": "false", + "additional_registry_images_list": "", + "build_name": "windows-2019-containerd", + "distribution": "windows", + "distribution_version": "2019", + "image_offer": "WindowsServer", + "image_publisher": "MicrosoftWindowsServer", + "image_sku": "2019-Datacenter-Core-smalldisk", + "image_version": "latest", + "load_additional_components": "false", + "runtime": "containerd", + "vm_size": "Standard_D4s_v3", + "windows_updates_kbs": "", + "wins_url": "" +} diff --git a/packer/azure/windows-2019.json b/packer/azure/windows-2019.json new file mode 100644 index 0000000..8b278ed --- /dev/null +++ b/packer/azure/windows-2019.json @@ -0,0 +1,14 @@ +{ + "additional_registry_images": "true", + "additional_registry_images_list": "docker.io/sigwindowstools/flannel:v0.13.0-nanoserver,docker.io/sigwindowstools/kube-proxy:{{user `kubernetes_semver`}}-nanoserver", + "build_name": "windows-2019", + "distribution": "windows", + "distribution_version": "2019", + "image_offer": "WindowsServer", + "image_publisher": "MicrosoftWindowsServer", + "image_sku": "2019-Datacenter-Core-smalldisk", + "image_version": "latest", + "load_additional_components": "true", + "vm_size": "Standard_D4s_v3", + "windows_updates_kbs": "" +} diff --git a/packer/azure/windows-2022-containerd.json b/packer/azure/windows-2022-containerd.json new file mode 100644 index 0000000..7638ad8 --- /dev/null +++ b/packer/azure/windows-2022-containerd.json @@ -0,0 +1,16 @@ +{ + "additional_registry_images": "false", + "additional_registry_images_list": "", + "build_name": "windows-2022-containerd", + "distribution": "windows", + "distribution_version": "2022", + "image_offer": "WindowsServer", + "image_publisher": "MicrosoftWindowsServer", + "image_sku": "2022-Datacenter-Core-smalldisk", + "image_version": "latest", + "load_additional_components": "false", + "runtime": "containerd", + "vm_size": "Standard_D4s_v3", + "windows_updates_kbs": "", + "wins_url": "" +} diff --git a/packer/config/additional_components.json b/packer/config/additional_components.json new file mode 100644 index 0000000..ea94621 --- /dev/null +++ b/packer/config/additional_components.json @@ -0,0 +1,10 @@ +{ + "additional_executables": "false", + "additional_executables_destination_path": "", + "additional_executables_list": "", + "additional_registry_images": "false", + "additional_registry_images_list": "", + "additional_url_images": "false", + "additional_url_images_list": "", + "load_additional_components": "false" +} diff --git a/packer/config/ansible-args.json b/packer/config/ansible-args.json new file mode 100644 index 0000000..b44429a --- /dev/null +++ b/packer/config/ansible-args.json @@ -0,0 +1,5 @@ +{ + "ansible_common_ssh_args": "-o IdentitiesOnly=yes -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa", + "ansible_common_vars": "containerd_url={{user `containerd_url`}} containerd_sha256={{user `containerd_sha256`}} pause_image={{user `pause_image`}} containerd_additional_settings={{user `containerd_additional_settings`}} containerd_cri_socket={{user `containerd_cri_socket`}} containerd_version={{user `containerd_version`}} containerd_wasm_shims_url={{user `containerd_wasm_shims_url`}} containerd_wasm_shims_version={{user `containerd_wasm_shims_version`}} containerd_wasm_shims_sha256={{user `containerd_wasm_shims_sha256`}} containerd_wasm_shims_runtimes=\"{{user `containerd_wasm_shims_runtimes`}}\" crictl_url={{user `crictl_url`}} crictl_sha256={{user `crictl_sha256`}} crictl_source_type={{user `crictl_source_type`}} custom_role_names=\"{{user `custom_role_names`}}\" firstboot_custom_roles_pre=\"{{user `firstboot_custom_roles_pre`}}\" firstboot_custom_roles_post=\"{{user `firstboot_custom_roles_post`}}\" node_custom_roles_pre=\"{{user `node_custom_roles_pre`}}\" node_custom_roles_post=\"{{user `node_custom_roles_post`}}\" disable_public_repos={{user `disable_public_repos`}} extra_debs=\"{{user `extra_debs`}}\" extra_repos=\"{{user `extra_repos`}}\" extra_rpms=\"{{user `extra_rpms`}}\" http_proxy={{user `http_proxy`}} https_proxy={{user `https_proxy`}} kubeadm_template={{user `kubeadm_template`}} kubernetes_cni_http_source={{user `kubernetes_cni_http_source`}} kubernetes_cni_http_checksum={{user `kubernetes_cni_http_checksum`}} kubernetes_http_source={{user `kubernetes_http_source`}} kubernetes_container_registry={{user `kubernetes_container_registry`}} kubernetes_rpm_repo={{user `kubernetes_rpm_repo`}} kubernetes_rpm_gpg_key={{user `kubernetes_rpm_gpg_key`}} kubernetes_rpm_gpg_check={{user `kubernetes_rpm_gpg_check`}} kubernetes_deb_repo={{user `kubernetes_deb_repo`}} kubernetes_deb_gpg_key={{user `kubernetes_deb_gpg_key`}} kubernetes_cni_deb_version={{user `kubernetes_cni_deb_version`}} kubernetes_cni_rpm_version={{user `kubernetes_cni_rpm_version`}} kubernetes_cni_semver={{user `kubernetes_cni_semver`}} kubernetes_cni_source_type={{user `kubernetes_cni_source_type`}} kubernetes_semver={{user `kubernetes_semver`}} kubernetes_source_type={{user `kubernetes_source_type`}} kubernetes_load_additional_imgs={{user `kubernetes_load_additional_imgs`}} kubernetes_deb_version={{user `kubernetes_deb_version`}} kubernetes_rpm_version={{user `kubernetes_rpm_version`}} no_proxy={{user `no_proxy`}} pip_conf_file={{user `pip_conf_file`}} python_path={{user `python_path`}} redhat_epel_rpm={{user `redhat_epel_rpm`}} epel_rpm_gpg_key={{user `epel_rpm_gpg_key`}} reenable_public_repos={{user `reenable_public_repos`}} remove_extra_repos={{user `remove_extra_repos`}} systemd_prefix={{user `systemd_prefix`}} sysusr_prefix={{user `sysusr_prefix`}} sysusrlocal_prefix={{user `sysusrlocal_prefix`}} load_additional_components={{ user `load_additional_components`}} additional_registry_images={{ user `additional_registry_images`}} additional_registry_images_list={{ user `additional_registry_images_list`}} additional_url_images={{ user `additional_url_images`}} additional_url_images_list={{ user `additional_url_images_list`}} additional_executables={{ user `additional_executables`}} additional_executables_list={{ user `additional_executables_list`}} additional_executables_destination_path={{ user `additional_executables_destination_path`}} build_target={{ user `build_target`}} amazon_ssm_agent_rpm={{ user `amazon_ssm_agent_rpm` }}", + "ansible_scp_extra_args": "{{env `ANSIBLE_SCP_EXTRA_ARGS`}}" +} diff --git a/packer/config/cni.json b/packer/config/cni.json new file mode 100644 index 0000000..3ae265f --- /dev/null +++ b/packer/config/cni.json @@ -0,0 +1,9 @@ +{ + "kubernetes_cni_deb_version": "1.2.0-00", + "kubernetes_cni_http_checksum": "sha256:https://storage.googleapis.com/k8s-artifacts-cni/release/v1.2.0/cni-plugins-linux-{{user `kubernetes_cni_http_checksum_arch`}}-v1.2.0.tgz.sha256", + "kubernetes_cni_http_checksum_arch": "amd64", + "kubernetes_cni_http_source": "https://github.com/containernetworking/plugins/releases/download", + "kubernetes_cni_rpm_version": "1.2.0-0", + "kubernetes_cni_semver": "v1.2.0", + "kubernetes_cni_source_type": "pkg" +} diff --git a/packer/config/common.json b/packer/config/common.json new file mode 100644 index 0000000..a8166cc --- /dev/null +++ b/packer/config/common.json @@ -0,0 +1,19 @@ +{ + "build_target": "virt", + "disable_public_repos": "false", + "extra_debs": "", + "extra_repos": "", + "extra_rpms": "", + "firstboot_custom_roles_post": "", + "firstboot_custom_roles_pre": "", + "http_proxy": "", + "https_proxy": "", + "no_proxy": "", + "node_custom_roles_post": "", + "node_custom_roles_pre": "", + "pause_image": "registry.k8s.io/pause:3.9", + "pip_conf_file": "", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm", + "reenable_public_repos": "true", + "remove_extra_repos": "false" +} diff --git a/packer/config/containerd.json b/packer/config/containerd.json new file mode 100644 index 0000000..4703f26 --- /dev/null +++ b/packer/config/containerd.json @@ -0,0 +1,7 @@ +{ + "containerd_additional_settings": null, + "containerd_cri_socket": "/var/run/containerd/containerd.sock", + "containerd_sha256": "152c8479fc0054db63ff0175fea014da227279b8d3dcab5f2f4b4876317ffe26", + "containerd_sha256_windows": "5b723eb58f7678a63928ec6eadc4a837d52a727e264f365a888d1ee97046bc7f", + "containerd_version": "1.6.15" +} diff --git a/packer/config/goss-args.json b/packer/config/goss-args.json new file mode 100644 index 0000000..fe140de --- /dev/null +++ b/packer/config/goss-args.json @@ -0,0 +1,15 @@ +{ + "goss_arch": "amd64", + "goss_download_path": "", + "goss_entry_file": "goss/goss.yaml", + "goss_format": "json", + "goss_format_options": "pretty", + "goss_inspect_mode": "true", + "goss_remote_folder": "", + "goss_remote_path": "", + "goss_skip_install": "false", + "goss_tests_dir": "packer/goss", + "goss_url": "", + "goss_vars_file": "packer/goss/goss-vars.yaml", + "goss_version": "0.3.16" +} diff --git a/packer/config/kubernetes.json b/packer/config/kubernetes.json new file mode 100644 index 0000000..7d0ec58 --- /dev/null +++ b/packer/config/kubernetes.json @@ -0,0 +1,25 @@ +{ + "crictl_arch": "amd64", + "crictl_sha256": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-{{user `crictl_arch`}}.tar.gz.sha256", + "crictl_source_type": "pkg", + "crictl_url": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-{{user `crictl_arch`}}.tar.gz", + "crictl_version": "1.26.0", + "kubeadm_template": "etc/kubeadm.yml", + "kubernetes_container_registry": "registry.k8s.io", + "kubernetes_deb_gpg_key": "https://packages.cloud.google.com/apt/doc/apt-key.gpg", + "kubernetes_deb_repo": "\"https://apt.kubernetes.io/ kubernetes-xenial\"", + "kubernetes_deb_version": "1.23.15-00", + "kubernetes_http_source": "https://dl.k8s.io/release", + "kubernetes_load_additional_imgs": "false", + "kubernetes_rpm_gpg_check": "True", + "kubernetes_rpm_gpg_key": "\"https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg\"", + "kubernetes_rpm_repo": "https://packages.cloud.google.com/yum/repos/kubernetes-el7-{{user `kubernetes_rpm_repo_arch`}}", + "kubernetes_rpm_repo_arch": "x86_64", + "kubernetes_rpm_version": "1.23.15-0", + "kubernetes_semver": "v1.23.15", + "kubernetes_series": "v1.23", + "kubernetes_source_type": "pkg", + "systemd_prefix": "/usr/lib/systemd", + "sysusr_prefix": "/usr", + "sysusrlocal_prefix": "/usr/local" +} diff --git a/packer/config/ppc64le/cni.json b/packer/config/ppc64le/cni.json new file mode 100644 index 0000000..7c874dc --- /dev/null +++ b/packer/config/ppc64le/cni.json @@ -0,0 +1,3 @@ +{ + "kubernetes_cni_http_checksum_arch": "ppc64le" +} diff --git a/packer/config/ppc64le/common.json b/packer/config/ppc64le/common.json new file mode 100644 index 0000000..d48a3d3 --- /dev/null +++ b/packer/config/ppc64le/common.json @@ -0,0 +1,3 @@ +{ + "build_target": "raw" +} diff --git a/packer/config/ppc64le/containerd.json b/packer/config/ppc64le/containerd.json new file mode 100644 index 0000000..d5e5f6b --- /dev/null +++ b/packer/config/ppc64le/containerd.json @@ -0,0 +1,5 @@ +{ + "containerd_sha256": "49e46a2b8a1fe8b0406e49d745c955b91360f1d024063e0dbe0d9b9873649631", + "containerd_url": "https://oplab9.parqtec.unicamp.br/pub/ppc64el/containerd-cri/containerd-cri-{{user `containerd_version`}}/cri-containerd-cni-{{user `containerd_version`}}-linux-ppc64le.tar.gz", + "containerd_version": "1.6.2" +} diff --git a/packer/config/ppc64le/kubernetes.json b/packer/config/ppc64le/kubernetes.json new file mode 100644 index 0000000..b1c2c04 --- /dev/null +++ b/packer/config/ppc64le/kubernetes.json @@ -0,0 +1,4 @@ +{ + "crictl_arch": "ppc64le", + "kubernetes_rpm_repo_arch": "ppc64le" +} diff --git a/packer/config/wasm-shims.json b/packer/config/wasm-shims.json new file mode 100644 index 0000000..8a020a6 --- /dev/null +++ b/packer/config/wasm-shims.json @@ -0,0 +1,6 @@ +{ + "containerd_wasm_shims_runtimes": "", + "containerd_wasm_shims_sha256": "da84b1c065a58f95a841d39e143cd7115d43e6faedcce7a8782f2942388260d7", + "containerd_wasm_shims_url": "https://github.com/deislabs/containerd-wasm-shims/releases/download/{{user `containerd_wasm_shims_version`}}/containerd-wasm-shims-v1-linux-x86_64.tar.gz", + "containerd_wasm_shims_version": "v0.3.3" +} diff --git a/packer/config/windows/OWNERS b/packer/config/windows/OWNERS new file mode 100644 index 0000000..d047699 --- /dev/null +++ b/packer/config/windows/OWNERS @@ -0,0 +1,4 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - image-builder-windows-maintainers diff --git a/packer/config/windows/ansible-args-windows.json b/packer/config/windows/ansible-args-windows.json new file mode 100644 index 0000000..1ea4a2a --- /dev/null +++ b/packer/config/windows/ansible-args-windows.json @@ -0,0 +1,3 @@ +{ + "ansible_common_vars": "runtime={{user `runtime`}} docker_ee_version={{user `docker_ee_version`}} containerd_url={{user `containerd_url`}} containerd_sha256={{user `containerd_sha256_windows`}} pause_image={{user `pause_image`}} additional_debug_files=\"{{user `additional_debug_files`}}\" containerd_additional_settings={{user `containerd_additional_settings`}} custom_role_names=\"{{user `custom_role_names`}}\" http_proxy={{user `http_proxy`}} https_proxy={{user `https_proxy`}} no_proxy={{user `no_proxy`}} kubernetes_base_url={{user `kubernetes_base_url`}} kubernetes_semver={{user `kubernetes_semver`}} kubernetes_install_path={{user `kubernetes_install_path`}} cloudbase_init_url=\"{{user `cloudbase_init_url`}}\" cloudbase_plugins=\"{{user `cloudbase_plugins`}}\" cloudbase_metadata_services=\"{{user `cloudbase_metadata_services`}}\" cloudbase_plugins_unattend=\"{{user `cloudbase_plugins_unattend`}}\" cloudbase_metadata_services_unattend=\"{{user `cloudbase_metadata_services_unattend`}}\" prepull={{user `prepull`}} wins_url={{user `wins_url`}} windows_updates_kbs=\"{{user `windows_updates_kbs`}}\" windows_updates_categories=\"{{user `windows_updates_categories`}}\" windows_service_manager={{user `windows_service_manager`}} nssm_url={{user `nssm_url`}} distribution_version={{user `distribution_version`}} netbios_host_name_compatibility={{user `netbios_host_name_compatibility`}} disable_hypervisor={{ user `disable_hypervisor` }} cloudbase_logging_serial_port={{ user `cloudbase_logging_serial_port` }} load_additional_components={{ user `load_additional_components`}} additional_registry_images={{ user `additional_registry_images`}} additional_registry_images_list={{ user `additional_registry_images_list`}} additional_url_images={{ user `additional_url_images`}} additional_url_images_list={{ user `additional_url_images_list`}} additional_executables={{ user `additional_executables`}} additional_executables_list={{ user `additional_executables_list`}} additional_executables_destination_path={{ user `additional_executables_destination_path`}} ssh_source_url={{user `ssh_source_url` }} debug_tools={{user `debug_tools`}}" +} diff --git a/packer/config/windows/cloudbase-init.json b/packer/config/windows/cloudbase-init.json new file mode 100644 index 0000000..bb3ac70 --- /dev/null +++ b/packer/config/windows/cloudbase-init.json @@ -0,0 +1,3 @@ +{ + "cloudbase_init_version": "1.1.2" +} diff --git a/packer/config/windows/common.json b/packer/config/windows/common.json new file mode 100644 index 0000000..fa985b2 --- /dev/null +++ b/packer/config/windows/common.json @@ -0,0 +1,17 @@ +{ + "additional_debug_files": "", + "debug_tools": "true", + "disable_hypervisor": "false", + "http_proxy": "", + "https_proxy": "", + "netbios_host_name_compatibility": "true", + "no_proxy": "", + "nssm_url": "https://upstreamartifacts.azureedge.net/nssm/nssm.exe", + "prepull": "true", + "runtime": "docker-ee", + "ssh_source_url": "", + "windows_service_manager": "nssm", + "windows_updates_categories": "", + "windows_updates_kbs": "", + "wins_version": "0.0.4" +} diff --git a/packer/config/windows/containerd.json b/packer/config/windows/containerd.json new file mode 100644 index 0000000..ad43316 --- /dev/null +++ b/packer/config/windows/containerd.json @@ -0,0 +1,4 @@ +{ + "containerd_additional_settings": null, + "containerd_url": "https://github.com/containerd/containerd/releases/download/v{{user `containerd_version`}}/containerd-{{user `containerd_version`}}-windows-amd64.tar.gz" +} diff --git a/packer/config/windows/docker.json b/packer/config/windows/docker.json new file mode 100644 index 0000000..194f84b --- /dev/null +++ b/packer/config/windows/docker.json @@ -0,0 +1,3 @@ +{ + "docker_ee_version": "19.03.12" +} diff --git a/packer/config/windows/kubernetes.json b/packer/config/windows/kubernetes.json new file mode 100644 index 0000000..738e669 --- /dev/null +++ b/packer/config/windows/kubernetes.json @@ -0,0 +1,4 @@ +{ + "kubernetes_goarch": "amd64", + "kubernetes_install_path": "c:\\k" +} diff --git a/packer/digitalocean/OWNERS b/packer/digitalocean/OWNERS new file mode 100644 index 0000000..f1f027f --- /dev/null +++ b/packer/digitalocean/OWNERS @@ -0,0 +1,6 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - cluster-api-digitalocean-maintainers +reviewers: + - cluster-api-digitalocean-maintainers diff --git a/packer/digitalocean/centos-7.json b/packer/digitalocean/centos-7.json new file mode 100644 index 0000000..7df49b1 --- /dev/null +++ b/packer/digitalocean/centos-7.json @@ -0,0 +1,5 @@ +{ + "build_name": "centos-7", + "snapshot_name_suffix": "on CentOS 7", + "source_image": "centos-7-x64" +} diff --git a/packer/digitalocean/packer.json b/packer/digitalocean/packer.json new file mode 100644 index 0000000..4306921 --- /dev/null +++ b/packer/digitalocean/packer.json @@ -0,0 +1,95 @@ +{ + "builders": [ + { + "api_token": "{{ user `access_token` }}", + "image": "{{ user `source_image` }}", + "name": "{{user `build_name`}}", + "region": "{{ user `region` }}", + "size": "{{ user `size` }}", + "snapshot_name": "Cluster API Kubernetes {{ user `kubernetes_semver` }} {{ user `snapshot_name_suffix` }}", + "snapshot_regions": [ + "nyc1" + ], + "ssh_username": "root", + "tags": [ + "cluster-api-{{ user `build_name` }}:{{ user `kubernetes_semver` | replace_all `.` `-` }}" + ], + "type": "digitalocean" + } + ], + "provisioners": [ + { + "environment_vars": [ + "BUILD_NAME={{user `build_name`}}" + ], + "inline": [ + "if [ $BUILD_NAME != \"ubuntu-1804\" ]; then exit 0; fi", + "while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done", + "sudo apt-get -qq update && sudo DEBIAN_FRONTEND=noninteractive apt-get -qqy install python python-pip" + ], + "type": "shell" + }, + { + "environment_vars": [ + "BUILD_NAME={{user `build_name`}}" + ], + "inline": [ + "if [ $BUILD_NAME != \"ubuntu-2004\" ]; then exit 0; fi", + "while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done", + "sudo apt-get -qq update" + ], + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/node.yml", + "type": "ansible", + "user": "packer" + } + ], + "variables": { + "access_token": "{{env `DIGITALOCEAN_ACCESS_TOKEN`}}", + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_scp_extra_args": "", + "build_timestamp": "{{timestamp}}", + "containerd_sha256": null, + "containerd_url": "https://github.com/containerd/containerd/releases/download/v{{user `containerd_version`}}/cri-containerd-cni-{{user `containerd_version`}}-linux-amd64.tar.gz", + "containerd_version": null, + "crictl_url": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-amd64.tar.gz", + "crictl_version": null, + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "kubernetes_cni_deb_version": null, + "kubernetes_cni_http_source": null, + "kubernetes_cni_rpm_version": null, + "kubernetes_cni_semver": null, + "kubernetes_cni_source_type": null, + "kubernetes_container_registry": null, + "kubernetes_deb_gpg_key": null, + "kubernetes_deb_repo": null, + "kubernetes_deb_version": null, + "kubernetes_http_source": null, + "kubernetes_load_additional_imgs": null, + "kubernetes_rpm_gpg_check": null, + "kubernetes_rpm_gpg_key": null, + "kubernetes_rpm_repo": null, + "kubernetes_rpm_version": null, + "kubernetes_semver": null, + "kubernetes_series": null, + "kubernetes_source_type": null, + "region": "nyc1", + "size": "s-1vcpu-1gb" + } +} diff --git a/packer/digitalocean/ubuntu-1804.json b/packer/digitalocean/ubuntu-1804.json new file mode 100644 index 0000000..0b01b1d --- /dev/null +++ b/packer/digitalocean/ubuntu-1804.json @@ -0,0 +1,5 @@ +{ + "build_name": "ubuntu-1804", + "snapshot_name_suffix": "on Ubuntu 18.04", + "source_image": "ubuntu-18-04-x64" +} diff --git a/packer/digitalocean/ubuntu-2004.json b/packer/digitalocean/ubuntu-2004.json new file mode 100644 index 0000000..48060cd --- /dev/null +++ b/packer/digitalocean/ubuntu-2004.json @@ -0,0 +1,5 @@ +{ + "build_name": "ubuntu-2004", + "snapshot_name_suffix": "on Ubuntu 20.04", + "source_image": "ubuntu-20-04-x64" +} diff --git a/packer/files/flatcar/README.md b/packer/files/flatcar/README.md new file mode 100644 index 0000000..910fc25 --- /dev/null +++ b/packer/files/flatcar/README.md @@ -0,0 +1,42 @@ +# Flatcar-Related Build Files + +This directory contains files needed for building Flatcar Container Linux CAPI images. + +The following subdirectories exist: + +- `clc` - contains [Container Linux Config][1] files. +- `ignition` - contains [Ignition][2] files generated from the CLC files in the `clc` directory. +- `scripts` - contains scripts which are used by the various Flatcar builds. + +## Ignition Files + +Some Flatcar builds (e.g. QEMU) require Ignition files during OS installation. These files can be +consumed directly from the `ignition` directory. Ignition files are generated from CLC files by the +[Container Linux Config Transpiler][3]. + +### Adding New Files + +To add a new Ignition file, do the following: + +1. Place a CLC YAML file with the desired config in `clc`. +1. Add the name of the file without an extension to the `ignition_files` variable under the + `gen-ignition` target in the [Makefile](../../../Makefile). For example, for a CLC file named + `foo.yaml`, add `foo` to the Make target. +1. Run `make gen-ignition` under `images/capi`. A new Ignition file is generated under `ignition`. +1. Commit both the CLC file and the resulting Ignition file and open a PR to merge the changes. + +Once the changes are merged, the new Ignition file can be referenced in Flatcar builds and consumed +as a raw file directly from GitHub. + +### Changing Existing Files + +To change an existing Ignition file, do the following: + +1. Edit the relevant CLC YAML file in `clc`. +1. Run `make gen-ignition` under `images/capi`. The corresponding Ignition file is updated under + `ignition`. +1. Commit the changes and open a PR to merge them. + +[1]: https://flatcar.org/docs/latest/provisioning/cl-config/ +[2]: https://flatcar.org/docs/latest/provisioning/ignition/ +[3]: https://flatcar.org/docs/latest/provisioning/config-transpiler/ diff --git a/packer/files/flatcar/clc/bootstrap.yaml b/packer/files/flatcar/clc/bootstrap.yaml new file mode 100644 index 0000000..9727034 --- /dev/null +++ b/packer/files/flatcar/clc/bootstrap.yaml @@ -0,0 +1,26 @@ +# This file is used for initial provisioning of a Flatcar machine, before Packer provisioners (e.g. +# Ansible) are executed. +passwd: + users: + - name: builder + # "BUILDERPASSWORDHASH" gets overwritten by Packer on platforms where SSH password auth is used. + password_hash: BUILDERPASSWORDHASH + # "BUILDERSSHAUTHKEY" gets overwritten by Packer on platforms where SSH key auth is used. + # TODO: Once https://github.com/kubernetes-sigs/image-builder/pull/882 is merged we can remove + # the ssh_authorized_keys key altogether since the QEMU and raw targets would be using password + # auth and the rest of the targets have provider-specific authorization mechanisms, meaning SSH + # keys don't have to be specified in this CLC file. + ssh_authorized_keys: ["BUILDERSSHAUTHKEY"] + groups: + - wheel + - sudo + - docker +systemd: + units: + - name: docker.service + enable: true + # Mask update-engine and locksmithd to disable automatic updates during image creation. + - name: update-engine.service + mask: true + - name: locksmithd.service + mask: true diff --git a/packer/files/flatcar/ignition/bootstrap.json b/packer/files/flatcar/ignition/bootstrap.json new file mode 100644 index 0000000..bb2a848 --- /dev/null +++ b/packer/files/flatcar/ignition/bootstrap.json @@ -0,0 +1,44 @@ +{ + "ignition": { + "config": {}, + "security": { + "tls": {} + }, + "timeouts": {}, + "version": "2.3.0" + }, + "networkd": {}, + "passwd": { + "users": [ + { + "groups": [ + "wheel", + "sudo", + "docker" + ], + "name": "builder", + "passwordHash": "BUILDERPASSWORDHASH", + "sshAuthorizedKeys": [ + "BUILDERSSHAUTHKEY" + ] + } + ] + }, + "storage": {}, + "systemd": { + "units": [ + { + "enable": true, + "name": "docker.service" + }, + { + "mask": true, + "name": "update-engine.service" + }, + { + "mask": true, + "name": "locksmithd.service" + } + ] + } +} diff --git a/packer/files/flatcar/scripts/bootstrap-flatcar.sh b/packer/files/flatcar/scripts/bootstrap-flatcar.sh new file mode 100644 index 0000000..402cfd3 --- /dev/null +++ b/packer/files/flatcar/scripts/bootstrap-flatcar.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# +# This script installs PyPy as a Python interpreter on a Flatcar instance. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +BINDIR="/opt/bin" +BUILDER_ENV="/opt/bin/builder-env" + +set -x + +mkdir -p ${BINDIR} + +cd ${BINDIR} + +if [[ -e ${BINDIR}/.bootstrapped ]]; then + exit 0 +fi + +PYPY_VERSION=7.2.0 +PYTHON3_VERSION=3.6 + +curl -sfL https://github.com/squeaky-pl/portable-pypy/releases/download/pypy-${PYPY_VERSION}/pypy-${PYPY_VERSION}-linux_x86_64-portable.tar.bz2 | tar -xjf - +mv -n pypy-${PYPY_VERSION}-linux_x86_64-portable pypy2 +ln -s ./pypy2/bin/pypy python2 +ln -s ./pypy2/bin/pypy python + +curl -sfL https://github.com/squeaky-pl/portable-pypy/releases/download/pypy${PYTHON3_VERSION}-${PYPY_VERSION}/pypy${PYTHON3_VERSION}-${PYPY_VERSION}-linux_x86_64-portable.tar.bz2 | tar -xjf - +mv -n pypy${PYTHON3_VERSION}-${PYPY_VERSION}-linux_x86_64-portable pypy3 +ln -s ./pypy3/bin/pypy3 python3 + +${BINDIR}/python --version + +${BINDIR}/pypy2/bin/virtualenv-pypy ${BUILDER_ENV} +chown -R core ${BUILDER_ENV} + +ln -s builder-env/bin/pip ${BINDIR}/pip +# need to have symlink pip3 required by ansible/roles/providers/tasks/aws.yml +ln -s builder-env/bin/pip ${BINDIR}/pip3 + +touch ${BINDIR}/.bootstrapped diff --git a/packer/gce/OWNERS b/packer/gce/OWNERS new file mode 100644 index 0000000..3ac3383 --- /dev/null +++ b/packer/gce/OWNERS @@ -0,0 +1,4 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - cluster-api-gcp-maintainers diff --git a/packer/gce/ci/nightly/README.md b/packer/gce/ci/nightly/README.md new file mode 100644 index 0000000..03a82fe --- /dev/null +++ b/packer/gce/ci/nightly/README.md @@ -0,0 +1,5 @@ +## Configs for nightly builds + +The configurations in the directory is being used for the nightly job to build the images for GCE. + +The script that runs is [ci-gce-nightly.sh](../../../../scripts/ci-gce-nightly.sh) diff --git a/packer/gce/ci/nightly/overwrite-1-23.json b/packer/gce/ci/nightly/overwrite-1-23.json new file mode 100644 index 0000000..7a04e6d --- /dev/null +++ b/packer/gce/ci/nightly/overwrite-1-23.json @@ -0,0 +1,8 @@ +{ + "build_timestamp": "nightly", + "kubernetes_deb_version": "1.23.15-00", + "kubernetes_rpm_version": "1.23.15-0", + "kubernetes_semver": "v1.23.15", + "kubernetes_series": "v1.23", + "service_account_email": "gcb-builder-cluster-api-gcp@k8s-staging-cluster-api-gcp.iam.gserviceaccount.com" +} diff --git a/packer/gce/ci/nightly/overwrite-1-24.json b/packer/gce/ci/nightly/overwrite-1-24.json new file mode 100644 index 0000000..13d9de7 --- /dev/null +++ b/packer/gce/ci/nightly/overwrite-1-24.json @@ -0,0 +1,8 @@ +{ + "build_timestamp": "nightly", + "kubernetes_deb_version": "1.24.9-00", + "kubernetes_rpm_version": "1.24.9-0", + "kubernetes_semver": "v1.24.9", + "kubernetes_series": "v1.24", + "service_account_email": "gcb-builder-cluster-api-gcp@k8s-staging-cluster-api-gcp.iam.gserviceaccount.com" +} diff --git a/packer/gce/ci/nightly/overwrite-1-25.json b/packer/gce/ci/nightly/overwrite-1-25.json new file mode 100644 index 0000000..64a5faa --- /dev/null +++ b/packer/gce/ci/nightly/overwrite-1-25.json @@ -0,0 +1,8 @@ +{ + "build_timestamp": "nightly", + "kubernetes_deb_version": "1.25.5-00", + "kubernetes_rpm_version": "1.25.5-0", + "kubernetes_semver": "v1.25.5", + "kubernetes_series": "v1.25", + "service_account_email": "gcb-builder-cluster-api-gcp@k8s-staging-cluster-api-gcp.iam.gserviceaccount.com" +} diff --git a/packer/gce/ci/nightly/overwrite-1-26.json b/packer/gce/ci/nightly/overwrite-1-26.json new file mode 100644 index 0000000..815ccfa --- /dev/null +++ b/packer/gce/ci/nightly/overwrite-1-26.json @@ -0,0 +1,8 @@ +{ + "build_timestamp": "nightly", + "kubernetes_deb_version": "1.26.0-00", + "kubernetes_rpm_version": "1.26.0-0", + "kubernetes_semver": "v1.26.0", + "kubernetes_series": "v1.26", + "service_account_email": "gcb-builder-cluster-api-gcp@k8s-staging-cluster-api-gcp.iam.gserviceaccount.com" +} diff --git a/packer/gce/packer.json b/packer/gce/packer.json new file mode 100644 index 0000000..3ab7dc3 --- /dev/null +++ b/packer/gce/packer.json @@ -0,0 +1,126 @@ +{ + "builders": [ + { + "disable_default_service_account": "{{ user `disable_default_service_account` }}", + "image_family": "{{user `image_family` | clean_resource_name}}", + "image_name": "{{user `image_name` | clean_resource_name}}", + "labels": { + "build_timestamp": "{{user `build_timestamp`}}", + "distribution": "ubuntu", + "distribution_release": "{{user `distribution_release`}}", + "distribution_version": "{{user `distribution_version`}}", + "kubernetes_version": "{{user `kubernetes_semver` | clean_resource_name}}" + }, + "machine_type": "{{ user `machine_type` }}", + "name": "{{user `build_name`}}", + "project_id": "{{ user `project_id` }}", + "service_account_email": "{{ user `service_account_email` }}", + "source_image_family": "{{ user `source_image_family` }}", + "ssh_username": "ubuntu", + "type": "googlecompute", + "use_internal_ip": "{{ user `use_internal_ip`}}", + "zone": "{{ user `zone` }}" + } + ], + "provisioners": [ + { + "environment_vars": [ + "BUILD_NAME={{user `build_name`}}" + ], + "inline": [ + "if [ $BUILD_NAME != \"ubuntu-1804\" ] || [ $BUILD_NAME != \"ubuntu-2004\" ]; then exit 0; fi", + "while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done", + "sudo apt-get -qq update && sudo DEBIAN_FRONTEND=noninteractive apt-get -qqy install python python-pip" + ], + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/node.yml", + "type": "ansible" + }, + { + "arch": "{{user `goss_arch`}}", + "download_path": "{{user `goss_download_path`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "remote_folder": "{{user `goss_remote_folder`}}", + "remote_path": "{{user `goss_remote_path`}}", + "skip_install": "{{user `goss_skip_install`}}", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": true, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "ARCH": "amd64", + "OS": "ubuntu", + "PROVIDER": "gcp", + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_deb_version": "{{ user `kubernetes_cni_deb_version` }}", + "kubernetes_cni_rpm_version": "{{ split (user `kubernetes_cni_rpm_version`) \"-\" 0 }}", + "kubernetes_cni_source_type": "{{user `kubernetes_cni_source_type`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver` | replace \"v\" \"\" 1}}", + "kubernetes_deb_version": "{{ user `kubernetes_deb_version` }}", + "kubernetes_rpm_version": "{{ split (user `kubernetes_rpm_version`) \"-\" 0 }}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "kubernetes_version": "{{user `kubernetes_semver` | replace \"v\" \"\" 1}}" + }, + "version": "{{user `goss_version`}}" + } + ], + "variables": { + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_scp_extra_args": "", + "build_timestamp": "{{timestamp}}", + "containerd_sha256": null, + "containerd_url": "https://github.com/containerd/containerd/releases/download/v{{user `containerd_version`}}/cri-containerd-cni-{{user `containerd_version`}}-linux-amd64.tar.gz", + "containerd_version": null, + "crictl_url": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-amd64.tar.gz", + "crictl_version": null, + "disable_default_service_account": "", + "encrypted": "false", + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "image_family": "capi-{{user `build_name`}}-k8s-{{user `kubernetes_series`}}", + "image_name": "cluster-api-{{user `build_name`}}-{{user `kubernetes_semver`}}-{{user `build_timestamp`}}", + "kubernetes_cni_deb_version": null, + "kubernetes_cni_http_source": null, + "kubernetes_cni_rpm_version": null, + "kubernetes_cni_semver": null, + "kubernetes_cni_source_type": null, + "kubernetes_container_registry": null, + "kubernetes_deb_gpg_key": null, + "kubernetes_deb_repo": null, + "kubernetes_deb_version": null, + "kubernetes_http_source": null, + "kubernetes_load_additional_imgs": null, + "kubernetes_rpm_gpg_check": null, + "kubernetes_rpm_gpg_key": null, + "kubernetes_rpm_repo": null, + "kubernetes_rpm_version": null, + "kubernetes_semver": null, + "kubernetes_series": null, + "kubernetes_source_type": null, + "machine_type": "n1-standard-1", + "project_id": "{{env `GCP_PROJECT_ID`}}", + "service_account_email": "", + "source_image_family": "{{user `build_name`}}-lts", + "use_internal_ip": "false", + "zone": null + } +} diff --git a/packer/gce/ubuntu-1804.json b/packer/gce/ubuntu-1804.json new file mode 100644 index 0000000..c7ab95a --- /dev/null +++ b/packer/gce/ubuntu-1804.json @@ -0,0 +1,6 @@ +{ + "build_name": "ubuntu-1804", + "distribution_release": "bionic", + "distribution_version": "1804", + "zone": "us-central1-a" +} diff --git a/packer/gce/ubuntu-2004.json b/packer/gce/ubuntu-2004.json new file mode 100644 index 0000000..6e7b78c --- /dev/null +++ b/packer/gce/ubuntu-2004.json @@ -0,0 +1,6 @@ +{ + "build_name": "ubuntu-2004", + "distribution_release": "focal", + "distribution_version": "2004", + "zone": "us-central1-a" +} diff --git a/packer/gce/ubuntu-2204.json b/packer/gce/ubuntu-2204.json new file mode 100644 index 0000000..c7cd853 --- /dev/null +++ b/packer/gce/ubuntu-2204.json @@ -0,0 +1,6 @@ +{ + "build_name": "ubuntu-2204", + "distribution_release": "jammy", + "distribution_version": "2204", + "zone": "us-central1-a" +} diff --git a/packer/goss/goss-command.yaml b/packer/goss/goss-command.yaml new file mode 100644 index 0000000..6ae2150 --- /dev/null +++ b/packer/goss/goss-command.yaml @@ -0,0 +1,261 @@ +command: +{{ if ne .Vars.OS "windows" }} # Linux Only + containerd --version | awk -F' ' '{print substr($3,2); }': + exit-status: 0 + stdout: [] + stderr: [] + timeout: 0 + crictl ps: + exit-status: 0 + stdout: [] + stderr: [] + timeout: 0 +{{if ne .Vars.containerd_wasm_shims_runtimes ""}} + containerd-shim-slight-v1: + exit-status: 1 + stdout: [ ] + stderr: ["io.containerd.slight.v1: InvalidArgument(\"Shim namespace cannot be empty\")"] + timeout: 0 + containerd-shim-spin-v1: + exit-status: 1 + stdout: [ ] + stderr: ["io.containerd.spin.v1: InvalidArgument(\"Shim namespace cannot be empty\")"] + timeout: 0 + grep -E 'io\.containerd\.(slight|spin)\.v1' /etc/containerd/config.toml: + exit-status: 0 + stdout: [ ] + stderr: [ ] + timeout: 0 +{{end}} +{{if eq .Vars.kubernetes_source_type "pkg"}} +{{if eq .Vars.kubernetes_cni_source_type "pkg"}} + crictl images | grep -v 'IMAGE ID' | awk -F'[ /]' '{print $2}' | sed 's/-{{ .Vars.arch }}//g' | sort: + exit-status: 0 + stderr: [] + timeout: 0 + stdout: ["coredns", "etcd", "kube-apiserver", "kube-controller-manager", "kube-proxy", "kube-scheduler", "pause"] +{{end}} +{{end}} +{{if and (eq .Vars.kubernetes_source_type "http") (eq .Vars.kubernetes_cni_source_type "http") (not .Vars.kubernetes_load_additional_imgs)}} +# The second last pipe of awk is to take out arch from kube-apiserver-amd64 (i.e. amd64 or any other arch) + crictl images | grep -v 'IMAGE ID' | awk -F'[ /]' '{print $2}' | sed 's/-{{ .Vars.arch }}//g' | sort: + exit-status: 0 + stderr: [] + timeout: 0 + stdout: ["kube-apiserver", "kube-controller-manager", "kube-proxy", "kube-scheduler"] +{{end}} +{{if and (eq .Vars.kubernetes_source_type "http") (eq .Vars.kubernetes_cni_source_type "http") (.Vars.kubernetes_load_additional_imgs)}} +# The second last pipe of awk is to take out arch from kube-apiserver-amd64 (i.e. amd64 or any other arch) + crictl images | grep -v 'IMAGE ID' | awk -F'[ /]' '{print $2}' | sed 's/-{{ .Vars.arch }}//g' | sort: + exit-status: 0 + stderr: [] + timeout: 0 + stdout: ["coredns", "etcd", "kube-apiserver", "kube-controller-manager", "kube-proxy", "kube-scheduler", "pause"] +{{end}} +{{if eq .Vars.kubernetes_source_type "http"}} + kubectl version --short --client=true -o json | jq .clientVersion.gitVersion | tr -d '"' | awk '{print substr($1,2); }': + exit-status: 0 + stdout: [{{ .Vars.kubernetes_version }}] + stderr: [] + timeout: 0 + kubeadm version -o json | jq .clientVersion.gitVersion | tr -d '"' | awk '{print substr($1,2); }': + exit-status: 0 + stdout: [{{ .Vars.kubernetes_version }}] + stderr: [] + timeout: 0 + kubelet --version | awk -F' ' '{print $2}' | tr -d '"' | awk '{print substr($1,2); }': + exit-status: 0 + stdout: [{{ .Vars.kubernetes_version }}] + stderr: [] + timeout: 0 +{{end}} +{{if eq .Vars.kubernetes_cni_source_type "http"}} + /opt/cni/bin/host-device 2>&1 | awk -F' ' '{print substr($4,2); }': + exit-status: 0 + stdout: [{{ .Vars.kubernetes_cni_version }}] + stderr: [] + timeout: 0 +{{end}} +{{if eq .Vars.OS "photon"}} + cat /sys/kernel/mm/transparent_hugepage/enabled: + exit-status: 0 + stdout: ["always [madvise] never"] + stderr: [] + timeout: 0 +{{end}} +{{range $name, $vers := index .Vars .Vars.OS .Vars.PROVIDER "command"}} + {{ $name }}: + {{range $key, $val := $vers}} + {{$key}}: {{$val}} + {{end}} +{{end}} +{{end}} #End linux only + +{{ if eq .Vars.OS "windows" }} # Windows + automatic updates set to notify: + exit-status: 0 + exec: powershell -command "(Get-ItemPropertyValue 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -name AUOptions) -eq '2'" + stdout: + - "True" + timeout: 30000 + automatic updates set to notify with correct type: + exit-status: 0 + exec: powershell -command "(Get-ItemPropertyValue 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -name AUOptions).GetType().Name -eq 'Int32'" + stdout: + - "True" + timeout: 30000 + automatic updates are disabled: + exit-status: 0 + exec: powershell -command "(Get-ItemPropertyValue 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -name NoAutoUpdate) -eq '1'" + stdout: + - "True" + timeout: 30000 + automatic updates are disabled with correct type: + exit-status: 0 + exec: powershell -command "(Get-ItemPropertyValue 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -name NoAutoUpdate).GetType().Name -eq 'Int32'" + stdout: + - "True" + timeout: 30000 + kubectl version --client: + exit-status: 0 + stdout: + - {{.Vars.kubernetes_version}} + - "windows" + - {{.Vars.arch}} + timeout: 30000 + kubeadm version: + exit-status: 0 + stdout: + - {{.Vars.kubernetes_version}} + - "windows" + - {{.Vars.arch}} + timeout: 30000 + kubelet --version: + exit-status: 0 + stdout: + - {{.Vars.kubernetes_version}} + timeout: 10000 +{{ if eq .Vars.distribution_version "2019" }} + Windows build version is high enough: + exit-status: 0 + exec: powershell -command "(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name UBR).UBR -ge 1817" + stdout: + - "True" + timeout: 30000 + Check HNS Control Flag: + exit-status: 0 + exec: powershell -command "(Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\hns\State" -Name HNSControlFlag).HNSControlFlag -eq 80" + stdout: + - True + timeout: 30000 + Check WCIFS Flag: + exit-status: 0 + exec: powershell -command "(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\wcifs' -Name WcifsSOPCountDisabled).WcifsSOPCountDisabled -eq 0" + stdout: + - True + timeout: 30000 +{{end}} +{{ if eq .Vars.runtime "containerd" }} + Correct Containerd Version: + exec: "\"/Program Files/containerd/containerd.exe\" --version" + exit-status: 0 + stdout: + - "{{.Vars.containerd_version}}" + timeout: 30000 + Correct Containerd config: + exec: "\"/Program Files/containerd/containerd.exe\" config dump" + exit-status: 0 + stdout: + - "sandbox_image = \"{{.Vars.pause_image}}\"" + - "conf_dir = \"C:/etc/cni/net.d\"" + - "bin_dir = \"C:/opt/cni/bin\"" + - "root = \"C:\\\\ProgramData\\\\containerd\\\\root\"" + - "state = \"C:\\\\ProgramData\\\\containerd\\\\state\"" + timeout: 30000 + Check Windows Defender Exclusions are in place: + exit-status: 0 + exec: powershell -command "(Get-MpPreference | select ExclusionProcess)" + stdout: + - \Program Files\containerd\containerd.exe, + - \Program Files\containerd\ctr.exe + Check SMB CompartmentNamespace Flag: + exit-status: 0 + exec: powershell -command "(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\hns\State' -Name EnableCompartmentNamespace).EnableCompartmentNamespace -eq 1" + stdout: + - True + timeout: 30000 + Windows Port Range is Expanded: + exit-status: 0 + exec: netsh int ipv4 show dynamicportrange tcp + stdout: + - "Start Port : 34000" + - "Number of Ports : 31536" + timeout: 30000 +{{end}} + +{{ if eq .Vars.runtime "docker-ee" }} + Correct Docker Version: + exec: "docker.exe version" + exit-status: 0 + stdout: + - "{{.Vars.docker_ee_version}}" + timeout: 30000 +{{end}} + +{{if eq .Vars.PROVIDER "azure"}} + Verify firewall rule to block 168.63.129.16:80 for cve-2021-27075: + exit-status: 0 + exec: powershell -command "(Get-NetFirewallRule -ErrorAction Stop -DisplayName 'Block-Outbound-168.63.129.16-port-80-for-cve-2021-27075').Enabled" + stdout: + - True + stderr: [] + timeout: 30000 + + # this could be moved to place for other providers if they want to install it + Key Vault gMSA binary is installed: + exec: powershell -command "Test-Path -Path C:\Windows\System32\CCGAKVPlugin.dll" + exit-status: 0 + stdout: + - "True" + timeout: 30000 + Key Vault gMSA binary COM is registered: + exec: powershell -command "(Get-Item 'HKLM:SYSTEM\CurrentControlSet\Control\CCG\COMClasses\{CCC2A336-D7F3-4818-A213-272B7924213E}') | Ft -autosize -wrap" + exit-status: 0 + stdout: + - "CCC2A336-D7F3-4818-A213-272B7924213E" + timeout: 30000 + Key Vault gMSA binary is registered: + exec: powershell -command "Get-ItemProperty -Path 'HKLM:SOFTWARE\CLASSES\CLSID\{CCC2A336-D7F3-4818-A213-272B7924213E}\InprocServer32\'" + exit-status: 0 + stdout: + - "C:\\Windows\\System32\\CCGAKVPlugin.dll" + timeout: 30000 + Key Vault gMSA CCG interface is registered: + exec: powershell -command "(Get-Item 'HKLM:SOFTWARE\Classes\Interface\{6ECDA518-2010-4437-8BC3-46E752B7B172}') | Ft -autosize -wrap" + exit-status: 0 + stdout: + - "ICcgDomainAuthCredentials" + timeout: 30000 +{{end}} + +{{ if ne .Vars.ssh_source_url "" }} + Check permission of OpenSSH directory for SYSTEM: + exec: powershell -command "((Get-Acl 'C:\Program Files\OpenSSH').Access | Where-Object{$_.IdentityReference -eq 'NT AUTHORITY\SYSTEM' -and $_.FileSystemRights -eq 'FullControl'}) -ne $null" + exit-status: 0 + stdout: + - True + timeout: 30000 + Check permission of OpenSSH directory for Administrators: + exec: powershell -command "((Get-Acl 'C:\Program Files\OpenSSH').Access | Where-Object{$_.IdentityReference -eq 'BUILTIN\Administrators' -and $_.FileSystemRights -eq 'FullControl'}) -ne $null" + exit-status: 0 + stdout: + - True + timeout: 30000 + Check permission of OpenSSH directory for Users: + exec: powershell -command "((Get-Acl 'C:\Program Files\OpenSSH').Access | Where-Object{$_.IdentityReference -eq 'BUILTIN\Users' -and $_.FileSystemRights -eq 'ReadAndExecute, Synchronize'}) -eq $null" + exit-status: 0 + stdout: + - True + timeout: 30000 +{{end}} +{{end}} #end windows diff --git a/packer/goss/goss-files.yaml b/packer/goss/goss-files.yaml new file mode 100644 index 0000000..278f403 --- /dev/null +++ b/packer/goss/goss-files.yaml @@ -0,0 +1,17 @@ +file: +{{range $name, $vers := index .Vars .Vars.OS "common-files"}} + {{ $name }}: + exists: {{ $vers.exists }} + filetype: {{ $vers.filetype }} + contains: {{ range $vers.contains}} + - {{.}} + {{end}} +{{end}} +{{range $name, $vers := index .Vars .Vars.OS .Vars.PROVIDER "files"}} + {{ $name }}: + exists: {{ $vers.exists }} + filetype: {{ $vers.filetype }} + contains: {{ range $vers.contains}} + - {{.}} + {{end}} +{{end}} \ No newline at end of file diff --git a/packer/goss/goss-kernel-params.yaml b/packer/goss/goss-kernel-params.yaml new file mode 100644 index 0000000..b3413ee --- /dev/null +++ b/packer/goss/goss-kernel-params.yaml @@ -0,0 +1,31 @@ +{{ if ne .Vars.OS "windows" }} +kernel-param: + net.bridge.bridge-nf-call-iptables: + value: "1" + net.ipv6.conf.all.forwarding: + value: "1" + net.ipv6.conf.all.disable_ipv6: + value: "0" + net.ipv4.ip_forward: + value: "1" + net.bridge.bridge-nf-call-ip6tables: + value: "1" + vm.overcommit_memory: + value: "1" + kernel.panic: + value: "10" + kernel.panic_on_oops: + value: "1" +{{range $name, $vers := index .Vars .Vars.OS "common-kernel-param"}} + {{ $name }}: + {{range $key, $val := $vers}} + {{$key}}: "{{$val}}" + {{end}} +{{end}} +{{range $name, $vers := index .Vars .Vars.OS .Vars.PROVIDER "kernel-param"}} + {{ $name }}: + {{range $key, $val := $vers}} + {{$key}}: "{{$val}}" + {{end}} +{{end}} +{{end}} \ No newline at end of file diff --git a/packer/goss/goss-package.yaml b/packer/goss/goss-package.yaml new file mode 100644 index 0000000..3b41225 --- /dev/null +++ b/packer/goss/goss-package.yaml @@ -0,0 +1,86 @@ +{{ if ne .Vars.OS "windows"}} +kubernetes_version: &kubernetes_version + versions: + or: + - contain-element: + match-regexp: "^\\Q{{ .Vars.kubernetes_deb_version }}\\E$" + - contain-element: + match-regexp: "^\\Q{{ .Vars.kubernetes_rpm_version }}\\E$" + +kubernetes_cni_version: &kubernetes_cni_version + versions: + or: + - contain-element: + match-regexp: "^\\Q{{ .Vars.kubernetes_cni_deb_version }}\\E$" + - contain-element: + match-regexp: "^\\Q{{ .Vars.kubernetes_cni_rpm_version }}\\E$" + +package: +# Flatcar uses Ignition instead of cloud-init +{{if ne .Vars.OS "flatcar"}} + cloud-init: + installed: true +{{end}} + ntp: + installed: false +{{if eq .Vars.kubernetes_source_type "pkg"}} + kubeadm: + installed: true + <<: *kubernetes_version + kubelet: + installed: true + <<: *kubernetes_version + kubectl: + installed: true + <<: *kubernetes_version +{{end}} +{{if eq .Vars.kubernetes_cni_source_type "pkg"}} + kubernetes-cni: + installed: true + <<: *kubernetes_cni_version +{{end}} +# Looping over common packages for an OS +{{range $name, $vers := index .Vars .Vars.OS "common-package"}} + {{$name}}: + installed: true + {{range $key, $val := $vers}} + {{$key}}: {{$val}} + {{end}} +{{end}} +# Looping over provider specific packages for an OS +{{range $name, $vers := index .Vars .Vars.OS .Vars.PROVIDER "package"}} + {{$name}}: + installed: true + {{range $key, $val := $vers}} + {{$key}}: {{$val}} + {{end}} +{{end}} + +# Iterate thru different OS Versions like RHEL7/8, Photon 3/4(future) etc. +{{$distro_version := .Vars.OS_VERSION}} +{{range $component := index .Vars .Vars.OS .Vars.PROVIDER "os_version"}} +{{if eq $distro_version (index $component "distro_version")}} + {{ range $name, $vers := index $component "package"}} + {{$name}}: + installed: true + {{range $key, $val := $vers}} + {{$key}}: {{$val}} + {{end}} + {{end}} +{{end}} +{{end}} +{{end}} + +{{ if eq .Vars.OS "windows"}} # Windows +# Workaround until windows features are added to goss +command: +{{range $name, $vers := index .Vars .Vars.OS "common-windows-features"}} + "Windows Feature - {{ $name }}": + exec: powershell -command "(Get-WindowsFeature {{ $name }} | select *)" + exit-status: 0 + stdout: {{range $vers.expected}} + - {{.}} + timeout: 60000 + {{end}} +{{end}} +{{end}} diff --git a/packer/goss/goss-service.yaml b/packer/goss/goss-service.yaml new file mode 100644 index 0000000..44e03ee --- /dev/null +++ b/packer/goss/goss-service.yaml @@ -0,0 +1,77 @@ +service: +{{ if ne .Vars.OS "windows"}} # Linux + containerd: + enabled: true + running: true + dockerd: + enabled: false + running: false + kubelet: + enabled: true + running: false + conntrackd: + enabled: false + running: false + auditd: + enabled: true + running: true + {{if ne .Vars.OS "flatcar"}} + # Flatcar uses systemd-timesyncd instead of chronyd. + chronyd: + enabled: true + running: true + {{end}} +{{range $name, $vers := index .Vars .Vars.OS "common-service"}} + {{ $name }}: + {{range $key, $val := $vers}} + {{$key}}: {{$val}} + {{end}} +{{end}} +{{range $name, $vers := index .Vars .Vars.OS .Vars.PROVIDER "service"}} + {{ $name }}: + {{range $key, $val := $vers}} + {{$key}}: {{$val}} + {{end}} +{{end}} +{{end}} + +{{ if eq .Vars.OS "windows"}} # Windows +# Workaround until windows services are added to goss +command: +{{range $name, $vers := index .Vars .Vars.OS "common-windows-service"}} + "Windows Service - {{ $name }}": + exec: powershell -command "(Get-Service {{ $name }} | select *)" + exit-status: 0 + stdout: {{range $vers.expected}} + - {{.}} + {{end}} +{{end}} +{{range $name, $vers := index .Vars .Vars.OS .Vars.PROVIDER "windows-service"}} + "Windows Service - {{ $name }}": + exec: powershell -command "(Get-Service {{ $name }} | select *)" + exit-status: 0 + stdout: {{range $vers.expected}} + - {{.}} + {{end}} +{{end}} + +{{ if eq .Vars.runtime "docker-ee" }} + + "Windows Service - docker": + exec: powershell -command "(Get-Service docker | select *)" + exit-status: 0 + stdout: + - Automatic + - Running +{{end}} + +{{ if eq .Vars.runtime "containerd"}} + "Windows Service - containerd": + exec: powershell -command "(Get-Service containerd | select *)" + exit-status: 0 + stdout: + - Automatic + - Running +{{end}} + +{{end}} diff --git a/packer/goss/goss-vars.yaml b/packer/goss/goss-vars.yaml new file mode 100644 index 0000000..42b7476 --- /dev/null +++ b/packer/goss/goss-vars.yaml @@ -0,0 +1,546 @@ +--- +common_rpms: &common_rpms + audit: + ca-certificates: + cloud-init: + cloud-utils-growpart: + conntrack-tools: + chrony: + curl: + jq: + python3-pip: + socat: + sysstat: + yum-utils: + +al2_rpms: &al2_rpms + ebtables: + python-netifaces: + python-requests: + +rh7_rpms: &rh7_rpms + ebtables: + python-netifaces: + python-requests: + +rh8_rpms: &rh8_rpms + nftables: + python3-netifaces: + python3-requests: + +common_debs: &common_debs + auditd: + apt-transport-https: + conntrack: + chrony: + curl: + ebtables: + jq: + gnupg: + libnetfilter-acct1: + libnetfilter-cttimeout1: + libnetfilter-log1: + python3-distutils: + python3-netifaces: + python3-pip: + socat: + +chrony_deb: &chrony_deb + chrony: + ntp: + skip: true + installed: false + +common_photon_rpms: &common_photon_rpms + audit: + apparmor-parser: + conntrack-tools: + chrony: + distrib-compat: + ebtables: + net-tools: + openssl-c_rehash: + python3-pip: + rng-tools: + socat: + tar: + unzip: + +photon_3_rpms: &photon_3_rpms + python-netifaces: + python-requests: + jq: + +photon_4_rpms: &photon_4_rpms + jq: + +arch: "amd64" +containerd_version: "" +containerd_wasm_shims_runtimes: "" +kubernetes_cni_source_type: "" +kubernetes_cni_version: "" +kubernetes_source_type: "" +kubernetes_version: "" +kubernetes_rpm_version: "" +kubernetes_deb_version: "" +kubernetes_cni_deb_version: "" +kubernetes_cni_rpm_version: "" +# When k8s and k8s cni source is http +kubernetes_load_additional_imgs: false + +#windows variables +kubernetes_install_path: "" +windows_service_manager: "" +distribution_version: "" +runtime: "" + +# OS Specific package/Command/Kernal Params etc... +# Structured in below format +# OS_NAME +# common-package: +# common-kernel-params: +# common-services: +# PROVIDER_NAME: +# package: +# command: +# service: +# ... +amazon linux: + common-package: *common_rpms + amazon: + service: + amazon-ssm-agent: + enabled: true + running: true + package: + awscli: + amazon-ssm-agent: + <<: *al2_rpms +centos: + common-package: *common_rpms + amazon: + package: + amazon-ssm-agent: + <<: *rh7_rpms + command: + pip3 list --format=columns | grep 'awscli' | awk -F' ' '{print $1}': + exit-status: 0 + stdout: ["awscli"] + stderr: [] + timeout: 0 + azure: + package: + open-vm-tools: + azure-cli: + ova: + package: + python2-pip: + open-vm-tools: + <<: *rh7_rpms + qemu: + package: + open-vm-tools: + cloud-init: + cloud-utils-growpart: + python2-pip: + <<: *rh7_rpms + raw: + package: + cloud-init: + cloud-utils-growpart: + python2-pip: +flatcar: + common-service: + containerd: + enabled: true + running: true + systemd-timesyncd: + enabled: true + running: true + amazon: + command: + azure: + command: + qemu: + command: + raw: + command: + ova: + command: + nutanix: + command: +photon: + common-service: + apparmor: + enabled: false + running: false + common-kernel-param: + net.ipv4.tcp_limit_output_bytes: + value: "524288" + common-package: + <<: *common_photon_rpms + audit: + ova: + command: + grep apparmor=0 /boot/photon.cfg: + exit-status: 0 + stdout: ["apparmor=0"] + stderr: [] + timeout: 0 + service: + networkd-dispatcher: + enabled: true + running: true + package: + open-vm-tools: + cloud-init: + cloud-utils: + python3-netifaces: + os_version: + - distro_version: "3" + package: + <<: *photon_3_rpms + - distro_version: "4" + package: + <<: *photon_4_rpms +rockylinux: + common-package: *common_rpms + amazon: + package: + amazon-ssm-agent: + <<: *rh8_rpms + command: + pip3 list --format=columns | grep 'awscli' | awk -F' ' '{print $1}': + exit-status: 0 + stdout: [ "awscli" ] + stderr: [ ] + timeout: 0 + service: + amazon-ssm-agent: + enabled: true + running: true + ova: + package: + open-vm-tools: + python2-pip: + <<: *rh8_rpms + qemu: + package: + open-vm-tools: + cloud-init: + cloud-utils: + python3-netifaces: + <<: *rh8_rpms + raw: + package: + cloud-init: + cloud-utils: + python3-netifaces: + <<: *rh8_rpms + nutanix: + package: + cloud-init: + python3-netifaces: + iscsi-initiator-utils: + nfs-utils: + lvm2: + xfsprogs: + <<: *rh8_rpms + service: + iscsid: + enabled: true + running: true +rhel: + common-package: *common_rpms + amazon: + package: + amazon-ssm-agent: + os_version: + - distro_version: "8" + package: + <<: *rh8_rpms + command: + pip3 list --format=columns | grep 'awscli' | awk -F' ' '{print $1}': + exit-status: 0 + stdout: [ "awscli" ] + stderr: [ ] + timeout: 0 + service: + amazon-ssm-agent: + enabled: true + running: true + azure: + package: + open-vm-tools: + azure-cli: + os_version: + - distro_version: "8" + package: + <<: *rh8_rpms + ova: + package: + python2-pip: + open-vm-tools: + os_version: + - distro_version: "7" + package: + <<: *rh7_rpms + - distro_version: "8" + package: + <<: *rh8_rpms + qemu: + package: + open-vm-tools: + cloud-init: + cloud-utils-growpart: + python2-pip: + <<: *rh7_rpms + raw: + package: + cloud-init: + cloud-utils-growpart: + python2-pip: + <<: *rh7_rpms +ubuntu: + common-kernel-param: + net.ipv4.conf.all.rp_filter: + value: "1" + common-package: + <<: *common_debs + common-service: + apt-daily.timer: + enabled: false + running: false + apt-daily-upgrade.timer: + enabled: false + running: false + azure: + command: + pip3 list --format=columns | grep 'azure-cli' | awk -F' ' '{print $1}': + exit-status: 0 + stdout: ["azure-cli"] + stderr: [] + timeout: 0 + iptables -C FORWARD -d 168.63.129.16/32 -p tcp -m tcp --dport 80 -m comment --comment "block traffic to 168.63.129.16 for cve-2021-27075" -j DROP: + exit-status: 0 + timeout: 0 + package: + open-vm-tools: + linux-cloud-tools-virtual: + linux-tools-virtual: + <<: *chrony_deb + service: + chrony: + enabled: true + running: true + amazon: + service: + snap.amazon-ssm-agent.amazon-ssm-agent.service: + enabled: true + running: true + package: + linux-cloud-tools-virtual: + linux-tools-virtual: + command: + snap list | grep 'amazon-ssm-agent' | awk -F' ' '{print $1}': + exit-status: 0 + stdout: ["amazon-ssm-agent"] + stderr: [] + timeout: 0 + pip3 list --format=columns | grep 'awscli' | awk -F' ' '{print $1}': + exit-status: 0 + stdout: ["awscli"] + stderr: [] + timeout: 0 + gcp: + package: + linux-cloud-tools-virtual: + linux-tools-virtual: + command: + find -L /bin -maxdepth 1 -type f -executable -printf "%f\n" | grep -Fx 'gcloud': + exit-status: 0 + stdout: ["gcloud"] + stderr: [] + timeout: 0 + oci: + service: + package: + command: + outscale: + package: + linux-cloud-tools-virtual: + linux-tools-virtual: + ova: + service: + networkd-dispatcher: + enabled: true + running: true + package: + linux-cloud-tools-virtual: + linux-tools-virtual: + open-vm-tools: + cloud-guest-utils: + cloud-initramfs-copymods: + cloud-initramfs-dyn-netconf: + qemu: + package: + linux-cloud-tools-virtual: + linux-tools-virtual: + open-vm-tools: + cloud-guest-utils: + cloud-initramfs-copymods: + cloud-initramfs-dyn-netconf: + raw: + package: + cloud-guest-utils: + cloud-initramfs-copymods: + cloud-initramfs-dyn-netconf: + linux-cloud-tools-generic: + linux-tools-generic: + nutanix: + package: + linux-cloud-tools-virtual: + linux-tools-virtual: + cloud-guest-utils: + cloud-initramfs-copymods: + cloud-initramfs-dyn-netconf: + open-iscsi: + xfsprogs: + mdadm: + nfs-common: + service: + iscsid: + enabled: true + running: true + +oracle linux: + common-kernel-param: + common-package: + <<: *common_rpms + common-service: + oci: + command: + service: + package: + <<: *rh8_rpms + +# Windows specific variables +windows: + common-windows-features: + Hyper-V-PowerShell: + expected: + - Installed + Containers: + expected: + - Installed + + common-files: + c:/etc/kubernetes/pki: + exists: true + filetype: directory + contains: + c:/etc/kubernetes: + exists: true + filetype: directory + contains: + c:/etc/kubernetes/manifests: + exists: true + filetype: directory + contains: + c:/var/log/kubelet: + exists: true + filetype: directory + contains: + + common-windows-service: + cloudbase-init: + expected: + - Manual + - Stopped + kubelet: + expected: + - Automatic + - "/RequiredServices.+:.+(containerd|docker)/" + sshd: + expected: + - Automatic + - Running + + azure: + windows-service: + + files: + 'c:/program files/Cloudbase Solutions/Cloudbase-init/conf/cloudbase-init.conf': + exists: true + filetype: file + contains: + - "COM2,115200,N,8" + - "metadata_services=cloudbaseinit.metadata.services.azureservice.AzureService" + - "cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin" + - "cloudbaseinit.plugins.windows.azureguestagent.AzureGuestAgentPlugin" + - "cloudbaseinit.plugins.common.mtu.MTUPlugin" + - "cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin" + ova: + windows-service: + vmtools: + expected: + - Automatic + - Running + files: + 'c:/program files/Cloudbase Solutions/Cloudbase-init/conf/cloudbase-init.conf': + exists: true + filetype: file + contains: + - "!/logging_serial_port=COM1,115200,N,8/" + - "cloudbaseinit.metadata.services.vmwareguestinfoservice.VMwareGuestInfoService" + - "cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin" + - "cloudbaseinit.plugins.common.mtu.MTUPlugin" + - "cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin" + - "cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin" + - "cloudbaseinit.plugins.common.userdata.UserDataPlugin" + - "cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin" + - "cloudbaseinit.plugins.windows.createuser.CreateUserPlugin" + - "cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin" + 'c:/program files/Cloudbase Solutions/Cloudbase-init/conf/cloudbase-init-unattend.conf': + exists: true + filetype: file + contains: + - "metadata_services=cloudbaseinit.metadata.services.vmwareguestinfoservice.VMwareGuestInfoService" + amazon: + windows-service: + + files: + 'c:/program files/Cloudbase Solutions/Cloudbase-init/conf/cloudbase-init.conf': + exists: true + filetype: file + contains: + - "!/logging_serial_port=COM1,115200,N,8/" + - "metadata_services=cloudbaseinit.metadata.services.ec2service.EC2Service" + - "cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin" + - "cloudbaseinit.plugins.common.mtu.MTUPlugin" + - "cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin" + - "cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin" + + nutanix: + windows-service: + + files: + 'c:/program files/Cloudbase Solutions/Cloudbase-init/conf/cloudbase-init.conf': + exists: true + filetype: file + contains: + - "!/logging_serial_port=COM1,115200,N,8/" + - "cloudbaseinit.metadata.services.configdrive.ConfigDriveService" + - "cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin" + - "cloudbaseinit.plugins.common.mtu.MTUPlugin" + - "cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin" + - "cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin" + - "cloudbaseinit.plugins.common.userdata.UserDataPlugin" + - "cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin" + - "cloudbaseinit.plugins.windows.createuser.CreateUserPlugin" + - "cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin" + 'c:/program files/Cloudbase Solutions/Cloudbase-init/conf/cloudbase-init-unattend.conf': + exists: true + filetype: file + contains: + - "metadata_services=cloudbaseinit.metadata.services.base.EmptyMetadataService" \ No newline at end of file diff --git a/packer/goss/goss.yaml b/packer/goss/goss.yaml new file mode 100644 index 0000000..aff8aee --- /dev/null +++ b/packer/goss/goss.yaml @@ -0,0 +1,6 @@ +gossfile: + goss-command.yaml: {} + goss-kernel-params.yaml: {} + goss-service.yaml: {} + goss-package.yaml: {} + goss-files.yaml: {} diff --git a/packer/nutanix/OWNERS b/packer/nutanix/OWNERS new file mode 100644 index 0000000..b552638 --- /dev/null +++ b/packer/nutanix/OWNERS @@ -0,0 +1,7 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - cluster-api-nutanix-maintainers + +reviewers: + - cluster-api-nutanix-reviewers diff --git a/packer/nutanix/config.pkr.hcl b/packer/nutanix/config.pkr.hcl new file mode 100644 index 0000000..cf940f9 --- /dev/null +++ b/packer/nutanix/config.pkr.hcl @@ -0,0 +1,8 @@ +packer { + required_plugins { + nutanix = { + version = ">= 0.3.1" + source = "github.com/nutanix-cloud-native/nutanix" + } + } +} diff --git a/packer/nutanix/flatcar.json b/packer/nutanix/flatcar.json new file mode 100644 index 0000000..0f57bd1 --- /dev/null +++ b/packer/nutanix/flatcar.json @@ -0,0 +1,20 @@ +{ + "ansible_extra_vars": "ansible_python_interpreter=/opt/bin/python3", + "build_name": "flatcar-{{env `FLATCAR_CHANNEL`}}-{{env `FLATCAR_VERSION`}}", + "channel_name": "{{env `FLATCAR_CHANNEL`}}", + "crictl_source_type": "http", + "distribution": "flatcar", + "distribution_release": "Core", + "distribution_version": "{{env `FLATCAR_CHANNEL`}}", + "distro_name": "flatcar", + "guest_os_type": "Linux", + "image_url": "flatcar_production_openstack_image.img", + "kubernetes_cni_source_type": "http", + "kubernetes_source_type": "http", + "python_path": "/opt/bin/builder-env/site-packages", + "shutdown_command": "shutdown -P now", + "systemd_prefix": "/etc/systemd", + "sysusr_prefix": "/opt", + "sysusrlocal_prefix": "/opt", + "user_data": "ewogICAgImlnbml0aW9uIjogewogICAgICAgICJjb25maWciOiB7fSwKICAgICAgICAic2VjdXJpdHkiOiB7CiAgICAgICAgICAgICJ0bHMiOiB7fQogICAgICAgIH0sCiAgICAgICAgInRpbWVvdXRzIjoge30sCiAgICAgICAgInZlcnNpb24iOiAiMi4zLjAiCiAgICB9LAogICAgIm5ldHdvcmtkIjoge30sCiAgICAicGFzc3dkIjogewogICAgICAgICJ1c2VycyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImdyb3VwcyI6IFsKICAgICAgICAgICAgICAgICAgICAid2hlZWwiLAogICAgICAgICAgICAgICAgICAgICJzdWRvIiwKICAgICAgICAgICAgICAgICAgICAiZG9ja2VyIgogICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICJuYW1lIjogImJ1aWxkZXIiLAogICAgICAgICAgICAgICAgInBhc3N3b3JkSGFzaCI6ICIkNSQydU9Kc3M1ekpOalFZJHpYSVc3VFNxcUROTXZEbXFseUVybWQveE5UaGNDT1ZwRjZFUTZvR2JmaDUiCiAgICAgICAgICAgIH0KICAgICAgICBdCiAgICB9LAogICAgInN0b3JhZ2UiOiB7fSwKICAgICJzeXN0ZW1kIjogewogICAgICAgICJ1bml0cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm5hbWUiOiAiZG9ja2VyLnNlcnZpY2UiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJuYW1lIjogImZsYXRjYXItb3BlbnN0YWNrLWhvc3RuYW1lLnNlcnZpY2UiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJtYXNrIjogdHJ1ZSwKICAgICAgICAgICAgICAgICJuYW1lIjogInVwZGF0ZS1lbmdpbmUuc2VydmljZSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm1hc2siOiB0cnVlLAogICAgICAgICAgICAgICAgIm5hbWUiOiAibG9ja3NtaXRoZC5zZXJ2aWNlIgogICAgICAgICAgICB9CiAgICAgICAgXQogICAgfQp9" +} diff --git a/packer/nutanix/nutanix.json b/packer/nutanix/nutanix.json new file mode 100644 index 0000000..89e0712 --- /dev/null +++ b/packer/nutanix/nutanix.json @@ -0,0 +1,11 @@ +{ + "force_deregister": "true", + "nutanix_cluster_name": "", + "nutanix_endpoint": "", + "nutanix_insecure": "false", + "nutanix_password": "", + "nutanix_port": "9440", + "nutanix_subnet_name": "", + "nutanix_username": "admin", + "scp_extra_vars": "" +} diff --git a/packer/nutanix/packer-windows.json b/packer/nutanix/packer-windows.json new file mode 100644 index 0000000..6558260 --- /dev/null +++ b/packer/nutanix/packer-windows.json @@ -0,0 +1,149 @@ +{ + "builders": [ + { + "boot_type": "{{user `boot_type`}}", + "cd_files": [ + "./packer/nutanix/windows/{{user `build_name`}}/autounattend.xml", + "./packer/nutanix/windows/disable-network-discovery.cmd", + "./packer/nutanix/windows/sysprep.ps1" + ], + "cd_label": "OEMDRV", + "cluster_name": "{{user `nutanix_cluster_name`}}", + "communicator": "winrm", + "force_deregister": "{{user `force_deregister`}}", + "image_description": "kube image-builder packer", + "image_name": "{{user `image_name`}}", + "memory_mb": "{{user `memory`}}", + "nutanix_endpoint": "{{user `nutanix_endpoint`}}", + "nutanix_insecure": "{{user `nutanix_insecure`}}", + "nutanix_password": "{{user `nutanix_password`}}", + "nutanix_port": "{{user `nutanix_port`}}", + "nutanix_username": "{{user `nutanix_username`}}", + "os_type": "{{user `guest_os_type`}}", + "shutdown_command": "powershell F:/sysprep.ps1", + "shutdown_timeout": "1h", + "type": "nutanix", + "vm_disks": [ + { + "image_type": "ISO_IMAGE", + "source_image_name": "{{user `source_image_name`}}" + }, + { + "image_type": "ISO_IMAGE", + "source_image_name": "{{user `virtio_image_name`}}" + }, + { + "disk_size_gb": "{{user `disk_size_gb`}}", + "image_type": "DISK" + } + ], + "vm_name": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", + "vm_nics": { + "subnet_name": "{{user `nutanix_subnet_name`}}" + }, + "winrm_insecure": true, + "winrm_password": "S3cr3t0!", + "winrm_port": 5986, + "winrm_timeout": "4h", + "winrm_use_ssl": true, + "winrm_username": "Administrator" + } + ], + "provisioners": [ + { + "extra_arguments": [ + "-e", + "ansible_winrm_server_cert_validation=ignore", + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}" + ], + "playbook_file": "ansible/windows/node_windows.yml", + "type": "ansible", + "use_proxy": false, + "user": "Administrator" + }, + { + "restart_timeout": "10m", + "type": "windows-restart" + }, + { + "arch": "{{user `goss_arch`}}", + "download_path": "{{user `goss_download_path`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "remote_folder": "{{user `goss_remote_folder`}}", + "remote_path": "{{user `goss_remote_path`}}", + "skip_install": "{{user `goss_skip_install`}}", + "target_os": "Windows", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": false, + "vars_env": { + "GOSS_MAX_CONCURRENT": "1", + "GOSS_USE_ALPHA": "1" + }, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "OS": "{{user `distro_name` | lower}}", + "PROVIDER": "nutanix", + "containerd_version": "{{user `containerd_version`}}", + "distribution_version": "{{user `distro_version`}}", + "docker_ee_version": "{{user `docker_ee_version`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}", + "pause_image": "{{user `pause_image`}}", + "runtime": "{{user `runtime`}}", + "ssh_source_url": "{{user `ssh_source_url`}}" + }, + "version": "{{user `goss_version`}}" + }, + { + "inline": [ + "rm -Force -Recurse C:\\var\\log\\kubelet\\*" + ], + "type": "powershell" + } + ], + "variables": { + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_user_vars": "", + "build_timestamp": "{{timestamp}}", + "cloudbase_init_url": "https://github.com/cloudbase/cloudbase-init/releases/download/{{user `cloudbase_init_version`}}/CloudbaseInitSetup_{{user `cloudbase_init_version` | replace_all `.` `_` }}_x64.msi", + "cloudbase_metadata_services": "cloudbaseinit.metadata.services.configdrive.ConfigDriveService", + "cloudbase_metadata_services_unattend": "cloudbaseinit.metadata.services.base.EmptyMetadataService", + "cloudbase_plugins": "cloudbaseinit.plugins.windows.createuser.CreateUserPlugin, cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin, cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin, cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin, cloudbaseinit.plugins.common.mtu.MTUPlugin, cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin, cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin, cloudbaseinit.plugins.common.userdata.UserDataPlugin, cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin, cloudbaseinit.plugins.windows.createuser.CreateUserPlugin, cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin", + "cloudbase_plugins_unattend": "cloudbaseinit.plugins.common.mtu.MTUPlugin", + "containerd_sha256": null, + "containerd_url": "", + "containerd_version": null, + "cpus": "2", + "crictl_url": "", + "crictl_version": null, + "disk_size_gb": "40", + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "image_name": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", + "kubernetes_base_url": "https://kubernetesreleases.blob.core.windows.net/kubernetes/{{user `kubernetes_semver`}}/binaries/node/windows/{{user `kubernetes_goarch`}}", + "kubernetes_container_registry": null, + "kubernetes_http_package_url": "", + "kubernetes_http_source": null, + "kubernetes_install_path": "c:\\k", + "kubernetes_load_additional_imgs": null, + "kubernetes_semver": null, + "kubernetes_series": null, + "kubernetes_source_type": null, + "kubernetes_typed_version": "kube-{{user `kubernetes_semver`}}", + "machine_id_mode": "444", + "memory": "4096", + "scp_extra_vars": "", + "wins_url": null + } +} diff --git a/packer/nutanix/packer.json b/packer/nutanix/packer.json new file mode 100644 index 0000000..a29194d --- /dev/null +++ b/packer/nutanix/packer.json @@ -0,0 +1,142 @@ +{ + "builders": [ + { + "boot_type": "{{user `boot_type`}}", + "cluster_name": "{{user `nutanix_cluster_name`}}", + "cpu": "{{user `cpus`}}", + "force_deregister": "{{user `force_deregister`}}", + "image_description": "kube image-builder packer", + "image_name": "{{user `image_name`}}", + "memory_mb": "{{user `memory`}}", + "nutanix_endpoint": "{{user `nutanix_endpoint`}}", + "nutanix_insecure": "{{user `nutanix_insecure`}}", + "nutanix_password": "{{user `nutanix_password`}}", + "nutanix_port": "{{user `nutanix_port`}}", + "nutanix_username": "{{user `nutanix_username`}}", + "os_type": "{{user `guest_os_type`}}", + "shutdown_command": "echo '{{user `ssh_password`}}' | sudo -S -E sh -c 'usermod -L {{user `ssh_username`}} && {{user `shutdown_command`}}'", + "ssh_handshake_attempts": "100", + "ssh_password": "{{user `ssh_password`}}", + "ssh_timeout": "20m", + "ssh_username": "{{user `ssh_username`}}", + "type": "nutanix", + "user_data": "{{user `user_data`}}", + "vm_disks": { + "disk_size_gb": "{{user `disk_size_gb`}}", + "image_type": "DISK_IMAGE", + "source_image_uri": "{{user `image_url`}}" + }, + "vm_name": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", + "vm_nics": { + "subnet_name": "{{user `nutanix_subnet_name`}}" + } + } + ], + "post-processors": [ + { + "environment_vars": [ + "CUSTOM_POST_PROCESSOR={{user `custom_post_processor`}}" + ], + "inline": [ + "if [ \"$CUSTOM_POST_PROCESSOR\" != \"true\" ]; then exit 0; fi", + "{{user `custom_post_processor_command`}}" + ], + "name": "custom-post-processor", + "type": "shell-local" + } + ], + "provisioners": [ + { + "environment_vars": [ + "BUILD_NAME={{user `build_name`}}" + ], + "execute_command": "BUILD_NAME={{user `build_name`}}; if [[ \"${BUILD_NAME}\" == *\"flatcar\"* ]]; then sudo {{.Vars}} -S -E bash '{{.Path}}'; fi", + "script": "./packer/files/flatcar/scripts/bootstrap-flatcar.sh", + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} -o IdentitiesOnly=yes'" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args={{user `scp_extra_vars`}}" + ], + "playbook_file": "./ansible/node.yml", + "type": "ansible", + "user": "builder" + }, + { + "arch": "{{user `goss_arch`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": true, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "ARCH": "amd64", + "OS": "{{user `distro_name` | lower}}", + "PROVIDER": "nutanix", + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_deb_version": "{{ user `kubernetes_cni_deb_version` }}", + "kubernetes_cni_rpm_version": "{{ split (user `kubernetes_cni_rpm_version`) \"-\" 0 }}", + "kubernetes_cni_source_type": "{{user `kubernetes_cni_source_type`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver` | replace \"v\" \"\" 1}}", + "kubernetes_deb_version": "{{ user `kubernetes_deb_version` }}", + "kubernetes_rpm_version": "{{ split (user `kubernetes_rpm_version`) \"-\" 0 }}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "kubernetes_version": "{{user `kubernetes_semver` | replace \"v\" \"\" 1}}" + }, + "version": "{{user `goss_version`}}" + } + ], + "variables": { + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_user_vars": "", + "build_timestamp": "{{timestamp}}", + "containerd_sha256": null, + "containerd_url": "https://github.com/containerd/containerd/releases/download/v{{user `containerd_version`}}/cri-containerd-cni-{{user `containerd_version`}}-linux-amd64.tar.gz", + "containerd_version": null, + "cpus": "1", + "crictl_url": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-amd64.tar.gz", + "crictl_version": null, + "disk_size_gb": "10", + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "image_name": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", + "kubernetes_cni_deb_version": null, + "kubernetes_cni_http_source": null, + "kubernetes_cni_semver": null, + "kubernetes_cni_source_type": null, + "kubernetes_container_registry": null, + "kubernetes_deb_gpg_key": null, + "kubernetes_deb_repo": null, + "kubernetes_deb_version": null, + "kubernetes_http_source": null, + "kubernetes_load_additional_imgs": null, + "kubernetes_rpm_gpg_check": null, + "kubernetes_rpm_gpg_key": null, + "kubernetes_rpm_repo": null, + "kubernetes_rpm_version": null, + "kubernetes_semver": null, + "kubernetes_series": null, + "kubernetes_source_type": null, + "machine_id_mode": "444", + "memory": "2048", + "python_path": "", + "scp_extra_vars": "", + "ssh_password": "builder", + "ssh_username": "builder" + } +} diff --git a/packer/nutanix/rockylinux-8.json b/packer/nutanix/rockylinux-8.json new file mode 100644 index 0000000..defaa83 --- /dev/null +++ b/packer/nutanix/rockylinux-8.json @@ -0,0 +1,15 @@ +{ + "boot_type": "uefi", + "build_name": "rockylinux-8", + "distribution": "rockylinux", + "distribution_release": "Core", + "distribution_version": "8", + "distro_name": "rockylinux", + "epel_rpm_gpg_key": "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8", + "extra_rpms": "python3", + "guest_os_type": "Linux", + "image_url": "https://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud-Base-8.7-20221130.0.x86_64.qcow2", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm", + "shutdown_command": "shutdown -P now", + "user_data": "I2Nsb3VkLWNvbmZpZwp1c2VyczoKICAtIG5hbWU6IGJ1aWxkZXIKICAgIHN1ZG86IFsnQUxMPShBTEwpIE5PUEFTU1dEOkFMTCddCmNocGFzc3dkOgogIGxpc3Q6IHwKICAgIGJ1aWxkZXI6YnVpbGRlcgogIGV4cGlyZTogRmFsc2UKc3NoX3B3YXV0aDogVHJ1ZQ==" +} diff --git a/packer/nutanix/rockylinux-9.json b/packer/nutanix/rockylinux-9.json new file mode 100644 index 0000000..dd727e7 --- /dev/null +++ b/packer/nutanix/rockylinux-9.json @@ -0,0 +1,14 @@ +{ + "boot_type": "uefi", + "build_name": "rockylinux-9", + "distribution": "rockylinux", + "distribution_release": "Core", + "distribution_version": "9", + "distro_name": "rockylinux", + "epel_rpm_gpg_key": "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-9", + "guest_os_type": "Linux", + "image_url": "https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base-9.1-20221130.0.x86_64.qcow2", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm", + "shutdown_command": "shutdown -P now", + "user_data": "I2Nsb3VkLWNvbmZpZwp1c2VyczoKICAtIG5hbWU6IGJ1aWxkZXIKICAgIHN1ZG86IFsnQUxMPShBTEwpIE5PUEFTU1dEOkFMTCddCmNocGFzc3dkOgogIGxpc3Q6IHwKICAgIGJ1aWxkZXI6YnVpbGRlcgogIGV4cGlyZTogRmFsc2UKc3NoX3B3YXV0aDogVHJ1ZQ==" +} diff --git a/packer/nutanix/ubuntu-2004.json b/packer/nutanix/ubuntu-2004.json new file mode 100644 index 0000000..7c55d68 --- /dev/null +++ b/packer/nutanix/ubuntu-2004.json @@ -0,0 +1,8 @@ +{ + "build_name": "ubuntu-2004", + "distro_name": "ubuntu", + "guest_os_type": "Linux", + "image_url": "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img", + "shutdown_command": "shutdown -P now", + "user_data": "I2Nsb3VkLWNvbmZpZwp1c2VyczoKICAtIG5hbWU6IGJ1aWxkZXIKICAgIHN1ZG86IFsnQUxMPShBTEwpIE5PUEFTU1dEOkFMTCddCiAgICBzaGVsbDogL2Jpbi9iYXNoCmNocGFzc3dkOgogIGxpc3Q6IHwKICAgIGJ1aWxkZXI6YnVpbGRlcgogIGV4cGlyZTogRmFsc2UKc3NoX3B3YXV0aDogVHJ1ZQo=" +} diff --git a/packer/nutanix/ubuntu-2204.json b/packer/nutanix/ubuntu-2204.json new file mode 100644 index 0000000..39946a9 --- /dev/null +++ b/packer/nutanix/ubuntu-2204.json @@ -0,0 +1,8 @@ +{ + "build_name": "ubuntu-2204", + "distro_name": "ubuntu", + "guest_os_type": "Linux", + "image_url": "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img", + "shutdown_command": "shutdown -P now", + "user_data": "I2Nsb3VkLWNvbmZpZwp1c2VyczoKICAtIG5hbWU6IGJ1aWxkZXIKICAgIHN1ZG86IFsnQUxMPShBTEwpIE5PUEFTU1dEOkFMTCddCiAgICBzaGVsbDogL2Jpbi9iYXNoCmNocGFzc3dkOgogIGxpc3Q6IHwKICAgIGJ1aWxkZXI6YnVpbGRlcgogIGV4cGlyZTogRmFsc2UKc3NoX3B3YXV0aDogVHJ1ZQo=" +} diff --git a/packer/nutanix/windows-2022.json b/packer/nutanix/windows-2022.json new file mode 100644 index 0000000..5a64a5c --- /dev/null +++ b/packer/nutanix/windows-2022.json @@ -0,0 +1,11 @@ +{ + "build_name": "windows-2022", + "distro_name": "windows", + "distro_version": "2022", + "guest_os_type": "Windows", + "runtime": "containerd", + "shutdown_command": "shutdown /s /t 10 /f /d p:4:1 /c \"Packer Shutdown\"", + "source_image_name": "en-us_windows_server_2022_x64_dvd_620d7eac", + "virtio_image_name": "Nutanix-VirtIO-1.2.1", + "wins_url": "" +} diff --git a/packer/nutanix/windows/disable-network-discovery.cmd b/packer/nutanix/windows/disable-network-discovery.cmd new file mode 100644 index 0000000..d2c47db --- /dev/null +++ b/packer/nutanix/windows/disable-network-discovery.cmd @@ -0,0 +1,2 @@ +reg ADD HKLM\SYSTEM\CurrentControlSet\Control\Network\NewNetworkWindowOff /f +netsh advfirewall firewall set rule group="Network Discovery" new enable=No \ No newline at end of file diff --git a/packer/nutanix/windows/sysprep.ps1 b/packer/nutanix/windows/sysprep.ps1 new file mode 100644 index 0000000..571c759 --- /dev/null +++ b/packer/nutanix/windows/sysprep.ps1 @@ -0,0 +1,31 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Write-Output '>>> Sysprepping VM ...' + +Write-Output 'Removing default unattend.xml file...' +if( Test-Path $Env:SystemRoot\system32\Sysprep\unattend.xml ) { + Remove-Item $Env:SystemRoot\system32\Sysprep\unattend.xml -Force +} + +$unattendedXml = "$ENV:ProgramFiles\Cloudbase Solutions\Cloudbase-Init\conf\Unattend.xml" +$FileExists = Test-Path $unattendedXml + +If ($FileExists -eq $True) { + # Use the Cloudbase-init provided unattend file during install + Write-Output "Using cloudbase-init unattend file for sysprep: $unattendedXml" + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /shutdown /quiet /unattend:$unattendedXml +}else { + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /shutdown /quiet +} diff --git a/packer/nutanix/windows/windows-2022/autounattend.xml b/packer/nutanix/windows/windows-2022/autounattend.xml new file mode 100644 index 0000000..77d5e6d --- /dev/null +++ b/packer/nutanix/windows/windows-2022/autounattend.xml @@ -0,0 +1,265 @@ + + + + + + + + + e:\Windows Server 2022\x64 + + + + + + + + + 1 + 350 + Primary + + + 2 + true + Primary + + + + + NTFS + + 1 + 1 + 0x27 + + + 2 + 2 + C + + NTFS + + + 0 + true + + + + + + 0 + 2 + + + + /IMAGE/NAME + Windows Server 2022 SERVERSTANDARDCORE + + + + + + true + Administrator + Organization + + VDYBN-27WPP-V4HQT-9VMD4-VMK7H + OnError + + + true + + + + en-US + + 0409:00000409 + en-US + en-US + en-US + en-US + + + + + false + + + + + 1 + + + + + 0409:00000409 + en-US + en-US + en-US + en-US + + + true + + + 0 + + + + VDYBN-27WPP-V4HQT-9VMD4-VMK7H + + + + + + + S3cr3t0! +

true</PlainText> + </Password> + <Enabled>true</Enabled> + <Username>Administrator</Username> + </AutoLogon> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <Order>1</Order> + <Description>Set Execution Policy 64 Bit</Description> + <CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>2</Order> + <Description>Set Execution Policy 32 Bit</Description> + <CommandLine>%SystemDrive%\Windows\SysWOW64\cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v HideFileExt /t REG_DWORD /d 0 /f</CommandLine> + <Order>3</Order> + <Description>Show file extensions in Explorer</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\Console /v QuickEdit /t REG_DWORD /d 1 /f</CommandLine> + <Order>4</Order> + <Description>Enable QuickEdit mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v Start_ShowRun /t REG_DWORD /d 1 /f</CommandLine> + <Order>5</Order> + <Description>Show Run command in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v StartMenuAdminTools /t REG_DWORD /d 1 /f</CommandLine> + <Order>6</Order> + <Description>Show Administrative Tools in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateFileSizePercent /t REG_DWORD /d 0 /f</CommandLine> + <Order>7</Order> + <Description>Zero Hibernation File</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateEnabled /t REG_DWORD /d 0 /f</CommandLine> + <Order>8</Order> + <Description>Disable Hibernation Mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE</CommandLine> + <Order>9</Order> + <Description>Disable password expiration for Administrator user</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -Command New-SelfSignedCertificate -CertstoreLocation Cert:\LocalMachine\My -DnsName "WinRMCertificate"</CommandLine> + <Description>Certificate for WinRM</Description> + <Order>10</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -Command Enable-PSRemoting -SkipNetworkProfileCheck -Force</CommandLine> + <Description>Enable WinRM</Description> + <Order>11</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -Command ($cert = gci Cert:\LocalMachine\My\) -and (New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $cert.Thumbprint –Force)</CommandLine> + <Description>Add HTTPS WinRM listener with previously generated certificate</Description> + <Order>12</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -Command New-NetFirewallRule -DisplayName 'Windows Remote Management (HTTPS-In)' -Name 'Windows Remote Management (HTTPS-In)' -Profile Any -LocalPort 5986 -Protocol TCP</CommandLine> + <Description>Add firewall exception to TCP port 5986 for WinRM over HTTPS</Description> + <Order>13</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -Command Set-Item WSMan:\localhost\Service\Auth\Basic -Value $true</CommandLine> + <Description>Enable Basic authentication</Description> + <Order>14</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c f:\disable-network-discovery.cmd</CommandLine> + <Description>Disable Network Discovery</Description> + <Order>15</Order> + </SynchronousCommand> + </FirstLogonCommands> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Work</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + <SkipMachineOOBE>true</SkipMachineOOBE> + <SkipUserOOBE>true</SkipUserOOBE> + </OOBE> + <RegisteredOrganization>Organization</RegisteredOrganization> + <RegisteredOwner>Owner</RegisteredOwner> + <DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet> + <TimeZone>Pacific Standard Time</TimeZone> + <UserAccounts> + <AdministratorPassword> + <Value>S3cr3t0!</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Description>Administrator</Description> + <DisplayName>Administrator</DisplayName> + <Group>Administrators</Group> + <Name>Administrator</Name> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + </component> + </settings> +</unattend> diff --git a/packer/oci/oracle-linux-8.json b/packer/oci/oracle-linux-8.json new file mode 100644 index 0000000..3949ed1 --- /dev/null +++ b/packer/oci/oracle-linux-8.json @@ -0,0 +1,8 @@ +{ + "build_name": "oracle-linux-8", + "distribution": "Oracle Linux", + "operating_system": "Oracle Linux", + "operating_system_version": "8", + "redhat_epel_rpm": "oracle-epel-release-el8", + "ssh_username": "opc" +} diff --git a/packer/oci/oracle-linux-9.json b/packer/oci/oracle-linux-9.json new file mode 100644 index 0000000..fc13227 --- /dev/null +++ b/packer/oci/oracle-linux-9.json @@ -0,0 +1,8 @@ +{ + "build_name": "oracle-linux-9", + "distribution": "Oracle Linux", + "operating_system": "Oracle Linux", + "operating_system_version": "9", + "redhat_epel_rpm": "oracle-epel-release-el9", + "ssh_username": "opc" +} diff --git a/packer/oci/packer-windows.json b/packer/oci/packer-windows.json new file mode 100644 index 0000000..c9ee812 --- /dev/null +++ b/packer/oci/packer-windows.json @@ -0,0 +1,148 @@ +{ + "builders": [ + { + "availability_domain": "{{user `availability_domain`}}", + "base_image_filter": { + "operating_system": "{{user `operating_system`}}", + "operating_system_version": "{{user `operating_system_version`}}" + }, + "base_image_ocid": "{{user `base_image_ocid`}}", + "communicator": "winrm", + "compartment_ocid": "{{user `compartment_ocid`}}", + "image_name": "cluster-api-{{user `build_name`}}-{{user `kubernetes_semver`}}-{{user `build_timestamp`}}", + "shape": "{{user `shape`}}", + "shape_config": { + "ocpus": "{{user `ocpus`}}" + }, + "subnet_ocid": "{{user `subnet_ocid`}}", + "tenancy_ocid": "{{user `tenancy_ocid`}}", + "type": "oracle-oci", + "user_data_file": "packer/oci/scripts/winrm_bootstrap.txt", + "user_ocid": "{{user `user_ocid`}}", + "winrm_insecure": true, + "winrm_password": "{{user `opc_user_password`}}", + "winrm_port": 5986, + "winrm_timeout": "10m", + "winrm_use_ntlm": true, + "winrm_use_ssl": true, + "winrm_username": "opc" + } + ], + "post-processors": [ + { + "custom_data": { + "build_date": "{{isotime}}", + "build_name": "{{user `build_name`}}", + "build_timestamp": "{{user `build_timestamp`}}", + "build_type": "node", + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_semver": "{{user `kubernetes_cni_semver`}}", + "kubernetes_semver": "{{user `kubernetes_semver`}}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "os_name": "{{user `distro_name`}}", + "resource_group_name": "{{user `resource_group_name`}}", + "storage_account_name": "{{user `storage_account_name`}}" + }, + "output": "{{user `manifest_output`}}", + "strip_path": true, + "type": "manifest" + } + ], + "provisioners": [ + { + "elevated_password": "{{.WinRMPassword}}", + "elevated_user": "opc", + "script": "ansible/windows/ansible_winrm.ps1", + "type": "powershell" + }, + { + "extra_arguments": [ + "-e", + "ansible_winrm_server_cert_validation=ignore ansible_winrm_operation_timeout_sec=120 ansible_winrm_read_timeout_sec=150", + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `azure_extra_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}" + ], + "max_retries": 5, + "pause_before": "15s", + "playbook_file": "ansible/windows/node_windows.yml", + "type": "ansible", + "use_proxy": false, + "user": "opc" + }, + { + "restart_timeout": "10m", + "type": "windows-restart" + }, + { + "destination": "C:\\Users\\opc\\", + "source": "./packer/oci/scripts/sysprep.ps1", + "type": "file" + }, + { + "destination": "C:\\Users\\opc\\", + "source": "./packer/oci/scripts/attach_secondary_vnic.ps1", + "type": "file" + }, + { + "destination": "C:\\Windows\\Setup\\Scripts\\", + "source": "./packer/oci/scripts/enable_second_nic.ps1", + "type": "file" + }, + { + "inline": [ + "rm -Force -Recurse C:\\var\\log\\kubelet\\*" + ], + "type": "powershell" + }, + { + "elevated_password": "{{.WinRMPassword}}", + "elevated_user": "opc", + "inline": [ + "C:\\Users\\opc\\sysprep.ps1" + ], + "type": "powershell" + } + ], + "variables": { + "additional_debug_files": null, + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_user_vars": "", + "azure_extra_vars": "wire_server_users={{user `wire_server_users`}}", + "build_name": null, + "build_timestamp": "{{timestamp}}", + "cloudbase_init_url": "https://github.com/cloudbase/cloudbase-init/releases/download/{{user `cloudbase_init_version`}}/CloudbaseInitSetup_{{user `cloudbase_init_version` | replace_all `.` `_` }}_x64.msi", + "cloudbase_logging_serial_port": "COM1,9600,N,8", + "cloudbase_metadata_services": "cloudbaseinit.metadata.services.httpservice.HttpService", + "cloudbase_metadata_services_unattend": "cloudbaseinit.metadata.services.httpservice.HttpService", + "cloudbase_plugins": "cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin, cloudbaseinit.plugins.common.mtu.MTUPlugin, cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin", + "cloudbase_plugins_unattend": "cloudbaseinit.plugins.common.mtu.MTUPlugin", + "containerd_url": "", + "containerd_version": null, + "ib_version": "{{env `IB_VERSION`}}", + "image_version": "latest", + "kubernetes_base_url": "https://kubernetesreleases.blob.core.windows.net/kubernetes/{{user `kubernetes_semver`}}/binaries/node/windows/{{user `kubernetes_goarch`}}", + "manifest_output": "manifest.json", + "nssm_url": null, + "ocpus": "2", + "opc_user_password": "{{env `OPC_USER_PASSWORD`}}", + "prepull": null, + "private_virtual_network_with_public_ip": "", + "runtime": "containerd", + "shape": "VM.Standard.E4.Flex", + "virtual_network_name": "", + "virtual_network_resource_group_name": "", + "virtual_network_subnet_name": "", + "vm_size": "", + "windows_service_manager": null, + "windows_updates_kbs": null, + "wins_url": "", + "wire_server_users": "" + } +} diff --git a/packer/oci/packer.json b/packer/oci/packer.json new file mode 100644 index 0000000..7205ae6 --- /dev/null +++ b/packer/oci/packer.json @@ -0,0 +1,150 @@ +{ + "builders": [ + { + "availability_domain": "{{user `availability_domain`}}", + "base_image_filter": { + "operating_system": "{{user `operating_system`}}", + "operating_system_version": "{{user `operating_system_version`}}" + }, + "base_image_ocid": "{{user `base_image_ocid`}}", + "compartment_ocid": "{{user `compartment_ocid`}}", + "fingerprint": "{{user `fingerprint`}}", + "image_name": "cluster-api-{{user `build_name`}}-{{user `kubernetes_semver`}}-{{user `build_timestamp`}}", + "key_file": "{{user `key_file`}}", + "region": "{{user `region`}}", + "shape": "{{user `shape`}}", + "shape_config": { + "ocpus": "{{user `ocpus`}}" + }, + "ssh_private_key_file": "{{ user `ssh_private_key_file`}}", + "ssh_username": "{{user `ssh_username`}}", + "subnet_ocid": "{{user `subnet_ocid`}}", + "tags": { + "build_date": "{{isotime}}", + "build_timestamp": "{{user `build_timestamp`}}", + "containerd_version": "{{user `containerd_version`}}", + "distribution": "{{user `operating_system`}}", + "distribution_version": "{{user `operating_system_version`}}", + "image_builder_version": "{{user `ib_version`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}" + }, + "tenancy_ocid": "{{user `tenancy_ocid`}}", + "type": "oracle-oci", + "use_private_ip": "{{user `use_private_ip`}}", + "user_data": "{{user `user_data`}}", + "user_ocid": "{{user `user_ocid`}}" + } + ], + "provisioners": [ + { + "environment_vars": [ + "BUILD_NAME={{user `build_name`}}" + ], + "inline": [ + "if [ $BUILD_NAME != \"ubuntu-1804\" ] || [ $BUILD_NAME != \"ubuntu-2004\" ]; then exit 0; fi", + "while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done", + "sudo apt-get -qq update && sudo DEBIAN_FRONTEND=noninteractive apt-get -qqy install python python-pip" + ], + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}" + ], + "playbook_file": "./ansible/node.yml", + "type": "ansible" + }, + { + "arch": "{{user `goss_arch`}}", + "download_path": "{{user `goss_download_path`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "remote_folder": "{{user `goss_remote_folder`}}", + "remote_path": "{{user `goss_remote_path`}}", + "skip_install": "{{user `goss_skip_install`}}", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": true, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "ARCH": "amd64", + "OS": "{{user `distribution` | lower }}", + "PROVIDER": "oci", + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_deb_version": "{{ user `kubernetes_cni_deb_version` }}", + "kubernetes_cni_rpm_version": "{{ split (user `kubernetes_cni_rpm_version`) \"-\" 0 }}", + "kubernetes_cni_source_type": "{{user `kubernetes_cni_source_type`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver` | replace \"v\" \"\" 1}}", + "kubernetes_deb_version": "{{ user `kubernetes_deb_version` }}", + "kubernetes_rpm_version": "{{ split (user `kubernetes_rpm_version`) \"-\" 0 }}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "kubernetes_version": "{{user `kubernetes_semver` | replace \"v\" \"\" 1}}" + }, + "version": "{{user `goss_version`}}" + } + ], + "variables": { + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_user_vars": "", + "availability_domain": "{{env `OCI_AVAILABILITY_DOMAIN`}}", + "base_image_ocid": "", + "build_timestamp": "{{timestamp}}", + "compartment_ocid": "", + "containerd_sha256": null, + "containerd_url": "https://github.com/containerd/containerd/releases/download/v{{user `containerd_version`}}/cri-containerd-cni-{{user `containerd_version`}}-linux-amd64.tar.gz", + "containerd_version": null, + "crictl_url": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-amd64.tar.gz", + "crictl_version": null, + "disable_default_service_account": "", + "encrypted": "false", + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "fingerprint": "{{env `OCI_USER_FINGERPRINT`}}", + "ib_version": "{{env `IB_VERSION`}}", + "key_file": "{{env `OCI_USER_KEY_FILE`}}", + "kubernetes_cni_deb_version": null, + "kubernetes_cni_http_source": null, + "kubernetes_cni_rpm_version": null, + "kubernetes_cni_semver": null, + "kubernetes_cni_source_type": null, + "kubernetes_container_registry": null, + "kubernetes_deb_gpg_key": null, + "kubernetes_deb_repo": null, + "kubernetes_deb_version": null, + "kubernetes_http_source": null, + "kubernetes_load_additional_imgs": null, + "kubernetes_rpm_gpg_check": null, + "kubernetes_rpm_gpg_key": null, + "kubernetes_rpm_repo": null, + "kubernetes_rpm_version": null, + "kubernetes_semver": null, + "kubernetes_series": null, + "kubernetes_source_type": null, + "ocpus": "1", + "operating_system": null, + "operating_system_version": null, + "region": "us-ashburn-1", + "shape": "VM.Standard.E4.Flex", + "ssh_private_key_file": "", + "ssh_username": null, + "subnet_ocid": "{{env `OCI_SUBNET_OCID`}}", + "tenancy_ocid": "{{env `OCI_TENANCY_OCID`}}", + "use_private_ip": "false", + "user_data": "", + "user_ocid": "{{env `OCI_USER_OCID`}}" + } +} diff --git a/packer/oci/scripts/attach_secondary_vnic.ps1 b/packer/oci/scripts/attach_secondary_vnic.ps1 new file mode 100644 index 0000000..4bbb53d --- /dev/null +++ b/packer/oci/scripts/attach_secondary_vnic.ps1 @@ -0,0 +1,39 @@ +function Get-Second-Vnic-Ocid() { + $ocid = "" + $vnics = Invoke-RestMethod -Uri "http://169.254.169.254/opc/v1/vnics/" + if ($vnics.Count -eq 2) { + $ocid = $vnics[1].vnicId + } else { + Write-Host "vnics count not equal 2" + } + return $ocid +} + +$vnicId = Get-Second-Vnic-Ocid +Write-Host "found vnic id: ${vnicId}" + + +$retryDelaySeconds = 30 +# We should continue to retry indefinitely until the vnic is +# detected by IMDS +# https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm +while($vnicId -eq "") { + $vnicId = Get-Second-Vnic-Ocid + Write-Host("Getting second vnic failed. Waiting " + $retryDelaySeconds + " seconds before next attempt.") + Start-Sleep -Seconds $retryDelaySeconds +} + +if ($vnicId -ne "") { + Write-Host "Pulling down the secondary_vnic_windows_configure.ps1" + Invoke-WebRequest -Uri "https://docs.oracle.com/en-us/iaas/Content/Resources/Assets/secondary_vnic_windows_configure.ps1" -OutFile "C:\Users\opc\secondary_vnic_windows_configure.ps1" + + Write-Host "calling script using ${vnicId}" + + , 'Y', 'A' | powershell "C:\Users\opc\secondary_vnic_windows_configure.ps1 ${vnicId}" + Write-Error "secondary_vnic_windows_configure.ps1 - done" + + $ipconfig = ipconfig + Write-Error "${ipconfig}" +}else{ + Write-Error "VNIC OCID is empty. Can't configure." +} \ No newline at end of file diff --git a/packer/oci/scripts/enable_second_nic.ps1 b/packer/oci/scripts/enable_second_nic.ps1 new file mode 100644 index 0000000..764cfe7 --- /dev/null +++ b/packer/oci/scripts/enable_second_nic.ps1 @@ -0,0 +1,47 @@ + +$newNetAdapterName = "Ethernet 2" + +# check for two nics +$netAdapters = Get-NetAdapter +if ($netAdapters.Length -le 1) { + Write-Output "Could not find multiple Network Adapters." + Exit 1 +} + +$secondNic = $netAdapters[1] + +# make sure the network adapter is known +if ($secondNic.Name -ne "") { + Write-Output "Changing ${secondNic.Name} to ${newNetAdapterName} ..." + try + { + Rename-NetAdapter -Name $secondNic.Name -NewName "${newNetAdapterName}" + $secondNic.Name = $newNetAdapterName + } + catch + { + Write-Output "Could not rename net adapter" + Write-Output $_ + } +} else { + Write-Output "Can not change network adapter named: ${secondNic.Name}" +} + +# check that second is disabled +if ($secondNic.Status -ne "up") { + + try + { + Enable-NetAdapter -Name $secondNic.Name + Write-Output "${secondNic.Name} enabled ..." + } + catch + { + Write-Output "Could not enable net adapter" + Write-Output $_ + } +} else { + Write-Output "${secondNic.Name} already enabled ..." +} + +Remove-Item -Path .\enable_second_nic.ps1 \ No newline at end of file diff --git a/packer/oci/scripts/set_bootstrap.sh b/packer/oci/scripts/set_bootstrap.sh new file mode 100755 index 0000000..3814f46 --- /dev/null +++ b/packer/oci/scripts/set_bootstrap.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script overrides the 'opc' password set in the winrm_bootstrap.txt file +# This script is assumed to be run from the make file hence the pathing to the winrm_bootstrap.txt + +set -o errexit +set -o nounset +set -o pipefail + +echo "Changing Password in winrm_bootstrap.txt" + +cp packer/oci/scripts/winrm_bootstrap_template.txt packer/oci/scripts/winrm_bootstrap.txt + +sed "s/(\[adsi\].*/([adsi](\"WinNT:\/\/\"+\$opcUser.caption).replace(\"\\\\\",\"\/\")).SetPassword(\"$OPC_USER_PASSWORD\")/g" packer/oci/scripts/winrm_bootstrap.txt | tee packer/oci/scripts/winrm_bootstrap.txt >/dev/null diff --git a/packer/oci/scripts/sysprep.ps1 b/packer/oci/scripts/sysprep.ps1 new file mode 100644 index 0000000..5f25fd7 --- /dev/null +++ b/packer/oci/scripts/sysprep.ps1 @@ -0,0 +1,37 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if( Test-Path $Env:SystemRoot\system32\Sysprep\unattend.xml ) { + Remove-Item $Env:SystemRoot\system32\Sysprep\unattend.xml -Force +} + +$unattendedXml = "$ENV:ProgramFiles\Cloudbase Solutions\Cloudbase-Init\conf\Unattend.xml" +$FileExists = Test-Path $unattendedXml +If ($FileExists -eq $True) { + # Use the Cloudbase-init provided unattend file during install + Write-Output "Using cloudbase-init unattend file for sysprep: $unattendedXml" + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /quit /quiet /unattend:$unattendedXml +}else { + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /quit /quiet +} + +# Wait for the image to be reset +while($true) { + $imageState = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\State).ImageState + Write-Output $imageState + if ($imageState -eq 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { break } + Start-Sleep -s 5 +} + +Write-Output '>>> Sysprep complete ...' diff --git a/packer/oci/scripts/unset_bootstrap.sh b/packer/oci/scripts/unset_bootstrap.sh new file mode 100755 index 0000000..c2162da --- /dev/null +++ b/packer/oci/scripts/unset_bootstrap.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script overrides the 'opc' password set in the winrm_bootstrap.txt file +# This script is assumed to be run from the make file hence the pathing to the winrm_bootstrap.txt + +set -o errexit +set -o nounset +set -o pipefail + +echo "resetting Password in winrm_bootstrap.txt" + +rm packer/oci/scripts/winrm_bootstrap.txt diff --git a/packer/oci/scripts/winrm_bootstrap_template.txt b/packer/oci/scripts/winrm_bootstrap_template.txt new file mode 100644 index 0000000..31b7fbb --- /dev/null +++ b/packer/oci/scripts/winrm_bootstrap_template.txt @@ -0,0 +1,50 @@ +<powershell> + +# MAKE SURE IN YOUR PACKER CONFIG TO SET: +# +# +# "winrm_username": "Administrator", +# "winrm_insecure": true, +# "winrm_use_ssl": true, +# +# +#ps1_sysnative +cmd /C 'wmic UserAccount where Name="opc" set PasswordExpires=False' +$opcUser = get-wmiobject win32_useraccount | Where-Object { $_.Name -match 'opc' } +([adsi]("WinNT://"+$opcUser.caption).replace("\","/")).SetPassword("myTemp#Pa55_Word") + +write-output "Running User Data Script" +write-host "(host) Running User Data Script" + +Set-ExecutionPolicy Unrestricted -Scope LocalMachine -Force -ErrorAction Ignore + +# Don't set this before Set-ExecutionPolicy as it throws an error +$ErrorActionPreference = "stop" + +# Remove HTTP listener +Remove-Item -Path WSMan:\Localhost\listener\listener* -Recurse + +# Create a self-signed certificate to let ssl work +$Cert = New-SelfSignedCertificate -CertstoreLocation Cert:\LocalMachine\My -DnsName "packer" +New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $Cert.Thumbprint -Force + +# WinRM +write-output "Setting up WinRM" +write-host "(host) setting up WinRM" + +cmd.exe /c winrm quickconfig -q +cmd.exe /c winrm set "winrm/config" '@{MaxTimeoutms="1800000"}' +cmd.exe /c winrm set "winrm/config/winrs" '@{MaxMemoryPerShellMB="1024"}' +cmd.exe /c winrm set "winrm/config/service" '@{AllowUnencrypted="true"}' +cmd.exe /c winrm set "winrm/config/client" '@{AllowUnencrypted="true"}' +cmd.exe /c winrm set "winrm/config/service/auth" '@{Basic="true"}' +cmd.exe /c winrm set "winrm/config/client/auth" '@{Basic="true"}' +cmd.exe /c winrm set "winrm/config/service/auth" '@{CredSSP="true"}' +cmd.exe /c winrm set "winrm/config/listener?Address=*+Transport=HTTPS" "@{Port=`"5986`";Hostname=`"packer`";CertificateThumbprint=`"$($Cert.Thumbprint)`"}" +cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes +cmd.exe /c netsh firewall add portopening TCP 5986 "Port 5986" +cmd.exe /c net stop winrm +cmd.exe /c sc config winrm start= auto +cmd.exe /c net start winrm + +</powershell> \ No newline at end of file diff --git a/packer/oci/ubuntu-1804.json b/packer/oci/ubuntu-1804.json new file mode 100644 index 0000000..254297c --- /dev/null +++ b/packer/oci/ubuntu-1804.json @@ -0,0 +1,7 @@ +{ + "build_name": "ubuntu-1804", + "distribution": "ubuntu", + "operating_system": "Canonical Ubuntu", + "operating_system_version": "18.04", + "ssh_username": "ubuntu" +} diff --git a/packer/oci/ubuntu-2004.json b/packer/oci/ubuntu-2004.json new file mode 100644 index 0000000..82f82d0 --- /dev/null +++ b/packer/oci/ubuntu-2004.json @@ -0,0 +1,7 @@ +{ + "build_name": "ubuntu-2004", + "distribution": "ubuntu", + "operating_system": "Canonical Ubuntu", + "operating_system_version": "20.04", + "ssh_username": "ubuntu" +} diff --git a/packer/oci/ubuntu-2204.json b/packer/oci/ubuntu-2204.json new file mode 100644 index 0000000..b0d45bf --- /dev/null +++ b/packer/oci/ubuntu-2204.json @@ -0,0 +1,7 @@ +{ + "build_name": "ubuntu-2204", + "distribution": "ubuntu", + "operating_system": "Canonical Ubuntu", + "operating_system_version": "22.04", + "ssh_username": "ubuntu" +} diff --git a/packer/oci/windows-2019.json b/packer/oci/windows-2019.json new file mode 100644 index 0000000..a3b8bef --- /dev/null +++ b/packer/oci/windows-2019.json @@ -0,0 +1,7 @@ +{ + "build_name": "windows-2019", + "operating_system": "Windows", + "operating_system_version": "Server 2019 Standard", + "runtime": "containerd", + "ssh_username": "opc" +} diff --git a/packer/oci/windows-2022.json b/packer/oci/windows-2022.json new file mode 100644 index 0000000..c27b30f --- /dev/null +++ b/packer/oci/windows-2022.json @@ -0,0 +1,7 @@ +{ + "build_name": "windows-2022", + "operating_system": "Windows", + "operating_system_version": "Server 2022 Standard", + "runtime": "containerd", + "ssh_username": "opc" +} diff --git a/packer/outscale/ci/nightly/overwrite-1-21.json b/packer/outscale/ci/nightly/overwrite-1-21.json new file mode 100644 index 0000000..4d83489 --- /dev/null +++ b/packer/outscale/ci/nightly/overwrite-1-21.json @@ -0,0 +1,7 @@ +{ + "build_timestamp": "nightly", + "kubernetes_deb_version": "1.21.14-00", + "kubernetes_rpm_version": "1.21.14-0", + "kubernetes_semver": "v1.21.14", + "kubernetes_series": "v1.21" +} diff --git a/packer/outscale/ci/nightly/overwrite-1-22.json b/packer/outscale/ci/nightly/overwrite-1-22.json new file mode 100644 index 0000000..e06e114 --- /dev/null +++ b/packer/outscale/ci/nightly/overwrite-1-22.json @@ -0,0 +1,7 @@ +{ + "build_timestamp": "nightly", + "kubernetes_deb_version": "1.22.11-00", + "kubernetes_rpm_version": "1.22.11-0", + "kubernetes_semver": "v1.22.11", + "kubernetes_series": "v1.22" +} diff --git a/packer/outscale/ci/nightly/overwrite-1-23.json b/packer/outscale/ci/nightly/overwrite-1-23.json new file mode 100644 index 0000000..d723ef2 --- /dev/null +++ b/packer/outscale/ci/nightly/overwrite-1-23.json @@ -0,0 +1,7 @@ +{ + "build_timestamp": "nightly", + "kubernetes_deb_version": "1.23.8-00", + "kubernetes_rpm_version": "1.23.8-0", + "kubernetes_semver": "v1.23.8", + "kubernetes_series": "v1.23" +} diff --git a/packer/outscale/ci/nightly/overwrite-1-24.json b/packer/outscale/ci/nightly/overwrite-1-24.json new file mode 100644 index 0000000..9c89d61 --- /dev/null +++ b/packer/outscale/ci/nightly/overwrite-1-24.json @@ -0,0 +1,7 @@ +{ + "build_timestamp": "nightly", + "kubernetes_deb_version": "1.24.2-00", + "kubernetes_rpm_version": "1.24.2-0", + "kubernetes_semver": "v1.24.2", + "kubernetes_series": "v1.24" +} diff --git a/packer/outscale/ci/nightly/overwrite-1-25.json b/packer/outscale/ci/nightly/overwrite-1-25.json new file mode 100644 index 0000000..9f4b1cd --- /dev/null +++ b/packer/outscale/ci/nightly/overwrite-1-25.json @@ -0,0 +1,7 @@ +{ + "build_timestamp": "nightly", + "kubernetes_deb_version": "1.25.2-00", + "kubernetes_rpm_version": "1.25.2-0", + "kubernetes_semver": "v1.25.2", + "kubernetes_series": "v1.25" +} diff --git a/packer/outscale/packer.json b/packer/outscale/packer.json new file mode 100644 index 0000000..0c70ea8 --- /dev/null +++ b/packer/outscale/packer.json @@ -0,0 +1,124 @@ +{ + "builders": [ + { + "access_key": "{{ user `access_key` }}", + "omi_name": "{{user `build_name`}}-{{user `distribution_version`}}-kubernetes-{{user `kubernetes_semver`}}-{{isotime `2006-01-02`}}", + "region": "{{ user `region` }}", + "secret_key": "{{ user `secret_key` }}", + "source_omi": "{{ user `source_image` }}", + "source_omi_filter": { + "filters": { + "image-name": "{{ user `image_name` }}" + }, + "owners": [ + "Outscale" + ] + }, + "ssh_username": "outscale", + "tags": { + "Base_OMI_Name": "{{ .SourceOMIName }}", + "Extra": "{{ .SourceOMITags.TagName }}", + "OS_Version": "{{user `distribution_release`}}", + "Release": "{{user `distribution_version`}}", + "distribution_release": "{{user `distribution_release`}}", + "distribution_version": "{{user `distribution_version`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}" + }, + "type": "outscale-bsu", + "vm_type": "tinav5.c2r4p2" + } + ], + "provisioners": [ + { + "environment_vars": [ + "BUILD_NAME={{user `build_name`}}" + ], + "inline": [ + "if [ $BUILD_NAME != \"ubuntu-1804\" ]; then exit 0; fi", + "while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done", + "sudo apt-get -qq update && sudo DEBIAN_FRONTEND=noninteractive apt-get -qqy install python python-pip" + ], + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} -o IdentitiesOnly=yes'" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}" + ], + "playbook_file": "./ansible/node.yml", + "type": "ansible" + }, + { + "arch": "{{user `goss_arch`}}", + "download_path": "{{user `goss_download_path`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "remote_folder": "{{user `goss_remote_folder`}}", + "remote_path": "{{user `goss_remote_path`}}", + "skip_install": "{{user `goss_skip_install`}}", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": true, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "ARCH": "amd64", + "OS": "{{user `distribution` | lower}}", + "PROVIDER": "outscale", + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_deb_version": "{{ user `kubernetes_cni_deb_version` }}", + "kubernetes_cni_rpm_version": "{{ split (user `kubernetes_cni_rpm_version`) \"-\" 0 }}", + "kubernetes_cni_source_type": "{{user `kubernetes_cni_source_type`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver` | replace \"v\" \"\" 1}}", + "kubernetes_deb_version": "{{ user `kubernetes_deb_version` }}", + "kubernetes_rpm_version": "{{ split (user `kubernetes_rpm_version`) \"-\" 0 }}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "kubernetes_version": "{{user `kubernetes_semver` | replace \"v\" \"\" 1}}" + }, + "version": "{{user `goss_version`}}" + } + ], + "variables": { + "access_key": "{{env `OSC_ACCESS_KEY`}}", + "ansible_common_vars": "", + "ansible_extra_vars": "", + "build_timestamp": "{{timestamp}}", + "containerd_sha256": null, + "containerd_url": "https://github.com/containerd/containerd/releases/download/v{{user `containerd_version`}}/cri-containerd-cni-{{user `containerd_version`}}-linux-amd64.tar.gz", + "containerd_version": null, + "crictl_url": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-amd64.tar.gz", + "crictl_version": null, + "distribution_release": null, + "distribution_version": null, + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "kubernetes_cni_deb_version": null, + "kubernetes_cni_http_source": null, + "kubernetes_cni_rpm_version": null, + "kubernetes_cni_semver": null, + "kubernetes_cni_source_type": null, + "kubernetes_container_registry": null, + "kubernetes_deb_gpg_key": null, + "kubernetes_deb_repo": null, + "kubernetes_deb_version": null, + "kubernetes_http_source": null, + "kubernetes_load_additional_imgs": null, + "kubernetes_rpm_gpg_check": null, + "kubernetes_rpm_gpg_key": null, + "kubernetes_rpm_repo": null, + "kubernetes_rpm_version": null, + "kubernetes_semver": null, + "kubernetes_series": null, + "kubernetes_source_type": null, + "region": "{{env `OSC_REGION`}}", + "secret_key": "{{env `OSC_SECRET_KEY`}}" + } +} diff --git a/packer/outscale/ubuntu-2004.json b/packer/outscale/ubuntu-2004.json new file mode 100644 index 0000000..10ce900 --- /dev/null +++ b/packer/outscale/ubuntu-2004.json @@ -0,0 +1,7 @@ +{ + "build_name": "ubuntu-2004", + "distribution": "ubuntu", + "distribution_release": "ubuntu", + "distribution_version": "2004", + "image_name": "Ubuntu-20.04-2022.03.10-0" +} diff --git a/packer/ova/OWNERS b/packer/ova/OWNERS new file mode 100644 index 0000000..0457bf8 --- /dev/null +++ b/packer/ova/OWNERS @@ -0,0 +1,4 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - cluster-api-vsphere-maintainers diff --git a/packer/ova/centos-7.json b/packer/ova/centos-7.json new file mode 100644 index 0000000..9caf8b0 --- /dev/null +++ b/packer/ova/centos-7.json @@ -0,0 +1,17 @@ +{ + "boot_command_prefix": "<tab> text ks=hd:fd0:", + "boot_command_suffix": "/7/ks.cfg<enter><wait>", + "boot_media_path": "/HTTP", + "build_name": "centos-7", + "distro_arch": "amd64", + "distro_name": "centos", + "distro_version": "7", + "floppy_dirs": "./packer/ova/linux/{{user `distro_name`}}/http/", + "guest_os_type": "centos7-64", + "iso_checksum": "07b94e6b1a0b0260b94c83d6bb76b26bf7a310dc78d7a9c7432809fb9bc6194a", + "iso_checksum_type": "sha256", + "iso_url": "https://mirrors.edge.kernel.org/centos/7.9.2009/isos/x86_64/CentOS-7-x86_64-Minimal-2009.iso", + "os_display_name": "CentOS 7", + "shutdown_command": "sys-unconfig", + "vsphere_guest_os_type": "centos7_64Guest" +} diff --git a/packer/ova/flatcar.json b/packer/ova/flatcar.json new file mode 100644 index 0000000..60ad617 --- /dev/null +++ b/packer/ova/flatcar.json @@ -0,0 +1,25 @@ +{ + "ansible_extra_vars": "guestinfo_datasource_slug={{user `guestinfo_datasource_slug`}} guestinfo_datasource_ref={{user `guestinfo_datasource_ref`}} guestinfo_datasource_script={{user `guestinfo_datasource_script`}} ansible_python_interpreter=/opt/bin/python", + "boot_command_prefix": "sudo systemctl mask sshd.socket --now<enter>curl -sLo /tmp/ignition.json https://raw.githubusercontent.com/kubernetes-sigs/image-builder/0bb5cd6db390516c75daeeaf27f19b1aa958428b/images/capi/packer/files/flatcar/ignition/bootstrap.json<enter>sed -i \"s|BUILDERPASSWORDHASH|$(mkpasswd -5 {{user `ssh_password`}})|\" /tmp/ignition.json<enter>sudo flatcar-install -d /dev/sda -o vmware_raw -C {{user `channel_name`}} -V {{user `release_version`}} -i /tmp/ignition.json<enter>sudo reboot<enter>", + "boot_wait": "60s", + "build_name": "flatcar-{{env `FLATCAR_CHANNEL`}}-{{env `FLATCAR_VERSION`}}", + "channel_name": "{{env `FLATCAR_CHANNEL`}}", + "containerd_cri_socket": "/run/docker/libcontainerd/docker-containerd.sock", + "crictl_source_type": "http", + "distro_name": "flatcar", + "guest_os_type": "flatcar-64", + "http_directory": "", + "iso_checksum": "https://{{env `FLATCAR_CHANNEL`}}.release.flatcar-linux.net/amd64-usr/{{env `FLATCAR_VERSION`}}/flatcar_production_iso_image.iso.DIGESTS.asc", + "iso_checksum_type": "file", + "iso_url": "https://{{env `FLATCAR_CHANNEL`}}.release.flatcar-linux.net/amd64-usr/{{env `FLATCAR_VERSION`}}/flatcar_production_iso_image.iso", + "kubernetes_cni_source_type": "http", + "kubernetes_source_type": "http", + "os_display_name": "Flatcar Container Linux ({{env `FLATCAR_CHANNEL`}} channel release {{env `FLATCAR_VERSION`}})", + "python_path": "/opt/bin/builder-env/site-packages", + "release_version": "{{env `FLATCAR_VERSION`}}", + "shutdown_command": "shutdown -P now", + "systemd_prefix": "/etc/systemd", + "sysusr_prefix": "/opt", + "sysusrlocal_prefix": "/opt", + "vsphere_guest_os_type": "other3xLinux64Guest" +} diff --git a/packer/ova/linux/centos/http/7/ks.cfg b/packer/ova/linux/centos/http/7/ks.cfg new file mode 100644 index 0000000..3053362 --- /dev/null +++ b/packer/ova/linux/centos/http/7/ks.cfg @@ -0,0 +1,95 @@ +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Perform a fresh install, not an upgrade +install +cdrom + +# Perform a text installation +text + +# Do not install an X server +skipx + +# Configure the locale/keyboard +lang en_US.UTF-8 +keyboard us + +# Configure networking +network --onboot yes --bootproto dhcp --hostname capv.vm +firewall --disabled +selinux --permissive +timezone UTC + +# Don't flip out if unsupported hardware is detected +unsupported_hardware + +# Configure the user(s) +auth --enableshadow --passalgo=sha512 --kickstart +user --name=builder --plaintext --password builder --groups=builder,wheel + +# Disable general install minutia +firstboot --disabled +eula --agreed + +# Create a single partition with no swap space +bootloader --location=mbr +zerombr +clearpart --all --initlabel +part / --grow --asprimary --fstype=ext4 --label=slash + +%packages --ignoremissing --excludedocs +openssh-server +sed +sudo + +# Remove unnecessary firmware +-*-firmware + +# Remove other unnecessary packages +-postfix +%end + +# Enable/disable the following services +services --enabled=sshd + +# Perform a reboot once the installation has completed +reboot + +# The %post section is essentially a shell script +%post --erroronfail + +# Update the root certificates +update-ca-trust force-enable + +# Ensure that the "builder" user doesn't require a password to use sudo, +# or else Ansible will fail +echo 'builder ALL=(ALL) NOPASSWD: ALL' >/etc/sudoers.d/builder +chmod 440 /etc/sudoers.d/builder + +# Install open-vm-tools +yum install -y open-vm-tools + +# Remove the package cache +yum -y clean all + +# Disable swap +swapoff -a +rm -f /swapfile +sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab + +# Ensure on next boot that network devices get assigned unique IDs. +sed -i '/^\(HWADDR\|UUID\)=/d' /etc/sysconfig/network-scripts/ifcfg-* + +%end diff --git a/packer/ova/linux/centos/http/8/ks.cfg b/packer/ova/linux/centos/http/8/ks.cfg new file mode 100644 index 0000000..59700d7 --- /dev/null +++ b/packer/ova/linux/centos/http/8/ks.cfg @@ -0,0 +1,75 @@ +# version=RHEL8 +# Install OS instead of upgrade +install +cdrom +auth --enableshadow --passalgo=sha512 --kickstart +# License agreement +eula --agreed +# Use text mode install +text +# Disable Initial Setup on first boot +firstboot --disable +# Keyboard layout +keyboard --vckeymap=us --xlayouts='us' +# System language +lang en_US.UTF-8 +# Network information +network --bootproto=dhcp --device=link --activate +network --hostname=rhel8 +firewall --disabled +# Root password +rootpw builder --plaintext +# SELinux configuration +selinux --permissive +# Do not configure the X Window System +skipx +# System timezone +timezone UTC +# Add a user named builder +user --groups=wheel --name=builder --password=builder --plaintext --gecos="builder" + +# System bootloader configuration +bootloader --location=mbr --boot-drive=sda +# Clear the Master Boot Record +zerombr +clearpart --all --initlabel --drives=sda +part / --fstype="ext4" --grow --asprimary --label=slash --ondisk=sda + +# Reboot after successful installation +reboot + +%packages --ignoremissing --excludedocs +# dnf group info minimal-environment +@^minimal-environment +@core +openssh-server +sed +sudo +python3 +open-vm-tools + +# Exclude unnecessary firmwares +-iwl*firmware +%end + +# Enable/disable the following services +services --enabled=sshd + +%post --nochroot --logfile=/mnt/sysimage/root/ks-post.log +# Disable quiet boot and splash screen +sed --follow-symlinks -i "s/ rhgb quiet//" /mnt/sysimage/etc/default/grub +sed --follow-symlinks -i "s/ rhgb quiet//" /mnt/sysimage/boot/grub2/grubenv + +# Passwordless sudo for the user 'builder' +echo "builder ALL=(ALL) NOPASSWD: ALL" >> /mnt/sysimage/etc/sudoers.d/builder +# Remove the package cache +yum -y clean all + +# Disable swap +swapoff -a +rm -f /swapfile +sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab + +sed -i '/^\(HWADDR\|UUID\)=/d' /etc/sysconfig/network-scripts/ifcfg-* + +%end \ No newline at end of file diff --git a/packer/ova/linux/photon/http/3/ks.json b/packer/ova/linux/photon/http/3/ks.json new file mode 100644 index 0000000..4b03b10 --- /dev/null +++ b/packer/ova/linux/photon/http/3/ks.json @@ -0,0 +1,60 @@ +{ + "disk": "/dev/sda", + "hostname": "localhost", + "packages": [ + "bash", + "bc", + "bridge-utils", + "bzip2", + "ca-certificates", + "cloud-init", + "cpio", + "cracklib-dicts", + "dbus", + "e2fsprogs", + "file", + "filesystem", + "findutils", + "gdbm", + "grep", + "gzip", + "iana-etc", + "initramfs", + "iptables", + "iproute2", + "iputils", + "libtool", + "linux", + "motd", + "net-tools", + "openssh-server", + "open-vm-tools", + "pkg-config", + "photon-release", + "photon-repos", + "procps-ng", + "rpm", + "sed", + "sudo", + "tdnf", + "tzdata", + "util-linux", + "vim", + "which" + ], + "password": { + "age": -1, + "crypted": true, + "text": "*" + }, + "postinstall": [ + "#!/bin/sh", + "useradd -U -d /home/builder -m --groups wheel builder && echo 'builder:builder' | chpasswd", + "echo 'builder ALL=(ALL) NOPASSWD: ALL' >/etc/sudoers.d/builder", + "chmod 440 /etc/sudoers.d/builder", + "systemctl enable sshd", + "tdnf clean all", + "swapoff -a", + "rm -f /swapfile" + ] +} diff --git a/packer/ova/linux/photon/http/4 b/packer/ova/linux/photon/http/4 new file mode 120000 index 0000000..e440e5c --- /dev/null +++ b/packer/ova/linux/photon/http/4 @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/packer/ova/linux/rhel b/packer/ova/linux/rhel new file mode 120000 index 0000000..95e1895 --- /dev/null +++ b/packer/ova/linux/rhel @@ -0,0 +1 @@ +centos \ No newline at end of file diff --git a/packer/ova/linux/rockylinux/http/8/ks.cfg b/packer/ova/linux/rockylinux/http/8/ks.cfg new file mode 100644 index 0000000..55b5d99 --- /dev/null +++ b/packer/ova/linux/rockylinux/http/8/ks.cfg @@ -0,0 +1,96 @@ +# Use CDROM installation media +repo --name="AppStream" --baseurl="http://download.rockylinux.org/pub/rocky/8/AppStream/x86_64/os/" +cdrom + +# Use text install +text + +# Don't run the Setup Agent on first boot +firstboot --disabled +eula --agreed + +# Keyboard layouts +keyboard --vckeymap=us --xlayouts='us' + +# System language +lang en_US.UTF-8 + +# Network information +network --bootproto=dhcp --onboot=on --ipv6=auto --activate --hostname=capv.vm + +# Lock Root account +rootpw --lock + +# Create builder user +user --name=builder --groups=wheel --password=builder --plaintext --shell=/bin/bash + +# System services +selinux --permissive +firewall --disabled +services --enabled="NetworkManager,sshd,chronyd" + +# System timezone +timezone UTC + +# System booloader configuration +bootloader --location=mbr --boot-drive=sda +zerombr +clearpart --all --initlabel --drives=sda +part / --fstype="ext4" --grow --asprimary --label=slash --ondisk=sda + +skipx + +%packages --ignoremissing --excludedocs +openssh-server +open-vm-tools +sudo +sed +python3 + +# unnecessary firmware +-aic94xx-firmware +-atmel-firmware +-b43-openfwwf +-bfa-firmware +-ipw2100-firmware +-ipw2200-firmware +-ivtv-firmware +-iwl*-firmware +-libertas-usb8388-firmware +-ql*-firmware +-rt61pci-firmware +-rt73usb-firmware +-xorg-x11-drv-ati-firmware +-zd1211-firmware +-cockpit +-quota +-alsa-* +-fprintd-pam +-intltool +-microcode_ctl +%end + +%addon com_redhat_kdump --disable +%end + +reboot + +%post + +echo 'builder ALL=(ALL) NOPASSWD: ALL' >/etc/sudoers.d/builder +chmod 440 /etc/sudoers.d/builder + +# Remove the package cache +yum -y clean all + +swapoff -a +rm -f /swapfile +sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab + +systemctl enable vmtoolsd +systemctl start vmtoolsd + +# Ensure on next boot that network devices get assigned unique IDs. +sed -i '/^\(HWADDR\|UUID\)=/d' /etc/sysconfig/network-scripts/ifcfg-* + +%end diff --git a/packer/ova/linux/ubuntu/http/18.04/preseed.cfg b/packer/ova/linux/ubuntu/http/18.04/preseed.cfg new file mode 100644 index 0000000..37918c4 --- /dev/null +++ b/packer/ova/linux/ubuntu/http/18.04/preseed.cfg @@ -0,0 +1,15 @@ +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +d-i preseed/include string ../base/preseed.cfg diff --git a/packer/ova/linux/ubuntu/http/20.04/preseed-efi.cfg b/packer/ova/linux/ubuntu/http/20.04/preseed-efi.cfg new file mode 100644 index 0000000..e6eed39 --- /dev/null +++ b/packer/ova/linux/ubuntu/http/20.04/preseed-efi.cfg @@ -0,0 +1,15 @@ +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +d-i preseed/include string ../base/preseed-efi.cfg diff --git a/packer/ova/linux/ubuntu/http/20.04/preseed.cfg b/packer/ova/linux/ubuntu/http/20.04/preseed.cfg new file mode 100644 index 0000000..37918c4 --- /dev/null +++ b/packer/ova/linux/ubuntu/http/20.04/preseed.cfg @@ -0,0 +1,15 @@ +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +d-i preseed/include string ../base/preseed.cfg diff --git a/packer/ova/linux/ubuntu/http/22.04/meta-data b/packer/ova/linux/ubuntu/http/22.04/meta-data new file mode 100644 index 0000000..e69de29 diff --git a/packer/ova/linux/ubuntu/http/22.04/user-data b/packer/ova/linux/ubuntu/http/22.04/user-data new file mode 100644 index 0000000..3e57f7c --- /dev/null +++ b/packer/ova/linux/ubuntu/http/22.04/user-data @@ -0,0 +1,87 @@ +#cloud-config +# Copyright 2022 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# For more information on how autoinstall is configured, please refer to +# https://ubuntu.com/server/docs/install/autoinstall-reference +autoinstall: + version: 1 + # Disable ssh server during installation, otherwise packer tries to connect and exceed max attempts + early-commands: + - systemctl stop ssh + # Configure the locale + locale: en_US.UTF-8 + keyboard: + layout: us + # Create a single-partition with no swap space. Kubernetes + # really dislikes the idea of anyone else managing memory. + # For more information on how partitioning is configured, + # please refer to https://curtin.readthedocs.io/en/latest/topics/storage.html. + storage: + config: + - type: disk + id: disk-0 + size: largest + grub_device: true + preserve: false + ptable: msdos + wipe: superblock + - type: partition + id: partition-0 + device: disk-0 + size: -1 + number: 1 + preserve: false + flag: boot + - type: format + id: format-0 + volume: partition-0 + fstype: ext4 + preserve: false + - type: mount + id: mount-0 + device: format-0 + path: / + updates: 'all' + ssh: + install-server: true + allow-pw: true + # Customize the list of packages installed. + packages: + - open-vm-tools + # Create the default user. + # Ensures the "builder" user doesn't require a password to use sudo. + user-data: + users: + - name: builder + # openssl passwd -6 -stdin <<< builder + passwd: $6$xyz$UtXVazU08Q5b8AW.TJ3MPYZglyXa3Ttf2RCel8MCUPlEYO1evWxeWBhZ2QqivU/Ij4tqYAxMCqc2ujEM4dMSe1 + groups: [adm, cdrom, dip, plugdev, lxd, sudo] + lock-passwd: false + sudo: ALL=(ALL) NOPASSWD:ALL + shell: /bin/bash + + # This command runs after all other steps; it: + # 1. Disables swapfiles + # 2. Removes the existing swapfile + # 3. Removes the swapfile entry from /etc/fstab + # 4. Cleans up any packages that are no longer required + # 5. Removes the cached list of packages + late-commands: + - swapoff -a + - rm -f /swapfile + - sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab + - apt-get purge --auto-remove -y + - rm -rf /var/lib/apt/lists/* diff --git a/packer/ova/linux/ubuntu/http/base/preseed-efi.cfg b/packer/ova/linux/ubuntu/http/base/preseed-efi.cfg new file mode 100644 index 0000000..6841226 --- /dev/null +++ b/packer/ova/linux/ubuntu/http/base/preseed-efi.cfg @@ -0,0 +1,128 @@ +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Configure the locale +d-i debian-installer/locale string en_US.utf8 +d-i console-setup/ask_detect boolean false +d-i console-setup/layout string us + +# Configure the clock +d-i time/zone string UTC +d-i clock-setup/utc-auto boolean true +d-i clock-setup/utc boolean true + +# Configure the keyboard +d-i kbd-chooser/method select American English + +# Configure networking +d-i netcfg/wireless_wep string + +# Select the kernel +d-i base-installer/kernel/override-image string linux-virtual + +# Configure a non-interactive install +debconf debconf/frontend select Noninteractive + +# Configure the base installation +d-i pkgsel/install-language-support boolean false +d-i pkgsel/language-packs multiselect +tasksel tasksel/first multiselect # standard, ubuntu-server + + +### Simple GPT configuration w/o LVM +d-i partman-auto/disk string /dev/sda + +d-i partman/alignment string cylinder +d-i partman/confirm_write_new_label boolean true +d-i partman-basicfilesystems/choose_label string gpt +d-i partman-basicfilesystems/default_label string gpt +d-i partman-partitioning/choose_label string gpt +d-i partman-partitioning/default_label string gpt +d-i partman/choose_label string gpt +d-i partman/default_label string gpt + +d-i partman-auto/method string regular +d-i partman-auto/choose_recipe select gpt-boot-root-swap +d-i partman-auto/expert_recipe string \ + gpt-boot-root-swap :: \ + 1 1 1 free \ + $bios_boot{ } \ + method{ biosgrub } . \ + 200 200 200 fat32 \ + $primary{ } \ + method{ efi } format{ } . \ + 512 512 512 ext3 \ + $primary{ } $bootable{ } \ + method{ format } format{ } \ + use_filesystem{ } filesystem{ ext3 } \ + mountpoint{ /boot } . \ + 1000 20000 -1 ext4 \ + $primary{ } \ + method{ format } format{ } \ + use_filesystem{ } filesystem{ ext4 } \ + mountpoint{ / } . + +d-i partman-partitioning/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true + +# Create the default user. +d-i passwd/user-fullname string builder +d-i passwd/username string builder +d-i passwd/user-password password builder +d-i passwd/user-password-again password builder +d-i user-setup/encrypt-home boolean false +d-i user-setup/allow-password-weak boolean true + +# Disable upgrading packages upon installation. +d-i pkgsel/upgrade select none +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i finish-install/reboot_in_progress note +d-i pkgsel/update-policy select none + +# Disable use of the apt mirror during base install +# This means only packages available in the ISO can be installed +d-i apt-setup/use_mirror boolean false + +# Disable the security repo as well (it's on by default) +d-i apt-setup/services-select multiselect none + +# Customize the list of packages installed. +d-i pkgsel/include string openssh-server open-vm-tools + + +# Ensure questions about these packages do not bother the installer. +libssl1.1 libssl1.1/restart-without-asking boolean true +libssl1.1:amd64 libssl1.1/restart-without-asking boolean true +libssl1.1 libssl1.1/restart-services string +libssl1.1:amd64 libssl1.1/restart-services string + + +# This command runs after all other steps; it: +# 1. Ensures the "builder" user doesn't require a password to use sudo +# 2. Cleans up any packages that are no longer required +# 3. Cleans the package cache +# 4. Removes the cached list of packages +# 5. Disables swapfiles +# 6. Removes the existing swapfile +# 7. Removes the swapfile entry from /etc/fstab +d-i preseed/late_command string \ + echo 'builder ALL=(ALL) NOPASSWD: ALL' >/target/etc/sudoers.d/builder ; \ + in-target chmod 440 /etc/sudoers.d/builder ; \ + in-target swapoff -a ; \ + in-target rm -f /swapfile ; \ + in-target sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab ; \ + in-target rm -f /etc/udev/rules.d/70-persistent-net.rules diff --git a/packer/ova/linux/ubuntu/http/base/preseed.cfg b/packer/ova/linux/ubuntu/http/base/preseed.cfg new file mode 100644 index 0000000..f6e5d42 --- /dev/null +++ b/packer/ova/linux/ubuntu/http/base/preseed.cfg @@ -0,0 +1,128 @@ +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Configure the locale +d-i debian-installer/locale string en_US.utf8 +d-i debian-installer/add-kernel-opts console=ttyS0 +d-i console-setup/ask_detect boolean false +d-i console-setup/layout string us + +# Configure the clock +d-i time/zone string UTC +d-i clock-setup/utc-auto boolean true +d-i clock-setup/utc boolean true + +# Configure the keyboard +d-i kbd-chooser/method select American English + +# Configure networking +d-i netcfg/wireless_wep string + +# Select the kernel +d-i base-installer/kernel/override-image string linux-virtual + +# Configure a non-interactive install +debconf debconf/frontend select Noninteractive + +# Configure the base installation +d-i pkgsel/install-language-support boolean false +d-i pkgsel/language-packs multiselect +tasksel tasksel/first multiselect # standard, ubuntu-server + +# Create a single-partition with no swap space. For more information +# on how partitioning is configured, please refer to +# https://github.com/xobs/debian-installer/blob/master/doc/devel/partman-auto-recipe.txt. +d-i partman-auto/method string regular +d-i partman-lvm/device_remove_lvm boolean true +d-i partman-md/device_remove_md boolean true +d-i partman-lvm/confirm boolean true +d-i partman-auto-lvm/guided_size string max + +# Again, this creates a single-partition with no swap. Kubernetes +# really dislikes the idea of anyone else managing memory. +d-i partman-auto/expert_recipe string \ + slash :: \ + 0 0 -1 ext4 \ + $primary{ } $bootable{ } \ + method{ format } format{ } \ + use_filesystem{ } filesystem{ ext4 } \ + mountpoint{ / } \ + . + +d-i partman-partitioning/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true +d-i partman-basicfilesystems/no_swap boolean false +d-i partman-md/confirm boolean true +d-i partman-partitioning/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true +d-i partman-md/confirm_nooverwrite boolean true +d-i partman-lvm/confirm_nooverwrite boolean true +d-i partman-partitioning/no_bootable_gpt_biosgrub boolean true +d-i partman-partitioning/no_bootable_gpt_efi boolean false +d-i partman-efi/non_efi_system boolean false + +# Create the default user. +d-i passwd/user-fullname string builder +d-i passwd/username string builder +d-i passwd/user-password password builder +d-i passwd/user-password-again password builder +d-i user-setup/encrypt-home boolean false +d-i user-setup/allow-password-weak boolean true + +# Disable upgrading packages upon installation. +d-i pkgsel/upgrade select none +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i finish-install/reboot_in_progress note +d-i pkgsel/update-policy select none + +# Disable use of the apt mirror during base install +# This means only packages available in the ISO can be installed +d-i apt-setup/use_mirror boolean false + +# Disable the security repo as well (it's on by default) +d-i apt-setup/services-select multiselect none + +# Customize the list of packages installed. +d-i pkgsel/include string openssh-server open-vm-tools + + +# Ensure questions about these packages do not bother the installer. +libssl1.1 libssl1.1/restart-without-asking boolean true +libssl1.1:amd64 libssl1.1/restart-without-asking boolean true +libssl1.1 libssl1.1/restart-services string +libssl1.1:amd64 libssl1.1/restart-services string + + +# This command runs after all other steps; it: +# 1. Ensures the "builder" user doesn't require a password to use sudo +# 2. Cleans up any packages that are no longer required +# 3. Cleans the package cache +# 4. Removes the cached list of packages +# 5. Disables swapfiles +# 6. Removes the existing swapfile +# 7. Removes the swapfile entry from /etc/fstab +d-i preseed/late_command string \ + echo 'builder ALL=(ALL) NOPASSWD: ALL' >/target/etc/sudoers.d/builder ; \ + in-target chmod 440 /etc/sudoers.d/builder ; \ + in-target swapoff -a ; \ + in-target rm -f /swapfile ; \ + in-target sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab ; \ + in-target rm -f /etc/udev/rules.d/70-persistent-net.rules ; \ + in-target apt-get purge --auto-remove -y ; \ + in-target rm -rf /var/lib/apt/lists/* diff --git a/packer/ova/packer-common.json b/packer/ova/packer-common.json new file mode 100644 index 0000000..9505b42 --- /dev/null +++ b/packer/ova/packer-common.json @@ -0,0 +1,33 @@ +{ + "boot_wait": "10s", + "convert_to_template": "false", + "cpu": "4", + "cpu_cores": "1", + "disk_controller_type": "pvscsi", + "disk_thin_provisioned": "true", + "disk_type_id": "0", + "firmware": "bios", + "format": "", + "guestinfo_datasource_ref": "v1.4.1", + "guestinfo_datasource_script": "{{user `guestinfo_datasource_slug`}}/{{user `guestinfo_datasource_ref`}}/install.sh", + "guestinfo_datasource_slug": "https://raw.githubusercontent.com/vmware/cloud-init-vmware-guestinfo", + "headless": "true", + "insecure_connection": "false", + "memory": "8192", + "network": "", + "network_card": "vmxnet3", + "remote_datastore": "", + "remote_host": "", + "remote_password": "", + "remote_type": "", + "remote_username": "", + "skip_compaction": "false", + "ssh_password": "builder", + "ssh_timeout": "60m", + "ssh_username": "builder", + "vmx_version": "15", + "vnc_bind_address": "127.0.0.1", + "vnc_disable_password": "false", + "vnc_port_max": "6000", + "vnc_port_min": "5900" +} diff --git a/packer/ova/packer-node.json b/packer/ova/packer-node.json new file mode 100644 index 0000000..36363ac --- /dev/null +++ b/packer/ova/packer-node.json @@ -0,0 +1,485 @@ +{ + "builders": [ + { + "communicator": "ssh", + "disable_vnc": "True", + "format": "{{user `format`}}", + "headless": "{{user `headless`}}", + "http_directory": "{{user `http_directory`}}", + "http_port_max": "{{user `http_port_max`}}", + "http_port_min": "{{user `http_port_min`}}", + "name": "vmware-vmx", + "output_directory": "{{user `output_dir`}}", + "remote_datastore": "{{user `remote_datastore`}}", + "remote_host": "{{user `remote_host`}}", + "remote_password": "{{user `remote_password`}}", + "remote_type": "{{user `remote_type`}}", + "remote_username": "{{user `remote_username`}}", + "shutdown_command": "echo '{{user `ssh_password`}}' | sudo -S -E sh -c 'usermod -L {{user `ssh_username`}} && {{user `shutdown_command`}}'", + "skip_compaction": "{{user `skip_compaction`}}", + "source_path": "{{ user `source_path`}}", + "ssh_password": "{{user `ssh_password`}}", + "ssh_timeout": "4h", + "ssh_username": "{{user `ssh_username`}}", + "type": "vmware-vmx", + "vm_name": "{{user `build_version`}}", + "vmx_data": { + "ethernet0.networkName": "{{user `network`}}" + }, + "vnc_bind_address": "{{user `vnc_bind_address`}}", + "vnc_disable_password": "{{user `vnc_disable_password`}}", + "vnc_port_max": "{{user `vnc_port_max`}}", + "vnc_port_min": "{{user `vnc_port_min`}}" + }, + { + "boot_command": [ + "{{user `boot_command_prefix`}}", + "{{user `boot_media_path`}}", + "{{user `boot_command_suffix`}}" + ], + "boot_wait": "{{user `boot_wait`}}", + "communicator": "ssh", + "cores": "{{user `cpu_cores`}}", + "cpus": "{{user `cpu`}}", + "disk_adapter_type": "scsi", + "disk_size": "{{user `disk_size`}}", + "disk_type_id": "{{user `disk_type_id`}}", + "floppy_dirs": "{{ user `floppy_dirs`}}", + "format": "{{user `format`}}", + "guest_os_type": "{{user `guest_os_type`}}", + "headless": "{{user `headless`}}", + "http_directory": "{{ user `http_directory`}}", + "http_port_max": "{{user `http_port_max`}}", + "http_port_min": "{{user `http_port_min`}}", + "iso_checksum": "{{user `iso_checksum_type`}}:{{user `iso_checksum`}}", + "iso_url": "{{user `iso_url`}}", + "memory": "{{user `memory`}}", + "name": "vmware-iso-base", + "output_directory": "{{user `base_output_dir`}}", + "remote_datastore": "{{user `remote_datastore`}}", + "remote_host": "{{user `remote_host`}}", + "remote_password": "{{user `remote_password`}}", + "remote_type": "{{user `remote_type`}}", + "remote_username": "{{user `remote_username`}}", + "shutdown_command": "echo '{{user `ssh_password`}}' | sudo -S -E sh -c '{{user `shutdown_command`}}'", + "skip_compaction": "{{user `skip_compaction`}}", + "ssh_password": "{{user `ssh_password`}}", + "ssh_timeout": "4h", + "ssh_username": "{{user `ssh_username`}}", + "type": "vmware-iso", + "version": "{{user `vmx_version`}}", + "vm_name": "{{user `base_build_version`}}", + "vmdk_name": "{{user `base_build_version`}}", + "vmx_data": { + "ethernet0.networkName": "{{user `network`}}" + }, + "vnc_bind_address": "{{user `vnc_bind_address`}}", + "vnc_disable_password": "{{user `vnc_disable_password`}}", + "vnc_port_max": "{{user `vnc_port_max`}}", + "vnc_port_min": "{{user `vnc_port_min`}}" + }, + { + "boot_command": [ + "{{user `boot_command_prefix`}}", + "{{user `boot_media_path`}}", + "{{user `boot_command_suffix`}}" + ], + "boot_wait": "{{user `boot_wait`}}", + "communicator": "ssh", + "cores": "{{user `cpu_cores`}}", + "cpus": "{{user `cpu`}}", + "disk_adapter_type": "scsi", + "disk_size": "{{user `disk_size`}}", + "disk_type_id": "{{user `disk_type_id`}}", + "floppy_dirs": "{{ user `floppy_dirs`}}", + "format": "{{user `format`}}", + "guest_os_type": "{{user `guest_os_type`}}", + "headless": "{{user `headless`}}", + "http_directory": "{{ user `http_directory`}}", + "http_port_max": "{{user `http_port_max`}}", + "http_port_min": "{{user `http_port_min`}}", + "iso_checksum": "{{user `iso_checksum_type`}}:{{user `iso_checksum`}}", + "iso_url": "{{user `iso_url`}}", + "memory": "{{user `memory`}}", + "name": "vmware-iso", + "output_directory": "{{user `output_dir`}}", + "remote_datastore": "{{user `remote_datastore`}}", + "remote_host": "{{user `remote_host`}}", + "remote_password": "{{user `remote_password`}}", + "remote_type": "{{user `remote_type`}}", + "remote_username": "{{user `remote_username`}}", + "shutdown_command": "echo '{{user `ssh_password`}}' | sudo -S -E sh -c 'usermod -L {{user `ssh_username`}} && {{user `shutdown_command`}}'", + "skip_compaction": "{{user `skip_compaction`}}", + "ssh_password": "{{user `ssh_password`}}", + "ssh_timeout": "4h", + "ssh_username": "{{user `ssh_username`}}", + "type": "vmware-iso", + "version": "{{user `vmx_version`}}", + "vm_name": "{{user `build_version`}}", + "vmdk_name": "{{user `build_version`}}", + "vmx_data": { + "ethernet0.networkName": "{{user `network`}}" + }, + "vnc_bind_address": "{{user `vnc_bind_address`}}", + "vnc_disable_password": "{{user `vnc_disable_password`}}", + "vnc_port_max": "{{user `vnc_port_max`}}", + "vnc_port_min": "{{user `vnc_port_min`}}" + }, + { + "CPUs": "{{user `cpu`}}", + "RAM": "{{user `memory`}}", + "boot_command": [ + "{{user `boot_command_prefix`}}", + "{{user `boot_media_path`}}", + "{{user `boot_command_suffix`}}" + ], + "boot_wait": "{{user `boot_wait`}}", + "cluster": "{{user `cluster`}}", + "communicator": "ssh", + "convert_to_template": "{{user `convert_to_template`}}", + "cpu_cores": "{{user `cpu_cores`}}", + "create_snapshot": "{{user `create_snapshot`}}", + "datacenter": "{{user `datacenter`}}", + "datastore": "{{user `datastore`}}", + "disk_controller_type": "{{user `disk_controller_type`}}", + "firmware": "{{user `firmware`}}", + "floppy_dirs": "{{ user `floppy_dirs`}}", + "folder": "{{user `folder`}}", + "guest_os_type": "{{user `vsphere_guest_os_type`}}", + "host": "{{user `host`}}", + "http_directory": "{{ user `http_directory`}}", + "http_port_max": "{{user `http_port_max`}}", + "http_port_min": "{{user `http_port_min`}}", + "insecure_connection": "{{user `insecure_connection`}}", + "iso_checksum": "{{user `iso_checksum_type`}}:{{user `iso_checksum`}}", + "iso_urls": "{{user `iso_url`}}", + "name": "vsphere-iso-base", + "network_adapters": [ + { + "network": "{{user `network`}}", + "network_card": "{{user `network_card`}}" + } + ], + "password": "{{user `password`}}", + "shutdown_command": "echo '{{user `ssh_password`}}' | sudo -S -E sh -c '{{user `shutdown_command`}}'", + "ssh_clear_authorized_keys": "false", + "ssh_password": "{{user `ssh_password`}}", + "ssh_timeout": "4h", + "ssh_username": "{{user `ssh_username`}}", + "storage": [ + { + "disk_size": "{{user `disk_size`}}", + "disk_thin_provisioned": "{{user `disk_thin_provisioned`}}" + } + ], + "type": "vsphere-iso", + "username": "{{user `username`}}", + "vcenter_server": "{{user `vcenter_server`}}", + "vm_name": "{{user `base_build_version`}}", + "vm_version": "{{user `vmx_version`}}" + }, + { + "CPUs": "{{user `cpu`}}", + "RAM": "{{user `memory`}}", + "boot_command": [ + "{{user `boot_command_prefix`}}", + "{{user `boot_media_path`}}", + "{{user `boot_command_suffix`}}" + ], + "boot_wait": "{{user `boot_wait`}}", + "cluster": "{{user `cluster`}}", + "communicator": "ssh", + "convert_to_template": "{{user `convert_to_template`}}", + "cpu_cores": "{{user `cpu_cores`}}", + "datacenter": "{{user `datacenter`}}", + "datastore": "{{user `datastore`}}", + "disk_controller_type": "{{user `disk_controller_type`}}", + "export": { + "force": true, + "manifest": "{{ user `export_manifest`}}", + "output_directory": "{{user `output_dir`}}" + }, + "firmware": "{{user `firmware`}}", + "floppy_dirs": "{{ user `floppy_dirs`}}", + "folder": "{{user `folder`}}", + "guest_os_type": "{{user `vsphere_guest_os_type`}}", + "host": "{{user `host`}}", + "http_directory": "{{ user `http_directory`}}", + "http_port_max": "{{user `http_port_max`}}", + "http_port_min": "{{user `http_port_min`}}", + "insecure_connection": "{{user `insecure_connection`}}", + "iso_checksum": "{{user `iso_checksum_type`}}:{{user `iso_checksum`}}", + "iso_urls": "{{user `iso_url`}}", + "name": "vsphere", + "network_adapters": [ + { + "network": "{{user `network`}}", + "network_card": "{{user `network_card`}}" + } + ], + "password": "{{user `password`}}", + "shutdown_command": "echo '{{user `ssh_password`}}' | sudo -S -E sh -c 'usermod -L {{user `ssh_username`}} && {{user `shutdown_command`}}'", + "ssh_password": "{{user `ssh_password`}}", + "ssh_timeout": "4h", + "ssh_username": "{{user `ssh_username`}}", + "storage": [ + { + "disk_size": "{{user `disk_size`}}", + "disk_thin_provisioned": "{{user `disk_thin_provisioned`}}" + } + ], + "type": "vsphere-iso", + "username": "{{user `username`}}", + "vcenter_server": "{{user `vcenter_server`}}", + "vm_name": "{{user `build_version`}}", + "vm_version": "{{user `vmx_version`}}" + }, + { + "CPUs": "{{user `cpu`}}", + "RAM": "{{user `memory`}}", + "cluster": "{{user `cluster`}}", + "communicator": "ssh", + "convert_to_template": "{{user `convert_to_template`}}", + "cpu_cores": "{{user `cpu_cores`}}", + "create_snapshot": "{{user `create_snapshot`}}", + "datacenter": "{{user `datacenter`}}", + "datastore": "{{user `datastore`}}", + "export": { + "force": true, + "manifest": "{{ user `export_manifest`}}", + "output_directory": "{{user `output_dir`}}" + }, + "folder": "{{user `folder`}}", + "host": "{{user `host`}}", + "insecure_connection": "{{user `insecure_connection`}}", + "linked_clone": "{{user `linked_clone`}}", + "name": "vsphere-clone", + "network": "{{user `network`}}", + "password": "{{user `password`}}", + "shutdown_command": "echo '{{user `ssh_password`}}' | sudo -S -E sh -c 'usermod -L {{user `ssh_username`}} && {{user `shutdown_command`}}'", + "ssh_password": "{{user `ssh_password`}}", + "ssh_timeout": "4h", + "ssh_username": "{{user `ssh_username`}}", + "template": "{{user `template`}}", + "type": "vsphere-clone", + "username": "{{user `username`}}", + "vcenter_server": "{{user `vcenter_server`}}", + "vm_name": "{{user `build_version`}}" + } + ], + "post-processors": [ + { + "custom_data": { + "build_date": "{{isotime}}", + "build_name": "{{user `build_name`}}", + "build_timestamp": "{{user `build_timestamp`}}", + "containerd_version": "{{user `containerd_version`}}", + "disk_size": "{{user `disk_size`}}", + "distro_arch": "{{ user `distro_arch` }}", + "distro_name": "{{ user `distro_name` }}", + "distro_version": "{{ user `distro_version` }}", + "firmware": "{{user `firmware`}}", + "guest_os_type": "{{user `guest_os_type`}}", + "ib_version": "{{user `ib_version`}}", + "kubernetes_cni_semver": "{{user `kubernetes_cni_semver`}}", + "kubernetes_semver": "{{user `kubernetes_semver`}}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "kubernetes_typed_version": "{{user `kubernetes_typed_version`}}", + "os_name": "{{user `os_display_name`}}", + "vsphere_guest_os_type": "{{user `vsphere_guest_os_type`}}" + }, + "except": [ + "vmware-iso-base", + "vsphere-iso-base" + ], + "name": "packer-manifest", + "output": "{{user `output_dir`}}/packer-manifest.json", + "strip_path": true, + "type": "manifest" + }, + { + "except": [ + "vsphere-iso-base" + ], + "inline": [ + "cd {{user `output_dir`}}", + "../../hack/image-build-ova.py --vmx {{user `vmx_version`}} --eula ../../hack/ovf_eula.txt --ovf_template ../../hack/ovf_template.xml --vmdk_file {{user `build_version`}}-disk-0.vmdk" + ], + "name": "vsphere", + "type": "shell-local" + }, + { + "except": [ + "vmware-iso-base" + ], + "inline": [ + "./hack/image-build-ova.py --stream_vmdk --vmx {{user `vmx_version`}} --eula ./hack/ovf_eula.txt --ovf_template ./hack/ovf_template.xml {{user `output_dir`}}", + "./hack/image-post-create-config.sh {{user `output_dir`}}" + ], + "name": "local", + "type": "shell-local" + }, + { + "environment_vars": [ + "CUSTOM_POST_PROCESSOR={{user `custom_post_processor`}}" + ], + "inline": [ + "if [ \"$CUSTOM_POST_PROCESSOR\" != \"true\" ]; then exit 0; fi", + "{{user `custom_post_processor_command`}}" + ], + "name": "custom-post-processor", + "type": "shell-local" + } + ], + "provisioners": [ + { + "environment_vars": [ + "BUILD_NAME={{user `build_name`}}" + ], + "execute_command": "BUILD_NAME={{user `build_name`}}; if [[ \"${BUILD_NAME}\" == *\"flatcar\"* ]]; then sudo {{.Vars}} -S -E bash '{{.Path}}'; fi", + "script": "./packer/files/flatcar/scripts/bootstrap-flatcar.sh", + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'" + ], + "except": [ + "vmware-iso-base", + "vsphere-iso-base" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/firstboot.yml", + "type": "ansible", + "user": "{{user `ssh_username`}}" + }, + { + "environment_vars": [ + "DISTRO_NAME={{user `distro_name`}}" + ], + "expect_disconnect": true, + "inline": [ + "if [ $DISTRO_NAME != \"photon\" ]; then exit 0; fi", + "sudo reboot now" + ], + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'" + ], + "except": [ + "vmware-iso-base", + "vsphere-iso-base" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/node.yml", + "type": "ansible", + "user": "{{user `ssh_username`}}" + }, + { + "arch": "{{user `goss_arch`}}", + "download_path": "{{user `goss_download_path`}}", + "except": [ + "vmware-iso-base", + "vsphere-iso-base" + ], + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "remote_folder": "{{user `goss_remote_folder`}}", + "remote_path": "{{user `goss_remote_path`}}", + "skip_install": "{{user `goss_skip_install`}}", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": true, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "ARCH": "amd64", + "OS": "{{user `distro_name` | lower}}", + "OS_VERSION": "{{user `distro_version` | lower}}", + "PROVIDER": "ova", + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_deb_version": "{{ user `kubernetes_cni_deb_version` }}", + "kubernetes_cni_rpm_version": "{{ split (user `kubernetes_cni_rpm_version`) \"-\" 0 }}", + "kubernetes_cni_source_type": "{{user `kubernetes_cni_source_type`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver` | replace \"v\" \"\" 1}}", + "kubernetes_deb_version": "{{ user `kubernetes_deb_version` }}", + "kubernetes_rpm_version": "{{ split (user `kubernetes_rpm_version`) \"-\" 0 }}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "kubernetes_version": "{{user `kubernetes_semver` | replace \"v\" \"\" 1}}" + }, + "version": "{{user `goss_version`}}" + } + ], + "variables": { + "ansible_common_vars": "", + "ansible_extra_vars": "guestinfo_datasource_slug={{user `guestinfo_datasource_slug`}} guestinfo_datasource_ref={{user `guestinfo_datasource_ref`}} guestinfo_datasource_script={{user `guestinfo_datasource_script`}}", + "ansible_scp_extra_args": "", + "ansible_user_vars": "", + "base_build_version": "base-{{user `build_name`}}", + "base_output_dir": "./output/{{user `base_build_version`}}", + "build_timestamp": "{{timestamp}}", + "build_version": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", + "cluster": "", + "containerd_sha256": null, + "containerd_url": "https://github.com/containerd/containerd/releases/download/v{{user `containerd_version`}}/cri-containerd-cni-{{user `containerd_version`}}-linux-amd64.tar.gz", + "containerd_version": null, + "crictl_url": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-amd64.tar.gz", + "crictl_version": null, + "datastore": "", + "disk_size": "20480", + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "export_manifest": "none", + "folder": "", + "guest_os_type": null, + "http_directory": "./packer/ova/linux/{{user `distro_name`}}/http/", + "http_port_max": "", + "http_port_min": "", + "ib_version": "{{env `IB_VERSION`}}", + "kubernetes_cni_deb_version": null, + "kubernetes_cni_http_source": null, + "kubernetes_cni_rpm_version": null, + "kubernetes_cni_semver": null, + "kubernetes_cni_source_type": null, + "kubernetes_container_registry": null, + "kubernetes_deb_gpg_key": null, + "kubernetes_deb_repo": null, + "kubernetes_deb_version": null, + "kubernetes_http_source": null, + "kubernetes_load_additional_imgs": null, + "kubernetes_rpm_gpg_check": null, + "kubernetes_rpm_gpg_key": null, + "kubernetes_rpm_repo": null, + "kubernetes_rpm_version": null, + "kubernetes_semver": null, + "kubernetes_source_type": null, + "kubernetes_typed_version": "kube-{{user `kubernetes_semver`}}", + "output_dir": "./output/{{user `build_version`}}", + "username": "", + "vcenter_server": "", + "vsphere_guest_os_type": null + } +} diff --git a/packer/ova/packer-windows.json b/packer/ova/packer-windows.json new file mode 100644 index 0000000..98f9352 --- /dev/null +++ b/packer/ova/packer-windows.json @@ -0,0 +1,267 @@ +{ + "builders": [ + { + "content": "{\n \"unattend_timezone\" : \"{{user `unattend_timezone`}}\"\n}", + "target": "./packer_cache/unattend.json", + "type": "file" + }, + { + "boot_wait": "{{user `boot_wait`}}", + "communicator": "winrm", + "cpus": "{{user `cpu`}}", + "disk_adapter_type": "scsi", + "disk_size": "{{user `disk_size`}}", + "disk_type_id": "{{user `disk_type_id`}}", + "floppy_dirs": [ + "./packer/ova/windows/pvscsi" + ], + "floppy_files": [ + "./packer/ova/windows/{{user `build_name`}}/autounattend.xml", + "./packer/ova/windows/disable-network-discovery.cmd", + "./packer/ova/windows/disable-winrm.ps1", + "./packer/ova/windows/enable-winrm.ps1", + "./packer/ova/windows/sysprep.ps1" + ], + "guest_os_type": "{{user `local_guest_os_type`}}", + "http_port_max": "{{user `http_port_max`}}", + "http_port_min": "{{user `http_port_min`}}", + "iso_checksum": "{{user `iso_checksum` }}", + "iso_urls": [ + "{{user `os_iso_url`}}" + ], + "memory": "{{user `memory`}}", + "name": "vmware-iso", + "output_directory": "{{user `output_dir`}}", + "shutdown_command": "powershell A:/sysprep.ps1", + "shutdown_timeout": "1h", + "type": "vmware-iso", + "version": "{{user `vmx_version`}}", + "vm_name": "{{user `build_version`}}", + "vmdk_name": "{{user `build_version`}}", + "vmx_data": { + "numvcpus": "4", + "scsi0.virtualDev": "pvscsi" + }, + "winrm_password": "S3cr3t0!", + "winrm_timeout": "4h", + "winrm_username": "Administrator" + }, + { + "CPUs": "{{user `cpu`}}", + "RAM": "{{user `memory`}}", + "boot_wait": "{{user `boot_wait`}}", + "cluster": "{{user `cluster`}}", + "communicator": "winrm", + "convert_to_template": "{{user `convert_to_template`}}", + "datacenter": "{{user `datacenter`}}", + "datastore": "{{user `datastore`}}", + "disk_controller_type": "{{user `disk_controller_type`}}", + "export": { + "force": true, + "output_directory": "{{user `output_dir`}}" + }, + "firmware": "bios", + "floppy_dirs": [ + "./packer/ova/windows/pvscsi" + ], + "floppy_files": [ + "./packer/ova/windows/{{user `build_name`}}/autounattend.xml", + "./packer/ova/windows/disable-network-discovery.cmd", + "./packer/ova/windows/disable-winrm.ps1", + "./packer/ova/windows/enable-winrm.ps1", + "./packer/ova/windows/sysprep.ps1" + ], + "folder": "{{user `folder`}}", + "guest_os_type": "{{user `vsphere_guest_os_type`}}", + "host": "{{user `host`}}", + "http_port_max": "{{user `http_port_max`}}", + "http_port_min": "{{user `http_port_min`}}", + "insecure_connection": "{{user `insecure_connection`}}", + "iso_paths": [ + "{{user `os_iso_path`}}", + "{{user `vmtools_iso_path`}}" + ], + "name": "vsphere", + "network_adapters": [ + { + "network": "{{user `network`}}", + "network_card": "{{user `network_card`}}" + } + ], + "password": "{{user `password`}}", + "resource_pool": "{{user `resource_pool`}}", + "shutdown_command": "powershell A:/sysprep.ps1", + "shutdown_timeout": "1h", + "storage": [ + { + "disk_size": "{{user `disk_size`}}", + "disk_thin_provisioned": "{{user `disk_thin_provisioned`}}" + } + ], + "type": "vsphere-iso", + "username": "{{user `username`}}", + "vcenter_server": "{{user `vcenter_server`}}", + "vm_name": "{{user `build_version`}}", + "vm_version": "{{user `vmx_version`}}", + "winrm_insecure": true, + "winrm_password": "S3cr3t0!", + "winrm_timeout": "4h", + "winrm_username": "Administrator" + } + ], + "post-processors": [ + { + "custom_data": { + "build_date": "{{isotime}}", + "build_name": "{{user `build_name`}}", + "build_timestamp": "{{user `build_timestamp`}}", + "containerd_version": "{{user `containerd_version`}}", + "debug_tools": "{{user `debug_tools`}}", + "disable_hypervisor": "{{user `disable_hypervisor`}}", + "disk_size": "{{user `disk_size`}}", + "distro_arch": "{{user `distro_arch` }}", + "distro_name": "{{user `distro_name` }}", + "distro_version": "{{user `distro_version` }}", + "firmware": "{{user `firmware`}}", + "guest_os_type": "{{user `guest_os_type`}}", + "ib_version": "{{user `ib_version`}}", + "kubernetes_cni_semver": "{{user `kubernetes_cni_semver`}}", + "kubernetes_semver": "{{user `kubernetes_semver`}}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "kubernetes_typed_version": "{{user `kubernetes_typed_version`}}", + "os_name": "{{user `os_display_name`}}", + "vsphere_guest_os_type": "{{user `vsphere_guest_os_type`}}" + }, + "except": [ + "file" + ], + "output": "{{user `output_dir`}}/packer-manifest.json", + "strip_path": true, + "type": "manifest" + }, + { + "except": [ + "file" + ], + "inline": [ + "cd {{user `output_dir`}}", + "../../hack/image-build-ova.py --vmx {{user `vmx_version`}} --eula ../../hack/ovf_eula.txt --ovf_template ../../hack/ovf_template.xml --vmdk_file {{user `build_version`}}-disk-0.vmdk" + ], + "name": "vsphere", + "type": "shell-local" + }, + { + "except": [ + "file" + ], + "inline": [ + "./hack/image-build-ova.py --stream_vmdk --vmx {{user `vmx_version`}} --eula ./hack/ovf_eula.txt --ovf_template ./hack/ovf_template.xml {{user `output_dir`}}", + "./hack/image-post-create-config.sh {{user `output_dir`}}" + ], + "name": "local", + "type": "shell-local" + } + ], + "provisioners": [ + { + "except": "file", + "extra_arguments": [ + "-e", + "ansible_winrm_server_cert_validation=ignore", + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}" + ], + "playbook_file": "ansible/windows/node_windows.yml", + "type": "ansible", + "use_proxy": false, + "user": "Administrator" + }, + { + "except": "file", + "restart_check_command": "powershell -command \"& {if ((get-content C:\\ProgramData\\lastboot.txt) -eq (Get-WmiObject win32_operatingsystem).LastBootUpTime) {Write-Output 'Sleeping for 600 seconds to wait for reboot'; start-sleep 600} else {Write-Output 'Reboot complete'}}\"", + "restart_command": "powershell \"& {(Get-WmiObject win32_operatingsystem).LastBootUpTime > C:\\ProgramData\\lastboot.txt; Restart-Computer -force}\"", + "type": "windows-restart" + }, + { + "arch": "{{user `goss_arch`}}", + "download_path": "{{user `goss_download_path`}}", + "except": "file", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "remote_folder": "{{user `goss_remote_folder`}}", + "remote_path": "{{user `goss_remote_path`}}", + "skip_install": "{{user `goss_skip_install`}}", + "target_os": "Windows", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": false, + "vars_env": { + "GOSS_MAX_CONCURRENT": "1", + "GOSS_USE_ALPHA": "1" + }, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "OS": "{{user `distro_name` | lower}}", + "PROVIDER": "ova", + "containerd_version": "{{user `containerd_version`}}", + "distribution_version": "{{user `distro_version`}}", + "docker_ee_version": "{{user `docker_ee_version`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}", + "pause_image": "{{user `pause_image`}}", + "runtime": "{{user `runtime`}}", + "ssh_source_url": "{{user `ssh_source_url`}}" + }, + "version": "{{user `goss_version`}}" + }, + { + "inline": [ + "rm -Force -Recurse C:\\var\\log\\kubelet\\*" + ], + "type": "powershell" + } + ], + "variables": { + "additional_debug_files": null, + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_user_vars": "", + "build_name": null, + "build_timestamp": "{{timestamp}}", + "build_version": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", + "cloudbase_init_url": "https://github.com/cloudbase/cloudbase-init/releases/download/{{user `cloudbase_init_version`}}/CloudbaseInitSetup_{{user `cloudbase_init_version` | replace_all `.` `_` }}_x64.msi", + "cloudbase_metadata_services": "cloudbaseinit.metadata.services.vmwareguestinfoservice.VMwareGuestInfoService", + "cloudbase_metadata_services_unattend": "cloudbaseinit.metadata.services.vmwareguestinfoservice.VMwareGuestInfoService", + "cloudbase_plugins": "cloudbaseinit.plugins.windows.createuser.CreateUserPlugin, cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin, cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin, cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin, cloudbaseinit.plugins.common.mtu.MTUPlugin, cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin, cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin, cloudbaseinit.plugins.common.userdata.UserDataPlugin, cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin, cloudbaseinit.plugins.windows.createuser.CreateUserPlugin, cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin", + "cloudbase_plugins_unattend": "cloudbaseinit.plugins.common.mtu.MTUPlugin", + "containerd_sha256": null, + "containerd_url": "", + "containerd_version": null, + "disable_hypervisor": null, + "disk_size": "81920", + "http_port_max": "", + "http_port_min": "", + "ib_version": "{{env `IB_VERSION`}}", + "kubernetes_base_url": "https://kubernetesreleases.blob.core.windows.net/kubernetes/{{user `kubernetes_semver`}}/binaries/node/windows/{{user `kubernetes_goarch`}}", + "kubernetes_http_package_url": "", + "kubernetes_typed_version": "kube-{{user `kubernetes_semver`}}", + "manifest_output": "manifest.json", + "netbios_host_name_compatibility": null, + "nssm_url": null, + "output_dir": "./output/{{user `build_version`}}", + "prepull": null, + "unattend_timezone": "Pacific Standard Time", + "windows_service_manager": null, + "windows_updates_categories": null, + "windows_updates_kbs": null, + "wins_url": "https://github.com/rancher/wins/releases/download/v{{user `wins_version`}}/wins.exe" + } +} diff --git a/packer/ova/photon-3.json b/packer/ova/photon-3.json new file mode 100644 index 0000000..80e013d --- /dev/null +++ b/packer/ova/photon-3.json @@ -0,0 +1,16 @@ +{ + "boot_command_prefix": "<esc><wait> vmlinuz initrd=initrd.img root/dev/ram0 loglevel=3 photon.media=cdrom ks=", + "boot_command_suffix": "/3/ks.json<enter><wait>", + "boot_media_path": "http://{{ .HTTPIP }}:{{ .HTTPPort }}", + "build_name": "photon-3", + "distro_arch": "amd64", + "distro_name": "photon", + "distro_version": "3", + "guest_os_type": "vmware-photon-64", + "iso_checksum": "76fbe13df3f7340c94cf5706a0ec33ffc377c47e", + "iso_checksum_type": "sha1", + "iso_url": "https://packages.vmware.com/photon/3.0/Rev3/iso/Update1/photon-minimal-3.0-913b49438.iso", + "os_display_name": "VMware Photon OS 64-bit", + "shutdown_command": "shutdown now", + "vsphere_guest_os_type": "vmwarePhoton64Guest" +} diff --git a/packer/ova/photon-4.json b/packer/ova/photon-4.json new file mode 100644 index 0000000..e3793c9 --- /dev/null +++ b/packer/ova/photon-4.json @@ -0,0 +1,16 @@ +{ + "boot_command_prefix": "<esc><wait> vmlinuz initrd=initrd.img root/dev/ram0 loglevel=3 photon.media=cdrom ks=", + "boot_command_suffix": "/4/ks.json insecure_installation=1<enter><wait>", + "boot_media_path": "http://{{ .HTTPIP }}:{{ .HTTPPort }}", + "build_name": "photon-4", + "distro_arch": "amd64", + "distro_name": "photon", + "distro_version": "4", + "guest_os_type": "vmware-photon-64", + "iso_checksum": "4d5b9c6c59bbb7b6f501b7fa5e8af669332155ed", + "iso_checksum_type": "sha1", + "iso_url": "https://packages.vmware.com/photon/4.0/Rev2/iso/photon-minimal-4.0-c001795b8.iso", + "os_display_name": "VMware Photon OS 64-bit", + "shutdown_command": "shutdown now", + "vsphere_guest_os_type": "vmwarePhoton64Guest" +} diff --git a/packer/ova/rhel-7.json b/packer/ova/rhel-7.json new file mode 100644 index 0000000..d503428 --- /dev/null +++ b/packer/ova/rhel-7.json @@ -0,0 +1,18 @@ +{ + "boot_command_prefix": "<tab><wait><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>quiet text inst.ks=hd:fd0:", + "boot_command_suffix": "/7/ks.cfg<enter><wait>", + "boot_media_path": "/HTTP", + "build_name": "rhel-7", + "distro_arch": "amd64", + "distro_name": "rhel", + "distro_version": "7", + "floppy_dirs": "./packer/ova/linux/{{user `distro_name`}}/http/", + "guest_os_type": "rhel7-64", + "iso_checksum": "19d653ce2f04f202e79773a0cbeda82070e7527557e814ebbce658773fbe8191", + "iso_checksum_type": "sha256", + "iso_url": "file:///rhel-server-7.9-x86_64-dvd.iso", + "os_display_name": "RHEL 7", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm", + "shutdown_command": "sys-unconfig", + "vsphere_guest_os_type": "rhel7_64Guest" +} diff --git a/packer/ova/rhel-8.json b/packer/ova/rhel-8.json new file mode 100644 index 0000000..e5ffe44 --- /dev/null +++ b/packer/ova/rhel-8.json @@ -0,0 +1,19 @@ +{ + "boot_command_prefix": "<up><tab> text inst.ks=", + "boot_command_suffix": "/8/ks.cfg<enter><wait>", + "boot_media_path": "http://{{ .HTTPIP }}:{{ .HTTPPort }}", + "build_name": "rhel-8", + "distro_arch": "amd64", + "distro_name": "rhel", + "distro_version": "8", + "epel_rpm_gpg_key": "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8", + "guest_os_type": "rhel8-64", + "http_directory": "./packer/ova/linux/{{user `distro_name`}}/http/", + "iso_checksum": "48f955712454c32718dcde858dea5aca574376a1d7a4b0ed6908ac0b85597811", + "iso_checksum_type": "sha256", + "iso_url": "file:///rhel-8.4-x86_64-dvd.iso", + "os_display_name": "RHEL 8", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm", + "shutdown_command": "shutdown -P now", + "vsphere_guest_os_type": "rhel8_64Guest" +} diff --git a/packer/ova/rockylinux-8.json b/packer/ova/rockylinux-8.json new file mode 100644 index 0000000..2ec6ea5 --- /dev/null +++ b/packer/ova/rockylinux-8.json @@ -0,0 +1,18 @@ +{ + "boot_command_prefix": "<up><tab> text inst.ks=", + "boot_command_suffix": "/8/ks.cfg<enter><wait><enter>", + "boot_media_path": "http://{{ .HTTPIP }}:{{ .HTTPPort }}", + "build_name": "rockylinux-8", + "distro_arch": "amd64", + "distro_name": "rockylinux", + "distro_version": "8", + "epel_rpm_gpg_key": "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8", + "guest_os_type": "centos8-64", + "iso_checksum": "13c3e7fca1fd32df61695584baafc14fa28d62816d0813116d23744f5394624b", + "iso_checksum_type": "sha256", + "iso_url": "https://download.rockylinux.org/pub/rocky/8/isos/x86_64/Rocky-8.7-x86_64-minimal.iso", + "os_display_name": "RockyLinux 8", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm", + "shutdown_command": "/sbin/halt -h -p", + "vsphere_guest_os_type": "other4xLinux64Guest" +} diff --git a/packer/ova/ubuntu-1804.json b/packer/ova/ubuntu-1804.json new file mode 100644 index 0000000..d9e486c --- /dev/null +++ b/packer/ova/ubuntu-1804.json @@ -0,0 +1,18 @@ +{ + "boot_command_prefix": "<enter><wait><f6><wait><esc><wait><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>/install/vmlinuz auto console-setup/ask_detect=false console-setup/layoutcode=us debconf/frontend=noninteractive debian-installer=en_US fb=false initrd=/install/initrd.gz kbd-chooser/method=us keyboard-configuration/layout=USA keyboard-configuration/variant=USA locale=en_US netcfg/get_domain=local netcfg/get_hostname=localhost grub-installer/bootdev=/dev/sda ipv6.disable={{ user `boot_disable_ipv6` }} preseed/file=", + "boot_command_suffix": "/18.04/preseed.cfg -- <enter>", + "boot_disable_ipv6": "0", + "boot_media_path": "/media/HTTP", + "build_name": "ubuntu-1804", + "distro_arch": "amd64", + "distro_name": "ubuntu", + "distro_version": "18.04", + "floppy_dirs": "./packer/ova/linux/{{user `distro_name`}}/http/", + "guest_os_type": "ubuntu-64", + "iso_checksum": "f5cbb8104348f0097a8e513b10173a07dbc6684595e331cb06f93f385d0aecf6", + "iso_checksum_type": "sha256", + "iso_url": "http://cdimage.ubuntu.com/releases/18.04/release/ubuntu-18.04.6-server-amd64.iso", + "os_display_name": "Ubuntu 18.04", + "shutdown_command": "shutdown -P now", + "vsphere_guest_os_type": "ubuntu64Guest" +} diff --git a/packer/ova/ubuntu-2004-efi.json b/packer/ova/ubuntu-2004-efi.json new file mode 100644 index 0000000..9203dd1 --- /dev/null +++ b/packer/ova/ubuntu-2004-efi.json @@ -0,0 +1,20 @@ +{ + "boot_command_prefix": "<esc><wait><esc><wait><enter><wait>linux /install/vmlinuz auto console-setup/ask_detect=false console-setup/layoutcode=us console-setup/modelcode=pc105 debconf/frontend=noninteractive debian-installer=en_US fb=false kbd-chooser/method=us keyboard-configuration/layout=USA keyboard-configuration/variant=USA locale=en_US netcfg/get_domain=local netcfg/get_hostname=localhost ipv6.disable={{ user `boot_disable_ipv6` }} preseed/file=", + "boot_command_suffix": "/20.04/preseed-efi.cfg -- <wait><enter>initrd /install/initrd.gz<enter>boot<enter><wait>", + "boot_disable_ipv6": "0", + "boot_media_path": "/media/HTTP", + "build_name": "ubuntu-2004-efi", + "cdrom_type": "sata", + "distro_arch": "amd64", + "distro_name": "ubuntu", + "distro_version": "20.04", + "firmware": "efi", + "floppy_dirs": "./packer/ova/linux/{{user `distro_name`}}/http/", + "guest_os_type": "ubuntu-64", + "iso_checksum": "f11bda2f2caed8f420802b59f382c25160b114ccc665dbac9c5046e7fceaced2", + "iso_checksum_type": "sha256", + "iso_url": "http://cdimage.ubuntu.com/ubuntu-legacy-server/releases/20.04/release/ubuntu-20.04.1-legacy-server-amd64.iso", + "os_display_name": "Ubuntu 20.04", + "shutdown_command": "shutdown -P now", + "vsphere_guest_os_type": "ubuntu64Guest" +} diff --git a/packer/ova/ubuntu-2004.json b/packer/ova/ubuntu-2004.json new file mode 100644 index 0000000..6b641f4 --- /dev/null +++ b/packer/ova/ubuntu-2004.json @@ -0,0 +1,18 @@ +{ + "boot_command_prefix": "<esc><wait><esc><wait><enter><wait>/install/vmlinuz auto console-setup/ask_detect=false console-setup/layoutcode=us console-setup/modelcode=pc105 debconf/frontend=noninteractive debian-installer=en_US fb=false initrd=/install/initrd.gz kbd-chooser/method=us keyboard-configuration/layout=USA keyboard-configuration/variant=USA locale=en_US netcfg/get_domain=local netcfg/get_hostname=localhost ipv6.disable={{ user `boot_disable_ipv6` }} grub-installer/bootdev=/dev/sda preseed/file=", + "boot_command_suffix": "/20.04/preseed.cfg -- <wait><enter><wait>", + "boot_disable_ipv6": "0", + "boot_media_path": "/media/HTTP", + "build_name": "ubuntu-2004", + "distro_arch": "amd64", + "distro_name": "ubuntu", + "distro_version": "20.04", + "floppy_dirs": "./packer/ova/linux/{{user `distro_name`}}/http/", + "guest_os_type": "ubuntu-64", + "iso_checksum": "f11bda2f2caed8f420802b59f382c25160b114ccc665dbac9c5046e7fceaced2", + "iso_checksum_type": "sha256", + "iso_url": "http://cdimage.ubuntu.com/ubuntu-legacy-server/releases/20.04/release/ubuntu-20.04.1-legacy-server-amd64.iso", + "os_display_name": "Ubuntu 20.04", + "shutdown_command": "shutdown -P now", + "vsphere_guest_os_type": "ubuntu64Guest" +} diff --git a/packer/ova/ubuntu-2204.json b/packer/ova/ubuntu-2204.json new file mode 100644 index 0000000..64609a7 --- /dev/null +++ b/packer/ova/ubuntu-2204.json @@ -0,0 +1,17 @@ +{ + "boot_command_prefix": "c<wait>linux /casper/vmlinuz --- autoinstall ds='nocloud-net;s=http://{{ .HTTPIP }}:{{ .HTTPPort }}/22.04/'<enter><wait>initrd /casper/initrd<enter><wait>boot<enter>", + "boot_disable_ipv6": "0", + "boot_media_path": "/media/HTTP", + "build_name": "ubuntu-2204", + "distro_arch": "amd64", + "distro_name": "ubuntu", + "distro_version": "22.04", + "floppy_dirs": "./packer/ova/linux/{{user `distro_name`}}/http/", + "guest_os_type": "ubuntu-64", + "iso_checksum": "10f19c5b2b8d6db711582e0e27f5116296c34fe4b313ba45f9b201a5007056cb", + "iso_checksum_type": "sha256", + "iso_url": "https://releases.ubuntu.com/22.04/ubuntu-22.04.1-live-server-amd64.iso", + "os_display_name": "Ubuntu 22.04", + "shutdown_command": "shutdown -P now", + "vsphere_guest_os_type": "ubuntu64Guest" +} diff --git a/packer/ova/vmx.json b/packer/ova/vmx.json new file mode 100644 index 0000000..68504c1 --- /dev/null +++ b/packer/ova/vmx.json @@ -0,0 +1,3 @@ +{ + "source_path": "" +} diff --git a/packer/ova/vsphere.json b/packer/ova/vsphere.json new file mode 100644 index 0000000..e27a976 --- /dev/null +++ b/packer/ova/vsphere.json @@ -0,0 +1,16 @@ +{ + "cluster": "", + "convert_to_template": "false", + "create_snapshot": "true", + "datacenter": "", + "datastore": "", + "folder": "", + "insecure_connection": "false", + "linked_clone": "true", + "network": "", + "password": "", + "resource_pool": "", + "template": "", + "username": "", + "vcenter_server": "" +} diff --git a/packer/ova/windows-2004.json b/packer/ova/windows-2004.json new file mode 100644 index 0000000..347fece --- /dev/null +++ b/packer/ova/windows-2004.json @@ -0,0 +1,15 @@ +{ + "build_name": "windows-2004", + "disk_controller_type": "pvscsi", + "distro_arch": "amd64", + "distro_name": "windows", + "distro_version": "2004", + "guest_os_type": "Windows2004Server-64", + "iso_checksum": "none", + "local_guest_os_type": "windows9srv-64", + "os_display_name": "Windows Server 2004", + "os_iso_path": "[datastore] ISO/en_windows_server_version_2004_x64_dvd_765aeb22.iso", + "os_iso_url": "file:/path/en_windows_server_version_2004_x64_dvd_765aeb22.iso", + "vmtools_iso_path": "[datastore] ISO/vmtools/windows-11.1.5.iso", + "vsphere_guest_os_type": "windows9Server64Guest" +} diff --git a/packer/ova/windows-2019.json b/packer/ova/windows-2019.json new file mode 100644 index 0000000..ac4360a --- /dev/null +++ b/packer/ova/windows-2019.json @@ -0,0 +1,16 @@ +{ + "build_name": "windows-2019", + "disk_controller_type": "pvscsi", + "distro_arch": "amd64", + "distro_name": "windows", + "distro_version": "2019", + "guest_os_type": "Windows2019Server-64", + "iso_checksum": "none", + "local_guest_os_type": "windows9srv-64", + "os_display_name": "Windows Server 2019", + "os_iso_path": "[datastore] ISO/en_windows_server_2019_x64_dvd_4cb967d8.iso", + "os_iso_url": "file:/path/en_windows_server_2019_x64_dvd_4cb967d8.iso", + "vmtools_iso_path": "[datastore] ISO/vmtools/windows-11.1.5.iso", + "vmtools_iso_url": "file:/path/VMware-tools-windows-11.1.5-16724464.iso", + "vsphere_guest_os_type": "windows9Server64Guest" +} diff --git a/packer/ova/windows-2022.json b/packer/ova/windows-2022.json new file mode 100644 index 0000000..38f42d1 --- /dev/null +++ b/packer/ova/windows-2022.json @@ -0,0 +1,16 @@ +{ + "build_name": "windows-2022", + "disk_controller_type": "pvscsi", + "distro_arch": "amd64", + "distro_name": "windows", + "distro_version": "2022", + "guest_os_type": "Windows2019Server-64", + "iso_checksum": "none", + "local_guest_os_type": "windows9srv-64", + "os_display_name": "Windows Server 2022", + "os_iso_path": "[datastore] ISO/en-us_windows_server_2022_x64_dvd_620d7eac.iso", + "os_iso_url": "file:/path/en-us_windows_server_2022_x64_dvd_620d7eac.iso", + "vmtools_iso_path": "[datastore] ISO/vmtools/windows-11.1.5.iso", + "vmtools_iso_url": "file:/path/VMware-tools-windows-11.1.5-16724464.iso", + "vsphere_guest_os_type": "windows9Server64Guest" +} diff --git a/packer/ova/windows/disable-network-discovery.cmd b/packer/ova/windows/disable-network-discovery.cmd new file mode 100644 index 0000000..d2c47db --- /dev/null +++ b/packer/ova/windows/disable-network-discovery.cmd @@ -0,0 +1,2 @@ +reg ADD HKLM\SYSTEM\CurrentControlSet\Control\Network\NewNetworkWindowOff /f +netsh advfirewall firewall set rule group="Network Discovery" new enable=No \ No newline at end of file diff --git a/packer/ova/windows/disable-winrm.ps1 b/packer/ova/windows/disable-winrm.ps1 new file mode 100644 index 0000000..1ce9273 --- /dev/null +++ b/packer/ova/windows/disable-winrm.ps1 @@ -0,0 +1,8 @@ +netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block +netsh advfirewall firewall set rule group="Windows Remote Management" new enable=yes +$winrmService = Get-Service -Name WinRM +if ($winrmService.Status -eq "Running"){ + Disable-PSRemoting -Force +} +Stop-Service winrm +Set-Service -Name winrm -StartupType Disabled diff --git a/packer/ova/windows/enable-winrm.ps1 b/packer/ova/windows/enable-winrm.ps1 new file mode 100644 index 0000000..e6f30b1 --- /dev/null +++ b/packer/ova/windows/enable-winrm.ps1 @@ -0,0 +1,18 @@ + +$NetworkListManager = [Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]"{DCB00C01-570F-4A9B-8D69-199FDBA5723B}")) +$Connections = $NetworkListManager.GetNetworkConnections() +$Connections | ForEach-Object { $_.GetNetwork().SetCategory(1) } + +Enable-PSRemoting -Force +winrm quickconfig -q +winrm quickconfig -transport:http +winrm set winrm/config '@{MaxTimeoutms="1800000"}' +winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="800"}' +winrm set winrm/config/service '@{AllowUnencrypted="true"}' +winrm set winrm/config/service/auth '@{Basic="true"}' +winrm set winrm/config/client/auth '@{Basic="true"}' +winrm set winrm/config/listener?Address=*+Transport=HTTP '@{Port="5985"}' +netsh advfirewall firewall set rule group="Windows Remote Administration" new enable=yes +netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=allow +Set-Service winrm -startuptype "auto" +Restart-Service winrm diff --git a/packer/ova/windows/pvscsi/amd64/pvscsi.cat b/packer/ova/windows/pvscsi/amd64/pvscsi.cat new file mode 100644 index 0000000000000000000000000000000000000000..b290faaa927b055808617b3e6489271beb949232 GIT binary patch literal 10536 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 Cv=O)f literal 0 HcmV?d00001 diff --git a/packer/ova/windows/pvscsi/amd64/pvscsi.inf b/packer/ova/windows/pvscsi/amd64/pvscsi.inf new file mode 100644 index 0000000..03359ca --- /dev/null +++ b/packer/ova/windows/pvscsi/amd64/pvscsi.inf @@ -0,0 +1,221 @@ + +;pvscsi.inf +;This file contains the information required to load the driver for the VMware PVSCSI Controller +; Copyright (C) 2001 - 2019, VMware, Inc. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[version] +Signature="$Windows NT$" +Class=SCSIAdapter +ClassGuid={4D36E97B-E325-11CE-BFC1-08002BE10318} +Provider=%VMWARE% +DriverVer=08/02/2019,1.3.15.0 +CatalogFile=pvscsi.cat + +[ControlFlags] +ExcludeFromSelect = * + +[SourceDisksNames] +1 = %DSKID1%,pvscsi.sys,, + +[SourceDisksFiles] +pvscsi.sys = 1,, + +[Manufacturer] + + + + + + + +%VMWARE%=pvscsi,NTamd64.6.2 + + + + +; ################################################## + +; Other architectures are unsupported, as are older versions of Windows on all platforms. +[pvscsi] + + + + + + + + + + + + + + + + + + + + +[pvscsi.NTamd64.6.2] +%DEVICE%=DDInstall.x64.vista, PCI\VEN_15AD&DEV_07C0 + + + +; ################################################## + + + + + + +[DDInstall.x64.vista.NT] +CopyFiles=pvscsi.x64.CopyFiles + + +; ################################################## + +[DDInstall.x64.vista.NT.HW] +AddReg=enableMSI.reg +Include=machine.inf +Needs=PciIoSpaceNotRequired + + + +; ################################################## + +[pvscsi.x64.CopyFiles] +pvscsi.sys,,,2 + + +; ################################################## + +[DDInstall.x64.NT.Services] +AddService=pvscsi,2,Service_Install.x64,EventLog_Install + +[DDInstall.x64.vista.NT.Services] +AddService=pvscsi,2,Service_Install.x64,EventLog_Install + + +; ################################################## + +[DestinationDirs] +pvscsi.x64.CopyFiles = 12 +DefaultDestDir=12 + +; ################################################## + +[Service_Install.x64] +DisplayName=%pvscsi.DiskName% +ServiceType=1 ; %SERVICE_KERNEL_DRIVER% +StartType=0 ; %SERVICE_BOOT_START% +ErrorControl=1 ; %SERVICE_ERROR_NORMAL% +ServiceBinary=%12%\pvscsi.sys +LoadOrderGroup=SCSI Miniport + +; We need to force the use of \Driver\pvscsi32 as the driver object name, +; otherwise the crash dump driver loader functions cannot find the driver. +; StartName entry defined in the INF format is supposed to facilitate that, +; but at least on win2k3sp2-32 and win2k8-datacenter-32 the driver installer +; interpretes StartName as the name of the account to start the service under, +; which is an incorrect interpretation for SERVICE_KERNEL_DRIVER type. As a +; work around ObjectName registry entry is added directly using brute-force. + +AddReg=busTypeSAS,pnpsafe_pci_addreg,vmware_installers_addreg +DelReg=driverObjectName.del + + + + + + + + +; ################################################## + +[busTypeSAS] +HKR, "Parameters", "BusType", 0x00010001, 0x0000000A ; BusTypeSAS + +[enableMSI.reg] +HKR, Interrupt Management,, %FLG_ADDREG_KEYONLY% +HKR, Interrupt Management\MessageSignaledInterruptProperties,, %FLG_ADDREG_KEYONLY% +HKR, Interrupt Management\MessageSignaledInterruptProperties, MSISupported, \ + %FLG_ADDREG_TYPE_DWORD%, 1 +HKR, Interrupt Management\MessageSignaledInterruptProperties, MessageNumberLimit, \ + %FLG_ADDREG_TYPE_DWORD%, 1 + +[pnpsafe_pci_addreg] +HKR, "Parameters\PnpInterface", "5", 0x00010001, 0x00000001 + +[vmware_installers_addreg] +HKR,, %pvscsi.installers.value.name%, %FLG_ADDREG_KEYONLY%, %pvscsi.installers.value.windows% +;; FLG_ADDREG_KEYONLY +HKR,, %pvscsi.installers.value.name%, 0x00010002, %pvscsi.installers.value.windows% +;; FLG_ADDREG_NOCLOBBER | FLG_ADDREG_TYPE_MULTI_SZ + +[driverObjectName.del] +HKR, , "ObjectName" + +[EventLog_Install] +AddReg = EventLog_AddReg + +[EventLog_AddReg] +HKR,,EventMessageFile,%FLG_ADDREG_TYPE_EXPAND_SZ%,"%%SystemRoot%%\System32\IoLogMsg.dll" +HKR,,TypesSupported,%FLG_ADDREG_TYPE_DWORD%,7 + +[strings] +pvscsi.installers.value.name="vwdk.installers" +pvscsi.installers.value.windows="Windows" +pvscsi.DiskName="pvscsi Storage Controller Driver" +VMWARE="VMware, Inc." +DEVICE="VMware PVSCSI Controller" +DSKID1="VMware PVSCSI Controller Installation Disk 1" +FLG_ADDREG_KEYONLY = 0x00000010 +FLG_ADDREG_TYPE_DWORD = 0x00010001 +FLG_ADDREG_TYPE_EXPAND_SZ = 0x00020000 +FLG_DELREG_MULTI_SZ_DELSTRING = 0x00018002 diff --git a/packer/ova/windows/pvscsi/amd64/pvscsi.sys b/packer/ova/windows/pvscsi/amd64/pvscsi.sys new file mode 100644 index 0000000000000000000000000000000000000000..bf6d2e21b3842d33d15a9e6a7db57728028179ee GIT binary patch literal 60224 zcmeFa3tUvy`ZvDk2E%m*L`6jz6_nC+6jBt_8JLkhI$Q)bD=*wHg@DWqqM-o;%CMcZ z@|2awG&|MlTq5gN+QkM@K_xGFI%!wb${{TAlA>AjexJQ(0IkmNd_Mp8{eOP{_kX7L zUTd%QtY<yzd7kyGXFYqdO@3e#BViauip%9<nB7R}r{VAa`qqQ$!0>khnKykt9JyO$ z{BY#_!XlHZth}_KJZrfsC#$5S)U3+RQ<YmvR7E8!U22AEd1-Fmgn$6w2sdh5+oMl^ z_~)Sgo@=Qi_&__JUpY3|ohlC;=IfRFKjq5@9%$p!-~)${E_Dp#>z6uy*iY@2ItK9V z(u;Bm3Fq&PAWhFOx$C@{tdqJW9++-s6yqljR5FWDqIQ>VN2=uW8WAOw7c-1661u$T zfqoshz{GW&h!HWFT2CW)TT}{2JP8W=XPHgg877Sin;2$G2+~aqlSzWv09h<UdYoZY zNHi-LCKl=M{i;zf=LvN~l=aE<x7tiF=T(|f@wW`zl(^>OB4z0JQ!z~8g!0@ha~62! zg9_l}2Ty$J_tP*8J3#<r66C0;#zjoZ#?|kqVVJfFWda~S#!aZCK8yPSA*%sCLyCbY zV#<jZ+i?*b_22KOLJt$lP31W#<9XMF%phDeuzo)c!z4^7&nqqkpo+?m<0A1j(nH@* z1Gv7Y|GRT6x1Y7Ka@LlnG}<(wMjNYQtrwzLdz#Xz1kYLP{-~Dr_IB2`I3kp_RYj;4 zu=e7JZnoxv5^BbBvQJBqL<5T+PoJ{fjp&b9yD>to-A(Lu4x5Q;d%#sA^4IT1zTRaC zW9vV&3})>Ee?TcJuTy2Y%jIekbwwDtW7L_-XqO%1s|U)Hd8i;HP9vZuJFK}8X2xjO zMW`EOkE~=Eee@yLF3SvNm{ycgzpPCc5zBHXQG93^UtHGVF4nN<!7h72fUb;yrV=!Z zvZc^g%BLZJcCe6Ng#5R}37h^EWPkGFvrXuzigc${Zxi8%#~2wN59%8jXT&Im;ZD^% zECVn`Utx?^8sj@)0N28D$>3PKvuPxQL2Qj6MjE&WRR->nC<AwH3GrL;)(Pv)0gCl+ z0H>nvPq<redaZoWJg@GMS#SMLWR9iVIJ%9XTafjpNKyY2;6P&mc;13E)tydtr{V6@ z+nrvVA;2Zkv-LagUZnE=NM90=*18)#;7$|V>D}%$*quH^sr9C6<sx&6^}7(WmdJ~t z+gQ2{rCR`~waf#Be{?shcc;tU=>m5;!<|OD(>0V@zZ+?eCo~i3Hi~WofzT`m!dq^@ zuia_8JAKieR=Lypl#*Wmw*FVo7-hk*%|84@@Forj6M>%x_mTF)5JCGtFGfPzUl&29 z>9Rs8v&B=*%|=-RpE(`<(Pc%DE<#1K;;sf=M3i>-N$8VP6bJE>DjFlAj9kk*d|~WF zhS4chjyi|MmughbXU7t{4Lo|%n$_X-yfs2i1k~&&I+nGxW6~~Sxp`3BfIS*0S#1Mo z)^T&>Ixay<+RfG0p#(V%S6eOQV!4D66sWlbC6XvE!Ji98M&&&JlgowiE&_wx<F#H$ z!(cByj4o-gz@m67!5gF0Dc+jn#d4>SCdklvJS->6rAP62o+muq5$7^YT1s~+q0Db3 zd13m-6DgRWNl}DN&2ovAtyoMMrrYWt0Ue%C1_VO`vcyWx*TzcS73org)hV(#310^A zAw8HR5jlVQ1%229E@;8ov3$S4SRJZnnA%>foH0T}ESx%m+AWSK!ywbZyna6#FRWp4 zgp#$HBSP;3Usci94BRR1Cd+l|xPA2>ne|#wV4lL?$MW}3t+hu)$}(`ghGDwKAtRy8 zC}h5yh0G9SPEbaP%x=nfBeM}1BljW5mH}GFwK|vo>~ht+%=cI?(!6$w%-(xp9GEU} z|3haca+HSnqhu!KQm8J5lhixR(u0Xo5vZu}I+!REaT05fn6h4cR#M@mX)t+HskB0h ztel3g<_@x)87$$7Aqwk$6<DFkpb<l$AgT#-#QE7Rk{wBh2oZ!pBH83m%(n;p1whpH z!OqtKDd@|>`?L#z22z#1SA=RoWumM*HUR~Oa60->>|BFc(Q2gv!n=oV+#4Hkm%Kr$ zE4c*0>g*K~ZS%MOdhVd}hcIf{!@{Zt*%wOC^CkE}&Ukd%H5&@0f+@vxF><X~f1n$? zdH8PKG;@T?G#<<rW4c8ja_&dD4vK*37@**1Aeiup^D0zkvZM1lx@NB*bSW_R2YbNe zePAB~jC+y@WbNw+inZnxVj8yc`DUj-TXUZ22FGmHY5>{ih*;YrY0YQ+S^Ii&uUH8d zx5P!9l}74vu(r9Gl6}n`{;Z_6`Vj-x80!{Pn0;9p4{b{<1aYABzouiXY3N+XS%uyM z_MwR7G8Q?f3B`mFkVk~#Oy?+}m^h5$e+b2ePCpd0+>_J|ioYPdJvV9PWx<pvnjJ%i z5#q%WnGma5yL8#o{u2aZjjCQPW|~A6KkI5GQz3KGvIo$p7T#7Yfbi#Q7ibr1Y3^vC z0(%NjFQ|ap$gPewa+i(dNJM|e+R&v_&iB_%k{t$;Ud5v1%mYaV?x@o~kYON}J?Mf{ z*4(T{S`i4K{~{m_fV$3TM}`~hj}GAEto3|%^!N3L6m|6gGjdfhtM>8Bk!OsO@9kPw zoYR!3v*(EP+<rrIhtw!(HE@ndL5e*}(^yg<EWrOXz$i;Tb5;z+qMQ4b98!^f)vEi- zpLA5qeLjaX{-AUn;7+<)Sjk7M&Hs}Gl!RJyKk;X5#!uv2U|eT;J@v2B+QJ3Ks)@0V zA;j3N5xhvO`!(8S+NIhh+QrM3E@|mM?+R&fIoI6tj<;cD=-NH^jyRPVw=gBiK=u~v z{RsUL=cOC{TVg-p2BAsXCnG^VWYve{_sT~DGhPmJei`X%nD-lsw;m#^{uWj;J$H=z zm}cUk9|?bw+q$Op;^&SzE6}yR@fe&T6-+FnZ#<}J{21<*a|%E7q)HYuHHOi_w5nSf zQXIEcCv(3Ciy{V)^ec1U$(#FQ&RlqBAS^`+ir$QYEOwJb4$hW9cmL^}+~aZ?$)bIN zS$-;%dOy}jz`H0QLjD1$KH}%~Qs7w{!g#n|8KseoNIsq=!Xu}{@O7Ohu^D2mj}$WI zp)5ByjjN$0c3Mp}?r=sRipj9lym9PpPjf{1t2uH>AN@53H4*E9fjgn+t|oIgoLN_i z)b}E`105<Sqf)!O0}tF_r~W!jfi1txIISSo5`<bEYMm2N+W7<6sO2u`rWM2})_qJ5 z@AJm>3qZ{T6x)1G%6-gAmSOdkfSrl{I&LnOJjl%X!xf;7&G9!VbYE&E-y69raiuZk z`{AxD>KcKHOT@5_8aY25mn7xxlj|CCBZQ@X26@Zean?4w68(4)H9LUJI4!@@5~`zi z_5c<2&JA!jprSJg=zz<~L1j2^T|+6`ngrg<nbxGRY78>JHEEtmq-!Y1XiZWBmI26p zc)Wkc6L_cyQ@r&wWq|euEmJX=E`;VaDxC4S2ZS;ndV)M4R5*hsVZ~@*;b)^gKtd${ z0M)MkfJ&+@gp6{W_lzeAHUhAe0lMqd<34^G90e4WI{y;FgKj3!FgE9D$^i5Nfj$jT z5NCUh3TJF5kSKsq1|XdTVkT^M*@tj}0LRzZ)^8yIZZ_6lOl_uH=Mw=xZg%DSay;p{ zm?*9F7ncPdML-2sDCeZh5T4y4f7eCL>;cWdtK(X<m>YwrD(;F_@zy~_!YN&B<m`tS zreRr@*<aU?Z_skzcmCbol`X2b8%`Q!?Qxm4MtdEvg9d&j@`EXGz6y=1bC|<m%(e@i zNm0z5bpGuU{Fl!Ze{gh3w2cSq9JpDK+utzTKxnD~+_)?Y#F;@c5IQpjOk=sf@|CDr zjT-J_XAUrF<GxqaE`{^BTZM*k&MrBhss2f+NOIc#&M$-@iaToiJFR}sN1^|4oQDI8 zl$aV094xo6lJx8;$fxroKoK3pSYbHD#JSJ#=pGKwa(jS<h&bu|5=(jCaD)x#>^VZq za#}if2rWa%n?kQ8C_}TC(X31Mt_GDlZXJ=yJw^oTxi+@>j5l_Gacx@8p-4Q!Hh0Ll z6Kvc*#omPmMPl=wUjP=@tVn8B>`j@2<x!>M96+3hCBZh^>`Vq`Eq9=cru;l2Jst(S zX&*`p3(t@{78cs$2tKWueA=prP~JCFC3D^UcKs4-J?3Ivt<~|<7|V6+Lx8zp8sYn= z^DC&0);b1z_Rq`<s^b`Q8r&;XkZF^EpIEUc8ST=z=tBnXcru*O5xPcStzCOO-e=_i ztzvJp-QQ+6IPLx{YuzWZoHfK9w|KGE7ExD|))t7(zId{OcSOkzg*mE%FN#%43y%Z7 z1B<n3%`#9T_ztn$K7I6OM()Hti-y3y&L$sxA{<`JTwUzqh)N6QvM!<wDt@T>jFR<h zV|Rlq%*c^IfCut4x<-QO>>er}#TPes`3g`fdbH8jOxQSSxklcdlC;^dG8u#&lYx7J z*ABRCk~ST98UtbaN5hFDZ_*}aZ3*($1h1h8DH*r~Oj>VrgxM4pL5ic&?xF1s)o3|6 z?PsVa*{;EU1`FJI#oGy9`CzCrUpMUmxy1*aQg2;@bf_WgS$;1zPj4T0&iTRRLZ?`; zV#%p^I0&R$_p?}G__c-hrfE<JFi*1-2{ErOLc^ErsRl;gw?}*?`XlXbC5oM^#zQ|j zUU#4#vCst!NL_!(Jdg$58rDWdtX%^K3xswHzAKm&1Az}^?HeLAA|Qb2pT@%(0QVi> z1P_HQcfU1$H?5Grx20=LG+-9`k#=dZvf}aDsdupE^9_a_vYzp`HcM<ux5I-yh(-<r zWsy#HbWP-Y1D8Px(+{LoBc+o5P3j#mPpZT@)WX)FRQ1+kAjBo`bg+{8+!|3x&o%pr zot|AHzy8z4=G<a}<@dr-B#zUuvP-!zs#?|pBYgt=QSBigUf7k`WmeGP)S{pfH4TYS z?jz2**t7O?ytniFiT&yad%cDBb|d$>!S<k%wo@APy1cJf%oi;z+i9+Wc4F2WE_3O% zW`+478kgoRk&Q3r2VVUM%OLoY8=+<l_C%+_me_%SnZcTK3SKgzQ789^fx-DAy6>7m zbp1p;=lz=SU->lw1n>aF+9lCUzNTq}!{ye*Gl9gl#Sx33jvCLVf#(-d{>Fp7{EDq! z#RBrQ`EH&j(lRwa`;1&OIfyD$o&bdcSph`W7$-InZkm3_*E$F(3C!f@MFvp=LcmNt z=jfb2hGEEuqx}oIF~XnX9qbc(L^SqS0FB19i*e5*iUEZ3dl|t~&cqllKF~XcMY_gS zkl|@vLsf)YU*|GoK2f|TR%cI8Img@uKnx3eog;#m%KKNuY6*2W4c!?zQyTA?8M&NH zEte9a<z_2Se&hO38#miuQAfV5KH9~#aR=DZv~0){6mRQBhRoq&Mj}@=auAG#AA+gu zE>S0~8rGg+iuKPUqp14>{}{Zo++2(~I@;RfqgdC%m-$r;wD$O$7g&3O6!lFg+U48% zHWI`fW>{Tf^H_UQ&!4}-imRyGhGK*yV0pGo#AvV_9KYal?UHt`M+Mvv6?^^E(2uIm zR#)$nc2(>Wb(QZ5?kcf52Ot;+)9i0`O3e3Kdn9Ic*PLA;UGsMh=t|qA>@r$=B3DM5 z!>v7G<&&*F!z+TUJu0)hVvMzCbcK(#C&Ci8%d2b5uE4HQR_7S=Fv1mVb^0K}*&{Ve zcLjC%?oxDl?UHwicgY&}Ma+tqY884{!$OI!u~pNsR$7hFqPBW18*GV#ce~JwODxkV zb{;Rkhf6f;8~5oNQ?Ri)(zsAYQ`Grqf6T}fFMcO+vcm_kjkBeV3nhBRTWuJ6auk@L zcxxXG|3sI2gK-iYjFo6d0&_XEw%JNvWc6v7&bmgoJ_M7|#~eMmM@kY;0zA!>MPy&z zhC64pV~35XZ>)iPSc9l9zZt|<5c=ny^9I|)p$3~tg<uN5mnIEdz6fXptCOZ{TtOPB zue)ZR82ypKK6f{-d#^&%`6=}~y7xZ6P3+aZM+&*e$o6yV$TXAwbv{dvKeMBI*yi)z z28oMPSZ|2SmGD#S0e`&*gK7N^{!NkfhG^wNg!2LrssAq&>$p^`Qv=^Yw*C+?7vS&X z!JyC`r~QI+(|NeTsd6{3uOA<oSY~}x%2>knWI#`9?X0&m9Mp9M@?B}EtHsoYC(Y%s z@#(a6)u2yN{{Xa<#?CVu7TL|{FR@?{qtl4dDR}TO8aw-kv6P#@aGu$MMdy7ZKFUX? z^d?CD6x#0mrjtBJPXuujIutDF41YKzjG00Z9}(R9fa~%lAM}WG8eFlyHnhobFFWuJ zdD}wqHR>;v_J4v*X(f4PVXsVKnpUK-+;>LKu!!X@y+do4p7Vlrki&J+j)Q?X8@I1~ zKG?hy+<_kcNLx_s38Fs}f}Quv(JdScEf+||+HomTTLVQwg~Akncd4?k6!Dce&{3ma zW?$_xK<f4dEMhaf>~AzAB{b^28j=zrB3sh5M!k1K(sWJ_PcliVcuVhROM>gO4BN+L zp^CSXeH&6^bzE~^qcpM(C|P@IR6~-^`H2eK^dzl{)BC?7HhVSdeHxOqwq+4`j&xv% z<}j;MV-Cd4GNd&r0`modQe-d|1WJ*SBEwEwrmR@ba{5@-&JJL$XS%JiqKXt4bFfrp zO^p!oOD%l;=#QN5peaa{BdBEdtwAJ)aG558Z_mNo0pDm3MXV6G_8gu%yD5UqAz+sz z=d>76l5*NIRmFowPOoxf{+N2A&BTKO`cOje!zHPvLEBA}fPIO8eHrSx`TPuc1SQU= zpv8T?y+*y&Qg612je1t=@)|FHWr<~E?9w@ACH5<6oG*g*<J#ipgqCBmn_X$!5~=|w zd|57~()LV<TblhmhkUr2wDeLyTiqM7==BF3#6C`@o{vQE2b)mBJ-&f2Kh4!NAft8r zW`dI_InG*J#k8VDu#%hLXvQeC;5Gq}++B}%7<H$2$WH+5;yucbK)y=58*M?r?*;NV zL=ZVNU$?<UCdca(J=g4aAn`a}?f^{heAOU2g$+#O9C6(t%L3TwbQY7+J`PkSDDn7! zH*Ryha7!Rduhzknk0P&K<&=L*(l~iAQG(8nIg1cNu5&2rgQ2lavi#OWiHzy&K-YwT zu%r`~2V}T4J9EE=jVJ#Yon@jk9xVbNFt!5nzkGwm^Q{aqci7q~HWyku1I-zfn-1sM z9A)j4ng>{K_?SnaeHz+39|3$<8OR}0-hrZbHll0ya|%}6t~Ed>quNtgfSN=gThyg< z11!N%8WSa5G2kv~qlGL}?A^dSn(!j>!iM4%mQ=@SC{8ewYJ5=B<kgT!!JoMlODi17 zkJSO3&I=eH?-WuL?FkuCvN$4*H(weOh2Z}x-kL{C58A#L0gP`GQ-F-3@<&jBe4<$K z_5kNLcZC-!&bljP7>?9ghZ;cP9aq2&p<=ENHIt*(n_ro;d58=@Kn#uqPHwIeaU_4| z9F!ZmxwN{ZqTGmqa~UDdXrUO!A)rzyhHcG=q6zU}=O9$VucLQ;i!igxV7@Bxt~*nu zT>ys=fHx0nx|w%qV|h^Hq}R#(l62Z>;UfrqY#Q%!0|LqBA3DdsDxeoLFmV2~kBUI_ z%Q-0=PFMlL#rD)etSwyoel-NitvkiTEe*9L!S<ePO;Y|1F>O6}AYW%I4RL1jai6DX ztA~Quo+8=XH_{_OoC&DazDH*iP&gEb^mhG=fO|QVM>_ED6VUH)9>vBm_~cYI6e)qh z8f~QNUnq=de`*n0P<0r|)V^^95<oeeU-8u&Rj?pbJpw7ppFryxYV8nOQ-4&gqUFZE zaTF51^$wwWi@SOhR&P{CAmOWP1>|D_gw_#AQT`N=>j-&{P#vWZs>dKjHBtUCt^GPw zYwea=>NpD9Tpgmj_KkPbhSxrDG$9xzAQ(SDKyWuw9%!J9YNbN$1n`?sj2HM$TK*j( zkYqj=V|$Cx19Z760<n!;e($2z_Jw=<#ZKH#!exE^Axojwe!plMCWd9H9!o%w7TaHY zfQCB#%Nfi&7REpG12wcF?i14h!Hce?A=y#;MsU}70w{DojJJK*@nWUcp=TfpvBlCG z!hnMWn?<nFXuR@iZG?3=>WqbC`AA74Hf<DevhuI&+?G*D-0^99z-X*Hcx~w%1}q3H z#^8~)Nd!Lxdm<DCrQq~J&3337q=(9yS17T|_Q0{i6K|7o-hp4IYIdWJT!0#f0NW-c z$AyIKwcEELHo=_9T9ScG@KiJVp9|6OPO2=7VXh_mh%AFJT9!s@>TtQNIZB2%Z|S@_ zvv1rEyp0)BT7EUOgHj{@#3k}?s{x_nH&xCWkdLU}Y>NNf4|I0{9oV}VkuzI{5_Zlx z7`S_l+WSN@9K=joZ~%&j-f(}}G75i15j`gZgbgFl*8F@$cpX3i92d}OWEezUo12yS z*PC4~i>QTdKBVl#B*hLBqsOb%+z1uWo-lF&`vJV?I;xDEtO<G6HIU8+D8hAsc18Y- zyel#s(LGmW5Pz5O_aFK4^ZdP;zaQc6@ALQP`TGvs`TcA%r^QC&Fy0(EK{T_Q7pJh5 z<?FQ^dOxJ&P9b=vwVvyF1#6HN!IR{iH0kwIM#%{_r_DI}gdwLTg_9~-te+fC1aS%S z#`z-2wS5Y+4|YJ-j&7r*EjhZ)z#Vd4Bc<Sba7A857*g0X%SrZ`(Rgup8iAxr>dm$) ztoo);g}h0sZO{(s%BO=2+d(7ej=|atO07*R*EY-xB_XF7v9xL^S4lbAK2vlJZu?<K zKZ->&@<0T+2Z7^O%v+H~0dZEwiy9J#DD=l{bHjL72uu2A2Z+q%JqUN)#;y%R5LQS2 z9hNV`*+6a@H&5AU2z8bMH<0ja2G%<I@&1OMUMZi(n|~ZmI&I+m3>@@8WbrkCiENx! zZVu79_8F`#N?qJX<zERgO?ivn7Strta)(*TA#VN=bchh7EvW0PfY^QK@zYTQEJ=vX zBEqTXj$<Kg)^ewD)Tf9)UN9SXxekG&C`CQJY{0pWc%9^kwi?mT5UqVKtBX5PzDAE| zgw5CXfTpV)HNlqR-LV84cf34XXPcw$O6KOo@<`k*@77vxs>^5VAT8gnS=?$hUkThX zcK>i5w_F={xI&|C3g+>J_2LT#I-3{@)tWF~g+8=`E2`EuI*9ccDvbv=HB@IC(!zK1 z6_+rc@2SqQ>LjAU@mlT>EUCHGg_B#-4_RO_f>Liz+Gx`qmcqt<OVzI!xKH;`Wimy{ zPnY}IrdwORC99>@^F6G!8KK5~as!1M&vvec*>cN^Ke{0tR_lEi&0T<Urm>u*40{`H zO{T8#JKK`jWUj-uq#JMBY$NO$A|rPRFHCgYNgel@w)$teS<4~NuJ2|!gWPCOhC1rg z1l{|Fx7><4G75Odfwp(=ix*ccMJpfkA{{m<K%wInbR!=9q4kDLvHlgzN=2OwcYWRW zin<ZVz#~-DH3I{mA-9iwL>+Dxvw@o(ixojpS3%2p^r6lNk#b4k;j$RQ3pUF1ai5yJ zsIRVPuq`UIh4KexI(-3&=25hujOSA0^_WDGWAxI3rV^K`K@!EK#v)M}xOUfZML5&x zhemw!#<otGfIvkEx&U|^0<?_3t>p<!LmijANNc^JDIdor!2?T;*NsE;B*dGA(;dt2 zVtJpL5K-qSRdp@ryL&xY7uQzq*Xpgo@H_3mb<kVhS&tMbtv6UjZ9e2`Ef{|>!7lwc zk$BY6qqE%?V@p!UHLnbyg?CvzEBRzxTNj+<lhgzH1jo>knr0WAesl+hgn|XV;~j<` zdU+g-hX(8nh|kw~!FVa^zVt|_MaY;Jk(lZ~f$2j-H8E!@w_uU=CR;ucp^tbi=HQq) zc7KI-+zA>bv5lCG7YH<FH^VzuVmNL_+tYsTd<MvYPHD93%lgr$eHZ17uCYTqy+u(! z4i9?T&?lGax#nbUp<IjiMp~;QRBtcDVah`m>`>nRscWc?`ze3g5OZL@Uh<Px@pfze zv@osZGM?}n=co%M$F!0U@j_4=_n{*3grc?`o!Aq^ar<<N#BN0`EsbEiF7A?ofA^@f zO~uAu3P;snXG>MmgiJEn)8#Z7?Wt-k5j_eeb4Br})@hyY!k*Qvw6@7@+8%n(ViR%V zE_y(S**3(cy@k1|#GF+nk2`7Euf?k{`@n5eV9X`Qw7Z9*XZUD{z&4AWuP7<D_8}_t zGzJ}BETEX>7L+;dsOlQU^ABnx2Z1`SyDJDfqvI}h`Dovh65F^dG#_*}7^MBY_Spt? zO?6A#97U4WafiE<o{~X$qGg~T{~O!7q>YDdgBlSjhMR{t9o8U|1}8LGPpl3HH{d7o zdy3yVKf4ID2&<%f-=t!#NARw#r8<5H-nFszL@7H%BFEuo|5n*fI0r<0*9bTc+TFB; zhAZ+o+yti@yJEoFWORlU2yu*~T|;ZF27cCqfN=X+YEG6#5cU}hQE222BR2Amln{-> zEb)(Zeg;I`z!`wSd$Tp(yQyy6KFM+S`zi32p?#(i(}7ki{?Lja-l?Kb+WgUM!il(= z3l1Jo)`98BcU>oJ;1NEfsfMKz5y8<BM8ctWstFbgy4pD$TMqseYV=3gTZY5P(Az}# zYk(xD{B?3F;2$(m3;1S_^8xy;5gqjG2?l5~gu@dQKC8J<O;w!iJ`W+f2)q$_lW#$% zjBt3u$wR;sI|Hu-orOixd~-U#M)yL*o`plIcABxPAQusovMl$p(@6F7G8yl!1H93Q zwH}7(_Gx3|gCM&4@697w`&<_rci5f?sy|dD9Opj8@j!CI$P3dn#E)a^ubCyRZ6*S2 zr-=*-2q$9?Lv9E8IBF_^PW?mZ6~!v&1pq4cqK=BOmSWAp2|f5hWJAzcvDb-Fyakrw z{e;Rn9Ar{AGm>$O0!1R{WU!gFd69h@iV@*F6U((Z=YuAe!zsXcNf*(SjEKLSoi@`f zG1$WS{+*9Q>=<x3;loR~KxdnjFwI1NPoY2O>!{Z>UewfmZ}!#1BFt@;Xx<iS8u3OL z?iWdn-%Hl)uf`ZW;p`=}*Kqs9QD7o@#?GmN`xwi|6WoUd!FAb2EjkqT3@|S5@xER% zhm(L?`8W26h&mTx=LsUiK-9cZfVQ?_ucBaGj_P<3=2YjO(Kjhs6gEtBid5x%07{P* zL(n4iH(GS9X5mCeiOV0PVW?n~iU6hM->Ey#f&=vqjbigbaC4XkU{N1HGY<fsVF#yT ztq!raVG3)*3kIUV>iC7<a&-~4(ZA6*YROr2&onwOgPF$Cfg^B@e-DssOAJl6%~g@s zgN?+bhcvxRip4v;HmEaje3xv;euzS1$MGhh%{_-HaABVEjtWd6B?usa@-OXaCp{2< z?n0r^EUYfXEo>J5DW>cdI-VbZgZgGEYaNU8cU^bUepQX-k=}|$@v)Jkynp2^=8uOS zZ9L_C7Hw%`>V6w|5@!c!G&np(gAw+n$*}sc4jkU6oY<3|mS}LwK~h6v6a=IIlV*Za zrvcP9CQ{_aeO`^<SE#C2L4UZ@P&sp$a{=_;-Dc=Ww87S%(M)z7Oe7d=jY%uf9@{xs zB>ZKAE&|~mh!^xB26TtLuDK8eD)_g8EOy@z$2(5ocm5TOaSilE%!i+?FaWS?N6;Pb z1MW#ipRR$QVbd?DHE-KkZZm%}8@$rm4MSZ6pF=(EBdFio|Hi^-*MvG>1QJln|BVIS z`*`F{QwHy9aZ01j02Rk8Tc`6uFu2}PDFb+Hi;?HEec)n{qus3|x!D6gAXW_J{a}IL zylR~0F}NHF)$wA+a?Kd$Fa;T04*nfG3zNt5nef4W4D{#mT@&&6N(tX#Ox9k%y7yeP z=lw-8j^xt&3LGPGFKq)b4WnH+K#8S<%sQR9VR6_5nmED8eaIjEB#KZ6oy3t|XDxc8 zlSW6#h{@P323RldVAmkhKxjp3&pU}33)5T!Gw<PJ5RfnY_+ms%U>Y+)z7X*i><lku zH`knZ(J+Wxpr8Jz1^$Zyu7n4l68KQf^WGgdp*3Qjd=Ew3w7Z2~FOLTlh=(&9iYbjP z2d|f&SYjbIZe{q(m`as!t_FuEp{3Ln22s@0C64|c)5&OG(ZjYnaQM(=ROrBrYnIV; zswS8p8cw>p2G*jVXjfMXW@IVAz?iNCHtt6YyxO0-u)ja*TqG0|F;Q&X4{io35`M(7 zN2dvaJHi;)hfz(G(`;Zl*yt)+*ox_uBpvz2AaKma6{!P^D{=&X=i#x&EBSpqOpOa$ zd$&DKQ@o9M+39@Dr0JYC42g!*!dhZZ(VyImz}bD2vCc}YKAOg=h{>Aj^P=iULK%w> zju6P5RlLd1k)x7dFZkDUui+>Q5m*UwuubRvi~eUL`8``E2%+<BJR$n-51>xfTr&<# z!sBE8TCsOC@JE@te4#E6X}MblGH%I^R{il2q<S`e8*j(2=|2*gKUch+{3*DfZ}V#s zamgJB7I4W<7+A$0nj7Xqj3&9!_J|Umb~i0(fjog*1(yUg@~fKr#4gO@DPdly`qPX* zjyes`WD~FTviGNZ2oLu#P=5kPufRehchjc-glA&%+cy1IyNTLXed`EDf><&PQltKm zaCB2!cR*41HEJ<vtvy-tida#nhlb*$(DD<C`opN<EFZA;wIVj|5oN_FoZt+#R1w?h zWP3Wcs-!acICw4xP8>*A8f;To?h(1cHVaV_+b*KD{+gmL8}mi+Hlp~+=kslP5tkSS z?s+57X44OD)nBAQA!~#8V_Tze?u3PG)L$an>hDIoSCoqSLZo{@Ir9OufXTP5IYhpM zG~bqdNI?5G(4GKV8aQDU293g2qb=_PXB=?Sf|wl6`+s=(K}$dH!qx02j`rt2Pciw5 z{|YtFPbp)xE&ssz1z1KrC2po*D&<?(oDeJO=kl}apkdqrb1L1U4RHs|ft>yWEaR;X zEN`wZO{4yU25%HKJLL^YB0i&RND`wS%yo6CZm&l%wB6O^!x!@|inUeBO1c&GX(%^v zUOazDK)Qwq5!n4U#&9QV>`6m->SY4;;l0#bQeRP;Xa2XNh8A@}9vCq%5?)aIKyK%= zAdWBUd;qy*+ZY5eI@i<uq_<<gugjOF_Sa<?exlgOorNyL@qkF(2aUC=t|g>aFF`hN zGZl3Y@@nPXt5!*n|6sf|g>os}YGt&oxegWZ=FfwA)M_-ZR_0j6+ul5LsNE-M2fZPl z^-UNNShVH?=Ph*T7I6I|V051<&BlR+f1yeSEWF%xtUijm*MNif{m>(n_E#OlxR8=8 zq}vqI6hBVFwjSiwgvI+S5gte<@HR}0LpySuJyg6UapzcD@&{dGP(@IrB6n2z(K9yc zV9W>n=xCTAq}4gHZ)kO$Br0nQ{(j+FCkrGbz5z(vcQ3+8f>u%CqU447Hf?C9n#OYf z?PU-AMj6d4Pf(K0fTPJZV}Y%VV!1LE%cX^~9NZ!fFU~mTdv?7v0*f8q`=ZW<IveWn zQf(tcPAj!uAT%$;s`HyDZe1xBkO1O05iR-mF_ruqm^>pB(tUkH?N`wk-*<KNEB-gK z2ne&|5J`Cfx@xfYq?#6y^U>TXF%RNn{46I8g3BQ{AkK+ch~=We+Tm>;-1|PE5!J3{ zgR8AU>#|%na4jwQ{Lk~gVN>sqwcb>gmt8+?J)<xO8pk!`G<T_V0Pc@U43Z;4uQEf; z4Mqi%xV{21MOWch02G9qF^G@7<Rezoft#Tl1R-de3cV3J?4{`T{^>~JP7p~Hia;rz zE!|y@_d@rQ*PCH$Zj$>{Uc*Oe6rs`}MW`-F!`3ucgC<v-<pNvNNf5i`c<x7EzT&(H zmw^b?pj;Q!!4uw%LL&OSp&PA_8fu)>+ECL&iRqc{YX1m?hl%MjMZFvY>Rg2xq6nw% zm|n8@j6ni-8bV6-2q_3VwID>2I|TuWek~te5Vrk?_~HM)d?1_dm1ZbD2`31`VXvR2 z5h%M69Mg2;yzsw}k1hyM5KsrmP?3O?xcLb3ng4Ie2LyUV^zX?B{8=f;2eKeO^@>T5 zPji3y^wLLiAYmAYQb9sWQ0^Z@6(IV&&%Y_3wdmhHx4<(<L|&exc<Up>9{Nja`WY0j zo!|wPMtrlL=`lzSLFIG|GsiYx-eZ8gEI;xyNrnPZPfM>p@`b($4f7cnWM;YMo=&~> z`K+R3pJI?h5hOB7aN4vP+IFTJ5)%aEZJwyOOQg6<t90utvfFVrK^#aNxQ^rMz}0Q} z7@a*Tw3C{H1^R%OkdpMSqFQ10O28lgin?h(PrvlM6_z}cIk9wkS#h2@PZe1<U6oOu zong*0TTC<5m64UIj2u%@zh~cG+f40eXB8Fa<*Ll3s;pvs$RG<S@BxRs5>ruW$#_+X zWqDdwL7r*4D$+86Z<w|)BQe7;UFAWdU!+f5FkionN~tW-s5K-n)9Du)67}?KNL`kg znle8<)hM(`*C*-?3-!zN3-u}UQIxJX>a`jAW%Cy37f=K3{Q25Mwhw`)-h(lNT`*sl zdS8lw4fGoqWU!tG^ruZoO`nf0lasY6x_<c4Z|XvQ`u+Vt0Zsj9=;tp>Sdiha5S~4~ zwYSgLrzaayv>2fwl|cCZda0%M$-vu3?|sQU8+hDwGo~ia?FZhzAm#QFp+944k|9xR zT(%$uU#IA8OBtfhXh@m6Y`!5`pSs|VTHx<jw=6X=aY1^zp2qF@-Bu5_q#5<|dBL~= z(o*l!r}u+TOG)eZ)ZV_J<l&Oil_jctOG%CyI&oViaP;X<L7rKard5^Z_bCuGrXp)b zWZ8ICSr+M{VM<J;syMG?Mx@1~g$qiuii-+Lpo0G$q(E?bQAvS`v^j6OYM~Z-S?0my zuFqRRAVMX&EK}VB-9`V<VuDOuUiL5RlT8KxvOcG@B)_NtSbn?v_Kfs|>8eCs!h`B1 z{Or4(I>?jijd>*n=0b2mHQUWr%J)s%KK2Ok{bn%W(z48jhFoefKdT&E=kuO^1pF3a zv1*<r&yqJktDrZp<wZ!3iMiRmM0lF=4CLEqVE*SBO7cq+Ecy9)<-L$Z;_TwA0=Hq& z;R`PF%d<*M{b6py<{t0-qUCw17W2ZaVoM(NP7MXr#G^9R9sTizZah3aDKJUsS^!Qh zFDfX4*0?R78$)@v-eb1<H*Zfjq$FibSE<tV^T>!zMDJ0Nmb+Am$!WJeCubz}f9@TH zHaDxxoL8<&&okwjCkQrB&@|zGTfHi`v;=zDPkil=LHVi-OIcZIxj8R4v9!coURpeV zRau@%m`NGt(lR#=z5xB0v&zlAMHzYKgwj&jcTzh*;5pq=QUVq4E$UB>XTA!^QR7DB z?ijS0R8h1l+@;DdEmsludxy&7F_mWns7z(b1s!H$W#w%E9(%CdRG>0r7}E(yKai~4 z+@k(=qC9VTX+>6XFFib;bMq>Sa<EpJR9R+~3CmNYWjg=AF9Uv*r-4^VdFBkPMqqss z)EzolWHPJr`NirsA*5KAXXllt=Ie{|mgkk2O;E4f=;hY~NLx2ug`vR~!4zQX5z#7h zftj?i7_+5!5WVpDx>Sn(wyt>o@Ooed`&Ggqfc7@}`|7hTST_02`jdol34){P<sQ&< zRbi>gEG*Hg89YPB^Js4?5{8w6FJR@Blv)Z3Rpq6n%T=W%svKeMhjCVwgRt9a{w@1# znh4otWf$iG<!{#0^jBr`bN@DGf&-vGt#Fm8C?~5}o10snXEO1MWb!oW9g=&{ee3^p zRgSq>h3|Q#EHD~-(e|$&8yRaF3wxb|Z+Zz5x_zx60e~gtW<%+8Re#5WSJ-}HP~8J0 zx!LO9K;miNHxEeJ`?c+*j2Cxbf24aPcF#iS)_Y#w2^N#eM1=iX-o5p?77~ssr##D4 zsLHi0FC$i9<tr`E%k3vJk3GpP%`=sZ#q1DFhbl=wUzMy~q?)fyrz~W?JgcOCLxJ3O zV`=HbJeio?V<W*%d?(Fh=DViHJ4V;uo}5+5FB7`FGE58TL#d@C7aC?RU&Rad_WJ&s zEOh9$!n9`co+41(_PZ0`ui)=Mxxkc%CAbgEo$cNAy{+&Jl!<U&=NCd73rmZ0`x=9i z-)R3U_+DfLUsRIokpVAK*h{RkGl+S2fqO-mw)f10Ub+|X3P`?abzW(H)ZJ8d*9>xU z`kA1VQWaLnf;=!2jO{0AH<$Rz2O}*@rmL1OFDm8j5jjZ378SqBlFQUzXoCNZS*YV} zXhIfdYI&a7QeI*rjl2`Q|9Ie+lv;{&NvoD;m8k@8j8*~aEPIuicaR|OqUDz59<ypJ zEug8sprpub5=_$_J@EQ1_#wTkTz^2_@~kN_2}M}Ycw>=wCzkt`7m+uRR$f|Bl#6wi zH-G*5$XHd9Q&?VFQnWgYmX&0fhT{G}h7v3h<(4w@Zvgg(^C)FM`A$~}JaqFfFLyln zf>nWcs`xF>c$Kcar~*`>+`X*#YwtF7x9y#1qM7<j8JnNk2Dla$L9ZioRd>!P-e?PR z2a6HleyyMQP=$y7-i*6o(uC*<lkT0M_LS!e1XsYmxz+bqhNnMyuy3X1p0<S2-9FMp zi?FH>%>-3x_CvIMmgm8r>~k8*D#&>DVG#6__j(}O^t=Loor4Ntmq{HbGo?&Elg{KZ zCgdzkIg^8YUl0wN%_eBIW|*vO3siEt>bF+Rz9FPB3z-Zi5tjjhszl%{!Dsr)krv~> zJmmUt)A%Ma6PRdRlbCyPr-&w%_WjD33Q(4Vs|fWbW)-gfF#Yq?$}>{lrNsvN;pwV= zOSGvh3w{nZ*TNK4<wDhYvs#o}sZvjHud&(+tSwMhC<i|c$GgkXYDMmZeih)~Z}fL3 z%WiX%egoc8;$Bp+=~_YVscL1BxllzU5e<F3rSS>!gec*ELyP-0%`GxLtXjFei2SO) zHn+nQS8m@SP#11%Grn)fgHpE#c6<A>ypmj6&9dBHxTiaT$CYJyIdH1`ItRVHeTJ*9 zDxk0kJxvf)dP1aW0;V#>HQ;#mN?n-XGa^mnRSSh$<a=wezc0(d&IP`U$8?s0CRI)$ zzC{W{#50c09neQg&80b|#eFlCxYySn`&>9&qzZ7A5N|2WQ}ONG3wdvkp8opa%ky&b zV1)Q4Sm7+iX4+%_9)d}#UQ~Sl>N}o2_zflS%8PPI8JEKer)7Xp{<j@?+V}D!r??bL zkeeaC4D8Nr1OUDW5h=GEq0l52el-<1<-ylCUY@~33$eyhg5`${zo&=WkPG}3@SAD2 z^dcXRr?TQzd`IJjkOLMK3*2fUFct}8UtX4z;d$y6n?SFnS6agGewUoyc099j^TjVs zn_@^DN!y#UBJ9)4sLVrm?)1gkmZIX^#iiMcvrEgf?wJ%5t)8ZyI$3zCSZ*rKH@iXb zY0*5a@Ap(riCHYz*~Kg2o|LXMEq*w!9QtQ6Te5G<qdI!>gd9(2peNEa9cD$?Y(x8? ze#>EtX|}<J{~!46e&Kecu{(ull{*b~r*d~{bf;gt+hx0-f4Kusqx<;<cRJIZ7Q55= z?sUrzp}!ICXU3g&xXatz>EGR{huZEr41IQjQO<RzI02*%#Wf6{hsC8HjnBp6%8bBY zBI081!XNkHl!Iy#KKqI*EgJcIag+epahyU0K1MSaaNBY9`(?P-mSn6iCAiYCQkCEF zVOh^NgvDrAkE;QTSOdLi!_QL}fV>$5E3rH&ALOg}+il<K?JNF&oKsZ`e7O_R?lPg= z;ZED!=>QL)p1At|{x3a4moaK{`uc;9v)byYfIqI?q56&}`QJ4E+|%*CqLSEjVU{kY zS(;r`;-0Da9&vW>sFldYF%180<cQ@Zd^vPOgN&yvLapZYMJ?@DHhB_LmWnSt;nO$* z9E4ql7hTk@h<u3%Xd9kUMqNx$t8?-T@G)dO(^=sO1^Gp}Xrfwy4=!Wyo^LLbKG6#q z;W=XU>eac~XvQ>{8Rm29zmjmHpc*vdrJCm(%$f$_^TrdhO~8uZCd>y3>fPU7CS6An zW1uKDRpS|dQ{<s_GL^NX48!r1MN?S^%KD8dhRSFje@Zk9<HU@@uSxjCGXHJn2sLIM zo>A5|0C4oZ<_I<42bGHlOBm50#%pi_!welD<fXL?<3NwGs2>{c&4^JS5R=RVh~_X} zWkQ_>d>I<)!`BTqk7WkOj9~_gMlpVALOlbTh7OeS^<L>>HQ|e*{(SM}Zs7Bc@nw9= zy~>6T6Q2EJ{22dod8VgdF+R5G54ez+ASR?-*&W#7f85t0FZ0gyO6$Yn#rRJ4DieqJ z@;F0s<xI$YZzd$hiwT*mEE7xJ<H6X(UT#_@i`9K&T8A;!_Kt~xOoxWa85uube<mQ^ zr^CC<i>HaF9XPyDN8{<m{epm-FTV}<FyIdD#SQj@U;c~_+7Fr^!VHQTzzo_I+^y^g zboiI~@qLgk4jt>y_Ys;qfC-%+%!Fe6p}Ru5gFBRE#2c`;kFVtdUwJu=_V6qCSNx)O zYHByc!}~zS_X#f^pN7iAeHl6EQpN-`$|wB0eLDm`Q@h*i0~uxd?e#og2Kq7*v>#}e zGXrzInStQ{z{x=!e1BdF$U2zu{YdC96a9&Y`19~X_WLnI%)ZPJeB69UjGP&gKJd6$ zA?IbO03HSKC?@-rp$VxIBgQv{q}lW&JbThUeZaVOK_9}Af}U{m046*)m<f+jGT~2z zb`R<ZIUej#ruFIq$r1gDyo$XPW(nh)D`sRdB1SBeqHKs4<@<Cc?AP-2=y$o8=cT}7 zZ(e@BwO$?4GD)Twg}wYA<<0BXfLcXaU}iv?zq^j)*Q-w$-_Vi1j34L^zU<cx4RpxL zq?wX5LDrQ7J5<aJ71j6g(G31U_lH3Dhk$28CJ*f3$LKZE%L_UwR$v}@sk{`xEe`eQ zmp}B&5BlZHgr*DnC3esM0STZ1y6W$x^coy9h#9==_W9SVhj#}6<|D7LnBh#A2)c9I zEEUELQ^*YE;g$~iZ{d3NQ^Cv8(+@9a99Wh!0!KwYGRB9IrFr;!=Q9saxBtPvn`;lg zfBu`5Nw3Fehuoi6_6*h#4e2;t^qc&`SNA+Kj$uArqWUQRA1+sMX@QEb_|NS>9QY3h z{=<R)aNs{2_zwsEf5ic8PyX}!zl8%zhKa;Qv4kk3e+Xij{m3%|ut&#5<!Yp@xT-9| z-hD&}d}`!XNTmZ2*TP|AB~ry8hB2aCi8K{gJMw8rBSR4%K%U}rMqIhbQ`&&fEu~cm zX*cp^D5o?E@w*Kur&wMgE-I&(-WhygNrn8^NS{^#KVY^XJv@?OHiOP~q<16c_X^RB z^eJ3lBTsR_2O{uoakQm);5l6JXiIUy;7Ea<5Ts9xWtgjg*^KlSHk6@&p}1iwu6UvW z>1XhT|BCX@k?ur1@f6A_t~ddI=GR00BK;26n<&44bTFbGw*XIZ$9=d$S0V<2^g1pY zmk|+=5z(L%FlwYJxYU55_~aT~G04;Y;|pBGw+^JUVuU=4^c!48z&Mc(y%#b<p5mG& zTw9T+_~y|m;4$(P=bRPCFpH7bAkD)?@-0OAHZG!PH_~wk0__LPc%)6ZUO}GHoS6(0 zR*AlmuEnK9z8dK>v%m+yY(<){0S}QcM7kXp;iR+!7xAzgX}%VDv@Jwx!?hXt4M;!1 zRgC;8q+_x7%S1j3=@DF&$hRZ?78mvFL@G-Z_#j6bi;McA^cP$@z$kUViz{T6kj~VD z7L;p{`m>M^@{}GiV7vs67;p+MHQ*`Tf-4?*O3&glB9C}8GZ0rM@)R##m4X!Gtwvge z*dL2LvmHFdMdSUS{-eM75&phb_(+p;Fev8#vhbKwUPga4)Ayk%mXm$|6+XI&ZwK%8 z><j5H%VdT)hb2}qiETcMzX%LC1Q^zD9~<4w+GOwH0bg(kc!vr(+21I^Um)YtIS$lr zqsQyVk=u$Koso{+(Z{j27Z4XM6XK1Y(to0iVyfNSJtfGaOo_7J`u%_P{l>uFlXV81 z<tW0tztwpR{uuzzdAr%=d3k+DO7QP6_SW2X)`SV_U;e8Tdf;3C`a4dF(IVjK4_FtJ z<YeU(=H>Pt+=RvHtxwCsNeF=;W_)icPMcVkdy4UX-h&ONA_Z8y?zW&FXPKDL-denn zFUohnM`Rf9-f~@Dwxu8qM>b%y7-nu>f~6p_FfZp}J?5?n=OXCL<^<C!6K3!H)Qq|5 zDaHwTmEhXPzRPf=3U4NtmoX*gQq#lbCB;I`U_Vsgyb5eE<w^+R0Y;ZC^g?*IGWtrb z`=otZX=(9&c=e7Wx=ahoNe2DPj*1PXbo#+kUXBA<I3kmS<95tB-(?l1k}@JYe;H`| zpZlYKxpF&9nF<$N0gAg<BkBE#kZVF&+8yN-Euy-@-zfKy{;Cj=B8JL0A*Xp0bDTPJ z|LFZdvBdL4(obemnc0kv(c+(`)M5q*&*_XFXGl_+1vs0Mh`i@lEWtv^pD|g6yB@W4 z(j=8hVU{6H0VWt}{N9&d{DtU1L=sPm-V{q1|C2IOlu`eEzjiUT$l%Yb(A&TQU@PKJ ztr+l>k2ix@D|r|-6N5<iG+Z(G$1l|=pUwoJEuD8+4)c`-_*I}L3r|o`{1#$dEAd>8 z=kbgRw3gt_BOD0)Dwwfo>*-4e2s-gnj=sx4OA&CE@NoTcs1Tv=f6|7irZ<<K_m?Ur zm&Z`SpSe-toyZi>dJm{A0wf$A{3av1+1Kwv9+J-4c>4SG*&GH~{LqG|H}mi%7*lb- zk^c619fo;`8I5*Ev?<_Q5U0w(BN}A^G>fMEugg@-pTVn1;Pf<T>ph@Y%|EIBB|m5K zG^V<7lO%~o4}a-Nkjn4TTEfdC4ed+8MGItM?l;2QW%<8D4)b9fMUY(o{_d2<ow86t zk{0l;7!=+Ep2RTGZjGD@F2O&-B?6a7<2)np(_)6<|G7cTxN<<0K;Mdy_SZEZX%l8J z!^}=iPM{gYVh&G$FR064L_^nnI)6+0HpKcv6O=M#<N5jo=@Vy|Y*pxHZ>6Djli!#z z8hKrWuX>{9ZKg$B?5DoRF=^Ay>+7SugCoTWOJ%1*GJ_wRV|_vTlz8M0@n*m7do*!- zBmA2DM~fxVF*AejnR|S3d!hWH^QNH83;SejtI`=18MUCgFm%U2>DbddgXe^{4BB|t ze)a1CA>+&Ix)+XjOmA#TsGnB-?8tgcgJ_12S65Kzx2jRH=nKyr_-=TTx;6N?vN^Tc zxjyswq8%?JHJB1a0bys`;#0a3K5FyNS)4k$eMQ*3f4tExPgoVZEyUY@ZQwS2%#=Tb zu0QwqzR>Vte|h||wv){R>eh%IZPlC3pK-=?xMtOS;deYr6IQp?rzy&@oyilw>C+?| zRUcK}@WK)8!{YX4pUB)-lr=9%{RT$Q*{|8LQ_5XhsNDIpcGKzjuuOd*7Z$SR=o=EP zp@U2Kn|SMETIEIGp99&b0#ik7_md4FXU;IQPVatvS>UP6?w93{gpD2UH`UAVcV((# zUBflirIIs;s!IoL&D{Kl!l=j8VgHymy7}C?_1~Nml{D?Xvd=1ytL#`J`OfQdM{}KB zY*Aj?pK|t(qgTIS`oy4WUQzkLwpPCc@sV)JL%Ma>nAGz=El<t}8&eT`^`FW?rtEI} zK9?(}=FI6zhx+{b8u@F=*Vlx;UY)`Hb%Xc4$_Mo)%|pZARX;Cj%84D=y7-Cl)lnUH z$zrUIFQ!*n)#;k>q<H_YrmD{e=G>ej?z+}I#xXH!M9o;o1TXK)Lpz4~v`!oLxvbfA zIZw60`_{8!$uHAnCgY`|njP;crPusVwRh*#-5VL&IxKu~Jafya&e^wZ*{L%JUhzLv zFu2{ee#ebXIh$RsM-(N#O^?k`%g0}Tv36#?`cEC=wN<f$Q@WW;+b&0)eYaub%&_wx zdapD&Mm%^bR{gSkqh#g7*}Jpsx2~NzDGk-Fx#v`~Y?8d<nOH+&UGDjN!ecwa7gfb} zTUw=oJ|o9^2mT|rswQjajDW=V!h57IiRRUK2X7v~X#bn%Mi@3J*M~-?>{RW$%M|}? zRYS|P&lf4i6gPClzFOJPF)eb2>Sx!0*fDh*;$>B}+To>NN=J;3e?p!W>@OSXbFO-F z!j#AEt9@C#F<5dm$vaT<{A&Nh-e;Tt*nG~O6<?tU4l8(7+OpO6`1-i7UL2v1|3i}h z^P<2H*T+0R+rQkmb+e<kHPzeuv5_s)d>o3&<F97U=-zVv(ko}Peq41_H6Z%xNvpT) z?}?kdWiz!yt53TgO^fY#J9y`|kA`i%IC$@KJ@fa~zNTyWuVUpN50q_Jh=N88`ZB5N znM=0jyGzC<G)<KLIe`iB_w}|)E_KRH{w-luRc)DrM})7f(oNnr>C1o-yElZiG|im6 z>$&C|UV%RZwN^^I?{Uf^Ur1i^oLt<lJ#)-!VnbE>sf@69r#AUT{xas=xEC)~rR>w* z2<to&9DetRc9&~pwzn+j*^6n#yT|@~xOL18<AKK(AB_Fe^}D^C&t#SiQiQxVeqz+I zsTb2v4cmNi^@u-uAIq2Jbf3B+x@8p&V}>30tUlGdO7$AEjr0C?*sI>wY1W}<ijFw; zuDfJ9^6t7)-)-NTe12Hyb*_qec*1(+k7e6NobKH4#ozob)4Nmtd?8kR@h0<=zi8cE zk4RrGlaCnto@>?vj|`~&?z?WoKUxYOni>7xq8nSfi?3|yj_+<$PK=Fzd;QSUGr~0^ z*1Z<H_njfJ2Y<ppMHq9SZ1p>;O|R#qN_*aAKHsx1%z7gA=I)N+C5)fkr)uxzRkr7! zsM^pR)T2Ca9T2;B&h%LAEmwHX8GYROggr9H=<a7eNxbZ=ZoU7@oswlQp4gH4&P;vg zY_FH5?o9G}?Dfjr4sPn?t6SnHx7!DQ*;D<v?a~RCtH<%E`RnuR25(;P75-e6|J4=4 zrq1jzUF`003@)j1e(=(3vB#pf?H{mEdCYm@L_zLXUOnB_$5wru6x^Za8uq?C>}#jP z<q98uYe2)sK*yP@S2xw{nRQ@V^~siviqO-s(;0i$e@*;+=5!`?c#9;j>CBGQZ)Q&K z2#mz1^R1G=nO@$97g-ZGwm2phUV7}D&xhyaU9XEqS8fcbDp(WN<bD1~NnMBc)xZ8= z-Lvs#?1rhUFZ}trk^86lS6o^3PEJx@%W&^+%^fScTeKZJV_$wF_SLAdD;DiNe<R|8 z_I>{wG0u_i`N>w_{8pOsfq4LX;ivmoWu=Xsa-~OeY`wE+*6^QQ8?V0J{PgJOzdo%! zxTz{+rfBPlRqJ!!Ui19ZD^5<oD?H+C_~wnp{#$mesJ-OfxVNx5ZfADKwxRamgH5Tv zlSa>+baNf|+>g(^Y4P74dr^EU<F(hs>XfY`e7<EaNd7)?+qPqeLa)ka1hwCD^6jMd z6PuF{4bGk!cW#%V=|osi$poLOXRlP=2x?o|Ftz-vDC6;;dt$zAt@~7388_B>wXDv2 z$cy*NMfz0(8-kjnCJl@GqhHLNe|&O9eIj9s_vj6k8$;)fdUKY%Jg0jPbAHA6tGoA| z7_jw|xy@IqUz<8}Wz)|6F4w>gS<8CAZYJDoY?+r{`M0e`=ON!mvtzqoez-!Zz9k9x zf$iRU>}YV$8?R3scr5rruK4Jr?w}!`4ybzd$NKd_i#|VAZ<k1?#)n_pux9Pd*$*ur z`f6(O?s27Wo~-<A>bEy?HcmFiz8UBW+#NbR)-g%``QP@f^c^j`Y8kbEm@MvrD^JR1 zC3j35y4%MwU36~6FRlXXyP=gSGuwZ9@jL7Kq}my^nI-2hY<2|xazUCMddz=TWcSwj zRf+i$$5CnYm<u=RmKZi1-c+@=Ic%x=lT{V<&B>yFmYuR3Zk_k}pU#Lxrp4WttV_B_ zG*q3;ijEa0ez3IX6tnKhfw3urI(9$a6Vfs6$b*mjjCpbMlZl}>8}|Kh*UM*2dlVTd zl2@ajJQg;h!R3;iX)0C~RBrM*>peJ8cE$3A^k~2jk&jegZNL7GV`ARA)Q-)gUvv%f zX`4EG_4Z4mvCYv(M~B39M8xiumd3X3e=K&S{cvVL&?o1I=Y*aP-C+;<EVS7k^hM|y z`>-zuhv$TTsXSv3`Vx=5w}7GaxFX;G%axL*#Vf|UUXnd~WZ<lU9c9}_1zg^6FfdGg zRO#P&;H#BBYj-*7Z^V6<?s!eUHzYIAZ%ovPw3Xrs!)D)?zLB}&nw9<f!!N!3^^jE+ zilmt}m2;BvYS-I#zczI5D>okc@#@(erL{mgF=Ac8$;!)T3qroya%xZg7mNIFq-4Js zbxb=Z_S>Z3uM8V*eg4gJjj2Os`i7qQrZZMO;CkV{{Z)$BZ?4*Ybkg3JboGzM#0OX0 zm$iQU#^{8wbr+tjPJMNR*SAN-IcqOux2|jRIwQKN{Pwv)?+(v=ET%Z!>k+TBvFnxR z?yY-1^VF&uZFs==+WXdj@(1OmTJLEA$Ig5>Zo{ScZClRQKT~^Aaw;c$?d}mrMoXWY z0Nb(J|MJ<qQ@gf1?`_|*?Fntwq-@g_=NjJ@`*Cl-DGS$qJFK?g`^VpVe^p54jKNKD z)>6?n>uce+bPn%K%`xln*WweVq^vDgf0xxgw>s&=qxao<%kgsS@ze9poj7xU#~&q< zqK~h<&`><3_573K{P7okE(F(y`{v6pjyhQN<=N<N<CisGMpxaN^NC;J&A`Uz{5IU> zcqU-&*|Fcp$=($W`+i7m@|moYE4@?qjb%P}xulLU=YlF<-4Z>lq-o1tJvYxri{o~x ztHS>@X|T+iw0g~@`gOyfTWUJ>ht{;22b#Bp468m<Ri0T^I%;Or<QLZunv$mY;`)lU zGcunZyzxZSUUTR(!$X>q7TnSed23?Kg~sZ4H)IBeUubU}9@IT!`i<2sV><2<?R2>| zJL<lf^xpJg_CJQ7{;Olnwo9E2$Nc=?sFhtBf9;iqxaZ5#6fI9)&0077q4;n`_?9IF zr;PDa*3MFY`I%STTR&Cx{L(pB9C&2()xTDsF3bEv`=!zUitEup(K<<O{ndvKcuC(Y znYJx(mjC-pGgDtGK6v4!$KDOTG^8gYcYD&P*!7D;H*FhRvcb_=)x55LhOtR9@!O{^ zS|_}FZI|TfV-2@fe?40ET=X^9tb)&NLmxSI^-}1p<nG}|dOAhM(T<eMwnO)g`O3Gd zHg~FIt!ba5<yqgjO-+AZ_4VHRns2<?&L2KHc}qs<u*Wt&c)__YuI0?J#tT)BlHiF? zDY%Klw!9m#VczADtJPbMc>neO*AtnwzEw+SzSOkm2Gf=qKA_5Yex-bEY@W+?Wb_Z0 zn~RsMwY+>T`l_-j=f`5l<i!;abz8hmmh6?&B{yC=Jhr7IWbf%4+3WWNRff#Ge0kna z>D7BGn&Tw9xG~QUd-t*IbzT>Zhu1&y?Ci=PDo?bGh^o4jGgz{$C)3{={#1NG#iq08 zL??IbFC3w0|7p;3fq&gOXziR+kA!@{^qe|YDw+Ag8$)im9-ZI1;Xv8Nofii^zQ(%t z>&LFAbpBMU*j}u@5MrHX=r+ANX-!%8iXc<7_p!XKLH^<$elt@78v<wVKKJ9?mI2=3 z4YKE3I}e40RgVbi+2`+jPiI(4{?Yue4}Gw=L-XbFQ%#OBS&O#&@9`B~*<tEv4f$%< znoskhQUhj&t!fbeGi|&q?A|Er-q(6uk1nZXFD*E5KI<>BBjwF|l`r~=($rC5{(<`k zXSLVt^B%tT#E-8#o*uMz>6C33V?zB~ZqCl#I@LOL#Pf4{f*b`q?|Y(QN~GqK+$iy1 z$@MxW8BssB{{4Bce<q9D*7M@IlLJ5aEcDygeY`fWzjju6`o3}P<JB(@nWFoAd#3S< z-9G%Ss##f2R{3SO4$ay6Q8edrt)Fn~!?QJK|NPCLzIptc%AOZb{e19~8!IEGR8AQe zYHi)7=omhl_iyH0I#l<3QdOoSXi-OyEN0MSQQNmnf6Q^BdR|h<+R>N4s9oZ}ZDpDC zF?GcB=^Z<=%wM}^O>fc<9sa<<>b8MT{qgho$=!0bdU8``=#Z#$cK;b;Hu^_zx-T$1 zdDKq{7p+ev9XRM+HT&yJH4{JYIsWuU>BWeP6SwU@@@%PL=DZs-55Ls9&KsYP?atg1 zo5R@e*Iixnu`0>?)(=teUDiMOd4}ZMeFM%jDT-M$GrvCTWMYmsiV{b(?UbDQ#nmFY zvg$yKWEs4h17X9Dzx>B5m1RH9V`5LoZe|`o^73o5OP5{L@3r38>+H;0=Urv?S8SfN ze*NBvv6XF=i>?H1`(n8L*5C1uE-aB;3$59>agzA;pS*5O62lD&s(Srk!-(}Y&-<Pm z-fDWUHRzu?lG(EldR=nOnzAKnvE-cB`F9^C56O{LXPtcT${X73y47R4BlmqG+IUX6 z^!VxVYo6?weeB}bwcn3!@UES<t5S8#x7^ozMS8)CMe~k+v%6~3wL||bFD?A~rLLWC zD!v~fn!O|W`Jwg)?v2XWX|z7D=I(p*FTB)K)+*k*^ZhdW=aaX+Cw{$BS#$rv#1zeA zz6qoMrd18Pem1xvvG(}!V}3QUN1u>o*KA~JVrR+&UY6GRJR09sso5qKugFvmJ?JYR z@Ml?_-g`&w(|;K`ZNpHtZ$ir{QP$9(HAd!zI@SE|lVU;*0SN<cX|2jXT>MrQF-x9& z^|G?z#|I=?54>5^v{@BnovM5(^>bHv-8Wu4o@uX9fAbyl=Xaaa@b6pq54%yA;Xl9s zQyeH0&Uf$L?$~~6NB54N9n1?%HQtM%KjrrSQ=R??@Bb7IXqJHzv6xZUE}P)xbN4!S z-Sq&Gw|IN)vTzg)6N^NX{M0^PvT=bD@lY9~HhTGv^Abr#wNu3+>GqlGc=h0ZB?`5V z1n&_3S9{+9*3`DGOGxOwN+<LVE1?Sr2uKq|K#*?eAiX0f5Q<c3iim<By@@EI(o{N1 zktR}Aq*xFHK@<euN+_b+z4tr!zVptx_d9-{nz^#p%*@Id{~Z5l3w8rH6bIngcO8tD zT%spPwQ$rn=X*g$1fqgxFl%@^8_lQ<prP~NU*l{jE(8~s@?7eN_25=rllR&g{sZ{m z<p8q1RYCB;&ku{kt_OMohEIcQs*IomXz;-SAq}Z9Sf~b8S;19M9(D*?fEsg{fQCfX z*UK8^=H~<!v?3_L*}$X3H28*&D1RS21S`OdIY2@~yL|wzV(0ZE_Jm*uSTS!yY3O&} z#xzC&E7Dv&Fh!0k$^hE|G6WKVL?Qs}AJYS5NC1Hp2aw{DvXZi<faSi$o-o+`IAR)F zgFvtyfRCLGSmKQdTVYG)z)3{6KaOwjZOjM4!K%0IPc*Re!ZhOW20t2XyP%E)&=Ai3 zHwc1<14Tn9z#kJrjEjaqa4O60Xc}r?W}zm2!4sIwX)NeIcAvLd#rt&QLs31`b=pS1 z3n~yEm8zUcwdJ?>f|`6fIftq);^408e0*4SPnG+0mzA1auWGjnwf6yZoKuz1(<^Lc zUd+5Yp6r!~@%7^<T!Vs=6-{Bh0JVIvnC{stvzzgg#)=B9DPvk+gT$u-iPt~*Mk`<2 zYJncVQZnRnJ=N*Foj`bG<0;za_6|APkw;;l*vWh*QaDSWtgib6^UbVkF^3d3Wr^I3 zeP1wB!bg7=wVLipRX3@Zqdjf0HcehAe7(dD-v1))ZF_LOu7>Is<D@D<3dxXTM5u*x zfUR2BsUznMAJYW41TJ<w_QVCH5t19W2869*e@a$z7(I;EDr3dXytC6MV_^L3g5v1h z(NNKG0Etfs3N{`d0R#f$1-Jpu-7x@(qIdN1@i-_Z=5FKdA?kw_9Z|5_5i4V?G!V!Z zj1a&F|HFmg0E*yG$jT0r0i*zlY-BbNCAxF5jhD;5YsI!DYQI3KD2syUVC9*W2Sy4I z@2&t!2#{lDrNDd=4Ac{RfFCoaf^h)sd7um<7(p$>3MwULLae-qAOQ&(5yXD+0QCnN z7KQ^Qx|$fc5Mlz9EI8g>dqc!pdkl?lA#GKlf1N(zJxdCas@sa|hm*m;+iy+@T;3$q z?&`%iWvRXjR$jfu)O6v4Ng7UhH>u9;XNUqqbHOv;C(p1NA1$(Dco(Q-&6DQV)+X&7 z&OvxV`Th8?u_CL#=6OMSe9HnEXHM$e<BZ|wssS`C7u5K;9gQdNS(a(vd?@J#d*PhU z%%u0rivQ_4w5`JcLgptOM#}E$u=PKr!xEq%VSfpsztdp@OdBmPj2=t;tsdX7A(2(R zU&~dfs##0EmGcQjY>;{@eY9MdLaW7XD@xwr1O;6;Oi^f~I4y0s0ToDM)^Rk-Z~B#r zHs_`JMj@5Q*IzhRT~InUA1U*Is)cLG_T(ozq`R(h&dumN+#x}>F74OjHiHLZkY=|m zub38P^6QZwm`@u;$*CP@?V&azNm2h^dh?6Df_j;U*KEqHRTt%h=5X7KjP*RBuiy4@ z=0B^!J(J^~YG&6of54|sIZAMh@MwHWe6mQEzbd=q^CBPrQF1e6oKc*3{jg2DX6C_` zmiOW$eT%uw%W)0EcZ}oYCSZkUIB#>`6m9cpY0wSlC0r+|FFtmK@HHjBS4r2l9-|?~ z;GHr6NHM~XRXbb&2cXA{crdE*7b-&#3V83T6R6-0fE^&XI|db;ZAV9ayhX4&?(Yre z5o{wgR?(&R&c%%q`f)DG2_^c=sbW6f-Xb<A5eKlW*Uk%&$1IK=CJBfGh-|TJk^NU= z0O2pL2BF~pR?Ct5!0kw@Q!cp{E!*JH5q+Bjik4D!WHSIeW*J;C27o^F_vrWYE(>lV z5S#$Hs$@Z&qn{G30qKC;2tERLhT9W7W7z{2p(RneJl8Xnpe*CvZvvlkIO2~~Bdc5= zE(`P8h)-$Sx{}Q_Igqd7<w?2($I!RFyZ!R^yK#7{Cq>sKpRsE5iF#>vm4KOmyIuS+ zcy7s}c5}2qGI?k()%S68KeuC+<Z4Hh-N<gem&vF+I6~w}`JE-ODm19fX?f#XH7UK& zVu%@$!d0_L`X@ADt~jDHWJLmr!mn>Hn-)VV=&lKiX)=#;);4VhTs+<sRf&$a_@q#E zJSR|GNHpld7@Ua4^Mf;^S<l;iz*sm&Q}IDhNXNrNoQ^gI8TC#+Wi71oAt|q#>R4XD zd;v5bE(lZacVNl{Mr6E1r~(M(3+8+&?Oym5royUz#)$&}9eC40TZ0_zQ{aSAc44@t zn~mtMQ9(jOzxxK<(90L>mZfXw<L~Zu8X*VBVy+~iK^oe5xq4%|2Y|UXnATeu$@2#H zA%A~=Om74yZ*W$Oe2RH_py2#A0tf&h3J7B^hSKot&935t0=qtmgQkWpXiI>XV2l^g zP62i;7%2n-K<=R*LKb7t?4qCT&)<p$pu$W~NJ^{%rX+z~2;7`d2x@>5bC7_PNDt-W z4p(;Zao@Geh)GF}bo^0Xb~|5w$6SN{eE4ti@8yk#!^yRC)45QV0+Lnkr5P(C=Eybr zyt2~7dVRpG$T&*><NL5iblqE*(^~q`BXc~@tu9x+X+Rv$Yj~$?n-czQcE-~qY$7t5 z0v}owgIILRB-TToY0l=PRB;w*hZ$YW2xGme*igCYfYh9N!6>q{QZ5!)dLzhpfR`p8 z(gpbdREkEIe!7r)=32;H_gmEkw&80eLfk1rE5o-gG8=R#zu+|4QcGIX{A#Q^Guk4R z>)&M1(QIf*`Eri-`(f0{Fzfa4fQWK)g5eU|#Kjj`BMY@|V~<b6%gK-1d|}h*o0NzS z&}bi>%DN`ib8seNQr0r*o8IEJ{6y_4wxWwvyZC1T-YFB%tj6FU3<G{Y*##iT31Qx1 zhk@n`0MBO2W{G0jK`U>245HasW5op=U@Q$3=43d`6gUYO;p01mk%D$Ta1`L$1v^{_ z{efMPv)y;OjfXeDvv(3Mz)1Sr(?Fs8oiPIijCZdEkysnh1kq{7^a7EX=I8a=V&Ru} z?*!bx_(DEE!CcRk;`c$k%i0|m5{EwF@h-gSE6m&nljp^V?)k-ac&4d}D#q|oT7IMy zD*8pGu_%}-{2XP|R9neJA|NATi1PK6tdo2~HCWw$MB1exe(H%t5fT_(T%cmNxcC4W zk|8#4Um3N<gWG7XW1jgvN#IdWtU=~sw?YN7XrUyfAl~!xJSBCni!5K^8BGx!8X{~T z4D6Qj%~{OK@pK4^yI_2;TH#|6g~^M$weq9G@tTxT?tJzVuSn?=RN1E)FHJ2QZbd}B zwKZ+H@3N^`nsltQvp(p2-92=QvgxRstAq!>vBsir>e!~%fU3FX)l*B(#VTm4CGz&G zzG?!A_ifjUd2$tUv=|NI7SiSKoER%@KRf4C*5KR6x+0xbP#B)b%PZY1Okv{G&5YCE zztP3_;W~(d86XOxw^5K7#h5yUrLHW$+$<pE_dr2IYz#I~0};o^-3>vBl~<6J5$N05 zy1KjBBG>>H40I6F(CIkYc)4SmY5YWq1OPtF`%oJA{`Wx~!=T%4jv!ng?6`>KVjzxz z*d>dUL`Y+}*v?QKGX%n({Jg^dIzS%;WH5^&p+SI{3nD2Xg<Z}Mj0cUnBVdCTl&_DY zyBC<aX^RDLcQ=<ngc_i-{e|TJfX{lq)-Fyqa4^Bq6$Li=`H70cmAB{e3B(wTl79=B z72Je`0%s(%PLY<(YRl=41=q=|vD6B^+7!V3W=xoC8%f8vvRK}Zh_T>&D_Bx+_)g;2 z2Pf`T-qXIbnRt%Ccy2*u)ZUs7)pLq0@5RK|S54N03WhWvG@qClM)FPzB{E<Cu#%J9 zD6aWJ#l~4*yD#yr$8nwB7^k2%k1uz|sC`C*EVPaK(;m|K=iFMp!LwAslH$NHy^IfA zNO~}ZGc|tjF5?z%&9eObtGEx+adXQ<WlLW^DO8O6CW|w&vM4?E;b!DW9j4tZ6Vz`@ z9E533CD^D(%R7ZX-Bhm^I(Mw)(oOQrfnJqI4&HBQ3C~R{UiZE)PTXUmC~-b5cHZDE z58V#&dkTcLV|OeN=7<51q~XIr#KQr=U0}z8!~>w9=?IWu4p3mpRn$I|2<f*gY#g7f z{R9wwR*_x9tq@0u|AaEu>%<&pAu4WEBSeOJr&s#YJQuTo6PU@^VY(oeYGrF=ABj@k z0X2-p=ejHBSS0p1?IZ>s6$9H5+4|b}c#G|cIcB%U3OPo&v2gvXjRvyKFhMkZyo7_$ zH24JYp!gfPda=tVq88cWFWfvk4`uB3y2PMSZcViT-%wP?hnP;ja*wAI)|GeGb0Lw+ z?Kwks?Qos>SH{6pZ=7Z(xs)B^9mD;E5>s;p?}jv(tQU9O=1qKZ<1?B${ZR5HzROXl zc{2-w(_K+TlXjs+lXR1}8@TYCYe+w<r|OjD#S;X|K0;9+#3PHzox5%v6R|$`Bv{>^ zHu*(01*65xeXX)O^>$B{v>7Bkf6PB>L;g*i=V;JX==Xc9NZSwe`q?VEYog^QDZ0UJ zOX7z$WR4#UmT6<==HypmV>-fqV3c9?6Q88ZeVgH~2euYM`IZZ<0#!?GUpYEqHO3o| z>e5z+Z?pYZLY7xJ*SuCXwhpo!KA7_@lp%jUfn~rxOAWGFGQYKcAQ4zy>=J0noVz=y ziK7sC|7mUNK|W{AN5D<{j#Vyxq~Eu^aHDpOZqu}q>`v0=kg5xpD_q|g@-<zLdh~qC zoA5H;=rcoWOQ=TU;PsF~`}k*>w~h_;r(0>C=?J+kp#O|Iw2i{Bn$4lBHQR*>8qu%e zJ1e+(OU{k>V8Lm>D|oq!{*EYdT6O&I1|j<ODYWKV+|Ca16$wct&%Ok5FsJecH+8es zN6Vz#RBL5)*3`R$_w}ZUbgVNE^kSt+nBLo(@X&i)lFNP%99T^l%HatY0W>}_NT-)} zY*c-m=@K5J?@Uo2(>eyU#oK<x=>Js^h~e#EfC$*n+rgpquI2ha)FA&mm);7Owq^cM zY%TKLfbr_WR2%QPa>n${qRg`wzet^?9P$kVLST*|PlbHhx>yP0UpieZl+L{t6#DAQ z>v?G%t8O{k`KW<sD}0i5JR~xO@iv(UatZTGZ>@JZTceK-a*Qw!FTRP8ixoT;X_f>z z)=}t38Nvq*Zn$~w?Bnrocy-Jg*PLb_<;lw{;~aXKHr+CH(ewEUNE;5XmXiG49NuEm zLf6m>NjyP44;5KHh^4JfpZ9OQYjpdJoFQPq{L)%ti>-IrG?hPc`se}Ob7mWPsaHZ? zb}i)jE-cV}QJ?+F==DNWf~laY@aVbv+GC^&#*4u70;vnnL;Z#rMf1DPNr=Skn!=ml z9=P^f51a=Ee&H}D0A%_2B&VV@AW>h{AOr`XfDy>3cT7#twq@D1ZT~>0Fb3|PdjJ;~ zU}O68b1_!#PgXeSopas&6bLB{1z6=+WSapdfH81_o(SCA9qhy$oa{mOI6FYF<5-8$ zCQkP=MU^F7INj6}>O7#qARNRf3h>Z#?d%+2+XeTZDW9J=_84X+iS7MH1P}rQviY)k zqIR#s>^FY<d7_66N?gQ261$y9>@5-k;|D;O=k6G^!Wnix7V8YzKg0Ik$OEGXXm?MC z;_khI{<+6u*V@t1)7BJ0ipcESWPfX^@Ap{zb{GD2Hv~NvMw~MedCjP&8V?}lMn$$1 z!|}<@wVkBX<!7=vLtd)OXo<M=hIlds&v_JAemXWqL29r@XSwN<uE`x4#><jStA6XK zO?+*R#6YXsTe3)AZ|Jk)cw5&o&y*Q8yi;^0$7R2tq&>YkkoPh0G$E<7*Q_)rK2ZbL zJ4MwDws)w^&dMb7?+SehWrr2)qz0-Kl=u^`Quc81c51_)Cb3(@AC-6_|6*GDxqCjm zUg87q@J+JWmW<V$Dvo4v_o%lgYk7iw$WLziGTSJ2BZ99cGN)y=82Ck)T`NysEBlyQ zW6{oG(1<p$sh}p)J5R-|V5pxKD|}5Mg^4f&PqZyHUn996fmU+{&}#O$5C{;~0sPMd zKwQc{cv^mhpR&VZ0mi+65D^qXx-a+y1G&VH;1mgh9CQ@^7}J5gb~nC71VeyffH2|X zpC9sng)+sS5AJ;*R<)9g_zJ51k4s1qEC5puL+GyiABz*N7;gsFcH6(Zf{_;(8iF$- zq&Av47CU5;4~fkxUOZ#li`?Y6<fdX^W@V6+74rFWDETWF!O!hS=ZEfC4guBe9`~JG z#IKi&ymGHVi!C0etaq4gAdY-4<VfjOF8iXJllW!~lYCM6E%(kqY4)o`r<+h6lCzd6 zi(7BTH<p@l9<2-T3vNB9^@2V~YkgX<ZV_p#cnT6-Uf|nOQT|ZrUZXupp^lf!7Ec8I z9AOv((;)@+<Io@)WIgG8u`qj_F^#)yLSjcw?8AO_6JFeo2z8m35km755RFcN#_?F| zm7eb=BEe?)o*CJV-18oRU!lr6<^$oS3fIimV#O&wahiPWm@TiuKOGeLF{7~9bT9{f z_$`3OCHQft@eydqR}iX~F+%C}&vxeHQvM;Duupg_0EQn6gCa=w1_TgLboPV;4?zK% z7P6pWAr1hNNC}WT{wg}x#&0&938W0J6}rl`-jRVQmgTAcjW`Iz;GGV5r7`9l%t6eb zD@(XC&kG+8f(IHe*^%q4Mbgjpl4cWsXng0o&_h-wM5uG3q}T=L#Eo0&AHPq!%cXw1 zMXX^w_&S^8ar3JWe#LDTL*BM{sY9?8V%Ag94v<?C$MlTh$q@Cvk7+#}hDr~_HRzQx zBH(x3-ZuFrp%1pB+(|T-L?#M^DKx{4N)-iQZ(p@_w;o#3G`)Ib+D_A<p32Qr<H4n` zUq7;SJ%%n{nMi0Qno}ojK`R_OGcYlBwHrEQA1fSiIw#1%Dg4UO?*ZJ3>E>?!)?yvz zZ;Qkp#OZw2l1V{|dy6$bs!4P2SiLqr%iBqPQKHG^iAGGI6yhO(RvP}HPcR4qt<(Xa z+2!``H73x^E({*x2gL&$o+ZNU*LF}CE~rs}`Hzbsuxt#U`qN-DHqf>2S{VFfWB8Sd zdbmj4IVN2`m-&($RmNLia^Fo3vK>kmX(rytMpHZmZRbwVyHT}cKU<Uxuskl-BeD2+ zBT!=5`lsMC3AmnLd>lK`<39`M?nIA4NAHhN&`*xu-^6=&eYFM}%1BA6pIx>)@n#u7 z1|*@_KifVIS;W7YxAx{8eD)HrqI;SNwSy=^cyhflZUqyy4_nkr6`mR;x>lHcv3yKp zL%SihG3AYug>q{zzlK|LDcN|Fo4pyG%!zgxF+EpiK5YGbsP?1M$7c>CEF6ZWd3SxA zO+9L<14PcdG<y#_I@g!Ic`Q?ss(9N=fK+wMyev`b8Z_LPWy)%VxAaauUH(PeTP#yq z@R^$0PsAD@bG9C&)EWB^_K_-^e2KTzvm=KI^@%|*+-IaeWY%~cA&5{<$?-C6u+Y=E zIv_K}mYq+a&c`wvQ6utxc6i9%SE;8F_~J>{Bi05t%9~4|3NPa)KUR55i|!tEvx^*o zT<(>**doQYYJ2*NdnW3C!&~dO&)w?n_wVnm-Cplscx(Ugg?{$drpXz63KYLwaW;HG zRH*aGy(l&>`+X+jVBvtCU=poB6p2GU&FPoYMRY`i`|%c3POP^!EeJ6oc*tzax|c+S zoJqLO{f_QTzXaFEj#&5EeiZod;b-=(GM{xP)J*FHm7x}@nH#wFuLVO^Mck!$5Htyo zNsc%uXSICVIzoRzjq`^4C)|7W!qEi~cJ8OoWzPqnpB&}Q6;wg!-|76~`9@$S+X9HH zrrb)7tjVn=7I?ItjEBT$Vvlu5LBC5%bM+Gt$TL$Xc+7oSN5!bR{Y20FSE`QTDwM{% zI))79S=~(8Nb6uKo%Dfw%8jrQHF-M?@eS^ZtPepmnw~t!;@JUU2#5BYnmdowME3HA z!fET^!txF#Qc`@zLgz8LCwF+Kl=#2i%r-h1Bcj5i<J#Td&rAk-YfV5*Q{KTdg@B=N zewwFlqs}gWVtW!L_{%N(-{lg;_yfejY@BUq1TfeL4uQQm{=Xc&|Bl&T4rjxmuMn>6 zBWxaWOg(;z&GXBode!Jj9WHC<s)we`$5(U782lY;Ur3dTap;X?eV(=A(<N!V+)m_? zT0CM-rkh8WoLx0X+nwxXDB7Jns`^f!Sy5U3iPlufqqnl92Av$wI(tH{&NOzObVk$7 zD@gUQv|J|OzOWe7fDln|8@qeu@O5p}B`X$>`2%N1<EBwD0d0=V6;uIC={K8Cs2dF5 zK|aqRKcdufIVHB&pzw-Wq@;2G`&4TboVX3}=cdxJ4zI2WazrEM?Vh~+Hkfc*ax7lR z-Pz0hRK&F<(u<aljP!9dI{UxopPdjJqM4E@iWod{pC+o2IrF`#%=p#OU1lE+nu{TT z*e<ICgG5{yvb;OqA0$HlfXUyw0$jFT0d{~580`AV6`&971(iToz;2WbW8q^0|DdxS zYvN-Z0Y6*@xBv^&pPh+u0|47R9E@22JlR~?98v5$G};^SW1V9AIXEW049xZUGdBIJ zUqJwKIu#6z?d5@i69f}z&tiikRJ#TShFl{=B&DSHVdrn5^&g?vyWmO_$;9SVEWMgt zpI32?6=krQ8E;8i;L8?pfNOkXjtP1~cHQ$*o1JOl1)xvW6gBGMKEi?5DC%9Kx!9Y! zY0=ST34IllrRc*pv?>!<{Jf`DQcV10QHoJ-yN(=sCNP5jJO8ap_0Oy~4$Yq*E(pmp z%2zCp5EYmRt}80ZRe(X>*QDMc*W(m8aP<7tK`N`~c|0*!@lK0XbwG~t1(B37;&Eo5 zqEj5{Sl2n4xbEfPzrlC0twxeOT5~gDgTF$Hx}NOLWxNShQo1!MA?Vm+J;-7Xl?Cne zWIdm@VS`YFB}GA#4<7Nt<)#?UN_PLx^<63hi${wU@5V(`*e*Sz<@ir149p$)C0F4; zp|Jmi!glhC{u2uOPblnP7z#t6$=?8I@|RfWXX?M4s^e1r&Y7^!L6IF+3`VAjcQWpY z7-0x7u@~}R_ip|pj!UsU2Mp*5<%Gd4Y}Lu8GE8okZ)FvvlAICpaGpIMyuNkMiv7~6 zf9rzke%`R-=`=6oe|@C}pBA{1Srl`p$GemZ;_pa?JeYXnI2lyl>acMYPO?e_JUrX_ z4F|wgG!m^D-S~QmFIX^mY^tLY?fc>LjBpv6FEDAK={{oZU|&Q|1A_rP^Xxe<3xM!k z+<gfVeWtOy#Eu`y@A*e%Xg5fp1^rHHd5R>|oT%zs$D2u#HJETXaf+IH+NSzJ(zDB4 z89mqf1P;&~KbqZGFq7(6Bxg6H)!j*nu(R=W&Mxt3L%c!UoHUs@u_e+b<``XN(`OWQ z_?=w#)Sb*>IhR^h@1Bkp6CaKyGvH|dW+9hCA?gg_>syR^+KD4m!V&j~wz?qI+zDUA z<89zMmyF+2<)BG@05qw`o`(tw1>IYur@&+8dv_P(-Xhfj4{72;pnr64g;n5_?Ee0g zpo1&yIxOrO5OxKW1AHQEl|?oZ7YZiofw7K#k^_UgbaA@!y_KFIfeyZ_d*7<b9*n+b z$9rY?zVMOD17Ds_<I*10&s=ME*PwW!lR}Z><uDjsbx@9h0!;eb@7Bx~%L8LZh}|3; zQn<Q{yEV!Mlg9!#G*HGqWJuZ859MY9X0TxD4YJXM{UE&rGq|By+Z2<Ow6Mp5fPlL! zW;WyCelfwg%gpvA+#FWhO;RVOq1^X5Zhj)(C@}v}Y}=CM-}l!3+;}*);$g=thAn-- z&^;LdO!oQV3X=uI5i;BS&-5SqULfK4>pF^iZ7H$*FFq$f)g!a`AfWmfZIb`Qw!1$3 z?(yl1oo=BS2VBOstY@TDEFq1gwh2%Cv%XmPt39x6=l-B9O=T}#*52iFwe!xx(a8|z zZtsSUv`k61;8nv_m$_`}=Qpop+B64PnG}n1t~BwzxNW9p!<V!bU#U=B^_l$9BHRu? zCKJk%drMzAQsP71({95r)SGu8F<J#{hAr&t{Vb2(JiPoy@fq_sr8efl54^!DK%2)> z098q(Zaz5~j@Cgc|J)~BkkG95R3nQcov|<aN{AyOZ^xJoBD<A?7<3Jvl=kXdQulLb z8GR;A9IsEg5ICqD)0CS%o&3GcA@jU=w<pqjmzouUcPj7aa3`2_coaiR;4m<?2~ge> zTnHP0$%X$BZspw{Zgs#)+K>EII}dknC$NQEpxFNG#y?B#K7#o`c9<ggz)-nt*=*@3 z$sMAF33&c8wFCI{RPe!KKk9b89Z^V3b~cy*8J51gK9FRF#Q|Z_fX40-05?j0m%iEA z_<}K?UnT~d;eo|Me>S-fnEL%As}b0l2@_96{yNd`mbZ_;iMYA-CQL&^=Vxu+6%LP( z$QyGi{FH-~!cnMo$-?&5)hf0b9BK8LOl6^?zo;VCIhL@&%AOeAZ};`C`H^>p{nd(E zPgU?53CNl(p%GltMQ7qKb8Ycmi#Ut=V6JU^=<2fSXz0n+YP*R<YGSLx^FH@RXqC<% zpS!N5e&W-Cw+x3o;fNW2&QjekH?N&;oK;<SB!h<-C|+dFi9+il=_ySWV@^JPRyA6k zw4Q)`m!o||PrAD4;5z|s>J(F5p$FB(XQbjUKU#<h7N0=8&bd4_t5%NE-!P5oH#`@s zV-^!cp61`XBrn4}@+}_)x5KS#DIX%WrsNkMuYOa6!YL!<%}t1WX4@f^#1^aHaXvvZ z-$En#dul<x-QwK0gJb!^Suj^?@y&^~>n+_Cmp&gaTIvRoiw+D<ZEPbK3ATL|1QFA! z2m9r20Y?#VfDHpkBs2&10TLz?Ya594#SuH1(8M&vKhD@a2;F~hCovI&ReMf*5UG%| zyGZp<g~`7|wvwwCno6mT^FXT8E>0eM@tE+-5w2mimP?n-N|_$^xfpkd<FjW|w920| z&^Yrj^L$ptd{eyo>%((z-sEVxkRE6*c>JKtY>`bh=E5e)1jk95d7bta&|!c3&Z2~0 z@{OkiuF@^p>b$sgc}dB|fmuTzj5CFTIbYBYEfTN=a7vx_53H#87IH!N^<6^5#`}-j zp4RbPi%{7_)xc^$sBu=g7~zqzzWBQ33qIZLj!wt@sNyIrxXl<16?i+963~3iCh)T+ zUs6Lad=-@0IGwFz$8PM`X?6j2Ot@$?LXYNH>sZ`_uj5Wfqd!~=0(iflIazB`N9&am zW_-`D<3%WOs2kBvqTpK~Z2D(h1JgJ#C4|40cVRs?*ebH$s-5~a6e<B=W#mq-;eQPU zrM4kX3;=iNY%r@Mis$zb=Vs?420mEYN5o4U3u*fuJBgpPLyAPzxNqv{t=VM6O-zn6 z*Zm^uSYUt|p|ygab$2|JvhQt>%u02+{1(3(!C>;uMcFD;^wD9S`wVU02P&nT9kYA* zouNM3HkTv)QF23KZ&vEXZ={^1V$X_oSrERpY@HzG-AgaESZ6s`6`CIKz0WW3?Q4#U zuh^S!ye@GVy>rcbT8XnqwtIBO|59P??WbQDqcX3z52WMW_H@_vy}77h>nvxfd!#_n zf-q$<_cN={Lu)dwcymFSOMQ<9yLzHW*!l0)ggMy?-YvK>=v^^(WE6HYdC09Ly=FkY z*~lkUJ>X<~!-CMpoXf-Bi>nXyUIo2M+JG|zFmPHBipH~KU;fN`<!o+dazDPRF3jx= z3;d1Lr)s?i2sAFZvEk-n?C;b6RI3H|5BAbNyDl3IAqSZDj7KoLf$S&y@NZ(&zZ&(I zGRxKiL&W3D1`V`XyG7j)pKQIt#cW1GbnZXQymX`(2+Q6#JuWFCEC~op0Kg23u=6n5 ztJHXBW(6z0JeT&Wy6(*7&a2r~47dYUc^3blZ_ti`4B6owzJ2&aBwk|InUSlPYjx}W z*iMG%cygF2e7}~S6cSKoXk(k*42ykD6$|^;@45E6Vo;fR8gbYxB4|32J;m%+QzKFL z3Q6}w$=J!@F*iQw5^+=mS@I(j(^e9W^(R3ne1qp+_Y$<NpE(Ot2gR<`eo<N{wMmtD zy1ugE{XWAj?lbMfhX+q6Jw_=pFnpz#NG&&>Hai{^p%L+7_NBrYt^MU-+nfQe8s<ux zQf?<)ycF7}Y3L5ZY{n>2qVW+PXEA^KB>JaQ3Y5GrOTWrV$0~~GOf`8Ty*lK}aF>{M zy+20L2%S^O`yjeGdvNLdLB%XnftU%DGP#HMgnpUTdxe`U0GcCePed^Mg2Nv?<PD1M zANj>UIeboH3wlI@z+Kg5dq<@KTa)8NhJ=p^x)cZA$5shM{sK2Z*oA$Z0IZ1Ktta|# zmI;8>wwUc2O7kOs8V)||5A*0vxS^wymn~cmeA+vhQ}6ET;qC@D=f!N}m{M)*<}3@= z>PmuSLP{Kot>6ZSAd$chfw1I%%7S-lyr4A5y@c<*1#6(ZY%xhj*wuoS;nv`T?7_8T z%LBX}!J@*y+taV0^~)2HzURNr4Zp|*6yKe75|Ua<WkQxe&N#q~mQ5ErEp7xj)6@aE zQ;F9vKeBYHR*jo}NNtmb(EV0Ny84;0K{j`s&X(7Je8n@Av(kyLV7kX?^Jpkr$q|ub z4RO68ZOtP~5J{rq7Yg?p9xofj=|8`$mEp2j=7t}@H#G)vNNpzB>O^qAx8_4DT}<>{ zcB8);9qDXiE`;EYZ+&1fAsD3QPMmNj?yA-IYiiRBC~Lf^>g+R8x9Q6d<7F5=656ov zh$eg>`VBAllgT5RQm^WS?BnRCS+6?+(uB&M3Tc|=_bc}F(M;*oslH}jn>+_A)E1sl z{J_~0JnRD4C+@NpFueoPwp|j%gDHvH<LbmvzJ0Ipb`5nXbf4LMoAn6O`I>_g(GBgc z{!uMq0kT@<fF5R7rh;h#>UkjB*=5co`>RuqdDv~2^LvB87)TJ@;rdQC?g&hX2b54e z8Zat}ltD@gNk~dcNXq<V$fG<9&sS_Y4RZJ$(@*8cX$Yh)jR1VM4FOkhXL8DR$hM2J z*)aq#v8q256S#YLW9td_q>2X#>?i{G3abj>|5#-J|KC#w%xh)vAB;+DGo!HGn*uT; zU%(si+#Q3w$nI}f0DkX%r3~Q2|M(hwekt?am+!e7kfQo>#O__zyIc;z@B;+qOVpf3 z1zm>vMk@9r+J|CD&s?l@=sQDN`ObQR)?J!bU<Dy@y~(7ryW0x67SYAS#U3N7+U#)J zY8fYf=FaEg8&1TRoCTe;(uImX4DdL4s(2dFIP=_|X3(WNK|FU~(X#!fp~GAI=e5h) zSy6}E>n#z^TAIfkoO3J}%9Udjo)t0#N3-9Mtg>RlPb(@HF{eY!4;&uir%t+m?6Ui{ z8_Kxe0gcLZXOIrEx8DM$qu&?w@K!qKl!TdXDY)@BIi0^rM$kn)#Uv4${^H0=Fp_6< z^V#G3MCFJB9+5gy33TMMetv#U849$Ccs;=)-T8~FtxF*Mz6M<}9oz6rj3Oy{j743d zKcH^(KLy0V%BOAA71$FuY$?{h(j~ing-e){)9sEKfCNGYDJ~%+E`z~a>FuE;X87;7 zLHr%q=3WCLhze~)dx+IPz32Xx3&1HXjS;+I83=;gdIQ}KM1*$4ruo`82R~AZ&gNga zd)-kiJ?<oK8N5oM*|VOXC4WQys_j(EhnFaxx=-_t4|@xro)g3k)3r4xHHZo{W|T(V zOaDfit;=b0^&7K@gFK(SQIh?ejS#!D6jAk(>ThE^mq#wVbJi^_JTZ48hHX$>&EV;` zEdTm0;cs~NLwq2tZ`T=KtMrn+D;bk^uiv~+@sU*razvN7>^u7HoKZN<g@cffw6;F) zZExFCRy(?a&!Ro6ry;7KGXIMEQft?RwvGE!mwLv}tzWXtfpn=<5)9=5^H-yo1l=B! znB4GjkJ|-X6%cF{!9w6|$0b^t4ua`afwK=$e+zG5Ht$Y98CqNzDH}1^UfmaLU9Ezn z{Bup#A1b!?6P>{?Mo$yp-Y~az>{Ym!WH!{D!O#qrv|^n^#{eBbJ6khbJxXmKgZcw- z{-FvBB>P|m9$0o~g^^nkKuQF;&9F>&kq|^iY>C(I7(~WHe}#-<7=IBK7XM^!pI`JA ziUx0>X{^IY=imkzud@F`;v)f7cP6Nvg#5W0?ZhBfeb8l8jb5f`ysXz}mqX5#Y<{kD z_dcV?I%`{;Jovrs4=5Ubn1|47G#?pe4!s$6Fo7b_)$vx=dl5z1{wFf$>Mer_-5AGO z8<%Co9Z{3|>fx_(AVz&PPtT+A5X8!Hy0alxRv)eUWku9J+@N{5!Pc0r?1`ecYA5#K z2{v&;a8G&864$~GtX}tG>*%XxYEZ!4BqF`7Qf0z)#UM3}!|K)Zd%Tdtm!!)0HX4>A z{T&q;>X#eRqdBRf^yxpV%|S0ydYsSoQ1O%>?TCUNAYJh`Yd!ng-1g&8i^kAL<YaG2 ze^22TV_noY{|^pJ(RU_rMxcq$VkM*xvA(dr-&gthwOE{&Tv%GL`}a9}Kd)2F+)LRp zu4Z6UfCc{XsPlM@RS%{cY#RlawCK#!C!6^qxAfmwpQE^qOEP5NMtr+p0S=?pLz;vW zpS}9gRE8R&Un6D-_qw39xH0RA8!z{oprONid0hQP)k0O?9pFLCA-^XNaMF#=mA;R? zpZ`pz)XATQgjVcbi8A|YP5nV3iP(AKQkxl@)oAVua55pe%r}ji`eoPI$z|m%2Hf<V zXU^&$ED08;6CmTYgq$qYEmk-a;qIe&>iQJ&m=sQht7reAf+KcTVLpqSk3C0@W-FVb z#~0ZVXu3-Pnl2ebPQwcR?eG!z&#eCa#5Qc`kD)HGY<!Q${DX=7zcSsw4PTwyv-WTy zqzElQBbzCd;m;lskRuq%k5S{@Mhibfg`Jy-ueX^04!78^usa0RZF)H9<U8i)K@aI~ zx4wF3m0hi@TmSMa^2E&{8wrcv&-PDzxZc%YlQC37=FUHkyzBd6CExxqN~=)5!lKwO zq7d&giQUq0rNzCIOhp98UR|ZWy@ZS-dQ0tbM*a$&S(Gb!79}<__1cw+Z|J$eCwl%8 z3x{6AWY=2qpq58Q3I|IIC-0p{Hd7RK1iMDZu3ebxGw3~TDm2P<EgX`fBfEO6Sc&0N zhh5JS&U1pMN%!N4?-!&7OcL&EFNA;R8ku1`Q#})8b>k8ReJrgYNqN-gg;N@vUrKm} zPS@OX35<)+8|~9m77R1!ur-zE3O&Wg+Fj;t$`Y@fc()M06fZPs_<0VRdKy4ezuvp$ z*uWTdFL<nL?+3>aSLz1vP|a@xV`#|neXa>GGKPjIf#ntQ;1(kT>cP&`5hlb?$Zw)! z$DBg)92A>#dGKDWnrGcst$dS4QS8EQD8)#-KI70FhKp}$zWQ>=pMeHr@PfvN1Rx`U z5J!qiOG%gluuxpca(39Wys)l6<VRvUp@Bv`Aq}pvGU6XEW(Qq)n9E6MxPRD7nqGD; zC^uX9KH6~9$<@in&K7|w$-<t_3igU}0lR(~8W_OU40I2wfom3#IIN~7azt5L4Z#DJ zCxGWcX;^+ti;10yVEaJ9y|<f&WuwW#`v+SD4aVUgd>uk?PTDd3ys7kajO7R+*e}qS z5MJ;<#c<sDW>6Z|->z71o1AYOix@Y#3_@C5MqF|`-5mjv^X=jPs^8~tS^Ypw?!`;h zS*C)o9^bkZMWb;rZ!Rk1kn%u!Gs#)rK(-n~ynvEt@9(5g@+>w&bH$dsfXI(2{E~>G z>$Gpq+!pJNdnIiA_+~kAK!|{5=dDh<K66IF6Js37eo3RfrFBUIBpH5g&2>s*<Hr}a zR@2WWk>N`9lePAj9<tmzbfL%Nu=WASDv!lk1Kv=LHiOB5<0Zs?(t;=IhTcq{SD<;r zU4GR7o|W@ewE(Fw#FmkiPVz~kKm7HJ!`f?8l)(fqeD7`4ITs5=&(&x<m^QfO;;gbK ziLA3upE`HC2YvX`WNtu>^5&th>~~F`U9dcQWctof^~J*(22vZjN4c_qv9$@vuA#{c z-l+ro%u0#ZZ!R-x@OZN*4sZ2EGx&xGy!HQyRu9@7G>=6jeO3tyKEm80?r0k6dg8xp z9fJA=Y8NP|JJzAJ2-tcD6H@$tU>*L++`kfHOy9#l^ojcoOJ*3~`ska|=^1-LSLO{x zb*bKJ>MhhpT8{q8dD^T9y?oiax@M*}m(Yt9CZ5A&Oz%Q6#`J8F7XlrhQ!+e1^)a9? zvZh`C-M!3~&SNa;6PJw6O1n?YF06Of-d&8I6(TpIpC30ZfB*>{&kRau^E+fRwRHPA zd6_(GcsJ=>8VdPO7(rTfl;@8>Z|)yDx#Hq>r1hP9==e#zgtAYM-|3OdWl{!fZ1xQ{ zbXw+&GbkTT;7n6E1eG~JGaufbwdK~3Ve~1YZa}kBHzVmv=aZ1B?o;<()VZr&ilqU2 zbSpb_O~08H=+k<BCYG-sw)v_AJ{R*sw3R)K?p#|djc<VedIDG@?+F5*$u3JK3KPV# zWZYk|F6`fnB?Evo=#4>$OG#pZPjY)Gff@dbVfpVUaWN}doj+CVLHOKvn#(I|Ax;-h z`xsIV<`d(fBiBC$J8BDCwKPqm8vX7avKKP@L}$PUL$E6|GhJfsCZf3zi~>w50ac2{ zQ_&HIDx^a?_hkt093D?Cd+rSw^P+Rr$ez7lQ4i~S$bAZV^3;)~lj>2(2sR&-L$Xzv zVP#M1(Uix{1s1(18aMt{rf*T9wJ%ZMk38mM<#wR<V&aIc(6)`yv#zw*@Ml-}Wb9j3 zAA49w>3A>2HT**kAuINeplPb-7M^KuxJ_p~sVrJLBS?(+G@^^2Tpdj)`8I&%*1IcR z^0Q*9HHFJ#w)J_C$8e(5fUfU#k2+K>Ea|U7@Q!in>>||+h*Xn*Nr`_8P5+_9Kj^j( zCQ1e3930c%FYFQ!mI8z&?zs#^p+Hy|ps-88!9;$<pAv8|HD1gX%)by*(K8UUwX;Y0 zy7+*tIb8vzy~}X{2{t60b>CCStgz2Za{-^DWdlBo%nJMLH_UUe?66M=!#v|m6wdk! zj$m0k!A$gVvdPw0>55^VP=(Cg91iz89|}5G5Dh(elf|2B#;%OOoyHdX4(FFEv!4R$ z+lsPy-h8Y3c-qjIQ0O>Xm3sYwL44=(De73iP9-Jft7GN|w#>g-9*|7uHq!Q4I851I z@i3${WJ&t0sUpkLl|ZdzeVhsziUTWRfnj#T#dl9nwRL}aat6m;8&Kuul<)Zn%}+}- z5vZ%)fW7v(j@07YI8h<CSv`G0yw=((W7Jsb-D39@`=t0SXX}vh7A=xw-bvXxMlDa9 z%peZerfa!3<kv#Zra31C6u9=izV?mvyUdG=%dPpuX;FFgSxh%!hr{pR4ZX@K1Ha2N zZ9BKR{P3KO2@k~?QCVZZ8=fWju@o`%HK;<Wrx`EE)iXj{o^=&ITR0p3>9w3E(Mt=T zbWTFl$SplK_xi=xI4@@r1bL|kSMAgT-hM68C|#ZKI)^&#?q3}Cw2-Jwwsx-d=?ip~ zR~PH@{YVH~=sI(ZprPz-vypbP6Iafh_m(4^Nf&va@Jd{iT43dom?0m>YI~XP2><=c z;?cC*g@<o+^e`&xyo&lJn2}3m5A@giAhloG1g%_&6=QDWs4%&Eb4*qKvYzKqPG8H3 zH%=4Ak9#M8M;o0pI_qQ)Esnj*WMh;OtLr(5C%QnEttUCMK*Yx~c;}oX8)40e)M&p| z;)GAMCUp)P7tA-o1)C&;to>`w|2{f#gNqpaYP<a_pmaD*Bn@}J;!oVFRm)hsZ%|Ea zuOIw1J1iUs3(Y(CFW#^A8AV_VTwFYyNyW3;04F9Na+m)B(&DnFdj}-J+HcGOz#jyc z?S23t*#zsCG3gk@I}>1>o5bKhxVYRcxL^w@0_<z5(d`qIx}RcBklJ@SE&|hLW49*( zhP~ougH5S0mq7_~;5oamAyL`;A!Vm)ub;P%N`4R9p}T58bUtPHYo!YCv2U>lHH4Y3 zWQeduWZu_o`dr$rkWl|_lff*FWR`#ZOeo%3PX%z7*B*`Uol<wY?ToIJ^NEjdk<1S= zMHy#41}dKLH>iko_<k+HPW4F0sEyL|sFp9iF#!#XG<?N2V(9}*=Ny*{;H=4C?G5fB z&`_|v5K4rV=+uAhFN$?K{f@fscRHaVT7Q{M@v9f<T<WppLt52-CRDBqWJxz-Jg8aq zO4kvY71bYbD_sovfUvcFP6=EwMA&;EY!(Qc0>bKHaAoUHxs%DZjm!nkI5gKaNX`$9 zla94tAtIK1Q+`D8DI1t;_-~*JzrS%{1~~qcxy>gyt>2F73AWvz>MTz`)>mE@^_b#X zM1wz4G~9H*K!Sb&Os|U?XUVoC+dd+NPX|=feUDG}@JmYEIZ<yZ8#o}aA)s^aI=o_T zOX^V-`OA672Yf~53=r!QxN!CGodO-4PE!e2q(LElmK)O?f6TNco?84fdiT^E`86sM z0lAF)j<UGPRrUvF-ei^3mD4)vD{pxLLLFy>a&ge`mhM@eJiMamOlw%;*jlo{M23Ch zx7yxI<UGXXPJ@Hv?K0|}9%UPmhdt9gj9;dIJ@0(1n3a~Pb?uEppj-6iDdm6yz=8U< z+y%}7R!PV1krui>CK*#cep?>Tb?afOUgz;sViktpxPXmb!FRDoS!!`P*TPjOojhi5 We@}$Qt>5I3i@u7_u!<!SIR6J>@H+?q literal 0 HcmV?d00001 diff --git a/packer/ova/windows/pvscsi/amd64/txtsetup.oem b/packer/ova/windows/pvscsi/amd64/txtsetup.oem new file mode 100644 index 0000000..662d6f0 --- /dev/null +++ b/packer/ova/windows/pvscsi/amd64/txtsetup.oem @@ -0,0 +1,35 @@ + +; txtsetup.oem file. +; Required to install the pvscsi driver at install time. + +[Disks] +;"directory" should specify the full-path as per the documentation, but only +; relative paths worked during testing. + + + + + + + + + + + +[Defaults] +SCSI = pvscsi + +[SCSI] +pvscsi = "VMware PVSCSI Controller" + +[Files.SCSI.pvscsi] +driver = disk,pvscsi.sys,pvscsi +inf = disk,pvscsi.inf +catalog = disk,pvscsi.cat + +[Config.pvscsi] +value = Parameters\PnpInterface, 5 ,REG_DWORD, 1 +value = Parameters, BusType, REG_DWORD, A + +[HardwareIds.scsi.pvscsi] +id = "PCI\VEN_15AD&DEV_07C0", "pvscsi" diff --git a/packer/ova/windows/pvscsi/i386/pvscsi.cat b/packer/ova/windows/pvscsi/i386/pvscsi.cat new file mode 100644 index 0000000000000000000000000000000000000000..d8654ec95864415424dc76c3b334ce6f627db5b4 GIT binary patch literal 10163 zcmeHN2{@Ep`+uI9v5hrjmodo}G0zNREBhLfU6f_a3`Q8sSYO*rC24;vNhy-FskEw4 z3N47Jl%!OWqLo)#^glBw_4R)5b^ZU}cYW{mUH{8<IOja)zMuQK&$)lk{ktCsSL>*t zh)#}E&ZsyngevB06(d}&ohTS4Nh0EcA|W|WFawQ2!2%G%72GB$qALhvV6HJ1F6Ij6 zf~ggVm>_m?QVfPMQg2@vrX9LE>{=ZgHA=kY(IqcVMi4_1f>0<F1JeLRV%l;vPb+#q z!a0GFnboN{ymn^sy-e=qqP6-V+h;KP8cCc&MC$u`6q<zMLWs$<8JMJlXyb(;2Z#wp zK@1c|3Wi{eTr89XB|tRLn2<hbF;Ex;L5S-Qk`~B(yco0y@`9`(Hu#C3l-49eW{?q( zBSUjRo9G)%_DK)|t{^;ZJge0_{f(b&wp{db^M{y*9OLu(pD*qBh1J>r3#${LRAB$V z+-fe2#}ML)ZVAqHN{+%*oWurFO!sQ9OE1gHc1i^f`Ou7XCE7@wv7a6PH+qD?Z`+81 zF{DMvLPTNOj3k0ZqXdeJF?&$JX3|fm_^+QG;lgql!f&KmnWi@w$__f^q4im2-3C@g zHIS=E*&2<8jMa*<J4u|kKYQqJbToaY`2$08MC_;8ih>pY#4~^O%*9;PR)mY%ggAdw zF9vpCK4ON~ggl@`hzeqm2||qtLXQd}iV3km=n)`ShyenU0Yyzl7?uJei1!0dxB3sd z<PaIWAY=!5L$;G^F(iJhZTVptXC=bn;*~(?CPJZ5G`LX$Kh`I1w*a!66b>@r-^LU& z1p#dg`oRzhGK37kZv-?7=##)a1%x?o<-{y+4euHmNRjzH1dNOz6ELRmwNOBxZx;Ip ztHKaEUJ9}SHpM`69vng6EpcW5*)X610WA#t=m8Iz0&OOs$Fs;7G(9k)f@?<nt28jr z0DT&0M&L0S;GRZ2XBho(S8YTSFAe#CdouY>jt8Cq8!_c-ZzvuZ#Nf%z|3OY4nTwYL zu4aH6L;;C-(0E%0HfmB<%z>u}sN+jJ{UB|InBXxy@SI$$V*)J()*Ats3<fHUz|&0h zcs`x*yD8)};WhUkG}$0lcr3W&$MY6Mn$$Np7MR6^mhvAdhQDtVU=1D81dVr^f`L|J zu!eVtin*e6ge&kH!o|>v(I^;&!Wn?+m08QXb)pWWR)Ibk@sMEhO$UpDHxFk$NfZ$g zMEhU_aVQ@z5)PTc8wlgDe$<2rW=uGn6+@B&FDx)AgcI^)(4$!~bP^t!$(zCAWEV1N z@vH<^7@J_rijQT*Q`t-)VFLCCTu0-G->>^IW9Y2p1cG-2GoDUxr^d5WflPEPD~1sR zq^${LghWR0^5P^@#0a7AbQq8W_^(bQ#9(rWa5nv044#LWP8dx7M}wWH@$_VBJcDm7 zc)n0(6cgC)o){X%Oo(8_1F%$^?pKf}pt-Olc&)=&6c>h}?Oe}_4dldC9|rH;Ek!H` zGMKtX?=~xuE9wg6mAR$`y2}iFkw{!MQu=hm?rj%?n5XU5^j(mq<&)gTs>Md%3)h;K zK9HqrdP$7LWvf*(wf6P3GRIxB@dq9B7S*n~nRTVU>m5uHIdIqfOl{q;*BZ||+nfud z-X4w-OwoGU4To>87ya5yQhyPu#kJb9A#pfH?nv&+NZL|elKR@lvw=N2zuB{}>rSlM z5&U_leV+m~R3PVcahlO_xe6PBLhp;ZUjk!uh0p4b36#F^zlb{&e|lZ92e)Zg@3oYC z-NQ|~3j)eIEL+=gPZw6(^(udSoQWxSncHLeQmr-pUKA3x0fhz$Nhud@0v@@7=mJNp zXkgS4wQnJSMQcs#3kV8>WJ*9l2#rQmc#9=4au}K8Dh^7k&Y2nJ)?STH&S~JJm^xP= z?!3!0FbfdJz4nMLX%3>r+eJ|v;rlKwie*e3$^@`ujCeMS;K`)1NC0GcO9gP^WCOAZ znWAe*p%_w35LKSW*%$>xE?b5gSH9}#SLW?mNsBx;YeTiE(uY<cY~BJ@Of<3#VHSrM zGjeDVY<8>}k(ivEtRFoU4f-@zG%<D=lNFmlq{T;l4+9M{G8qV<#}5VYmmdt^@3(N^ z^{v4#UP$Nzd4$86atg>V6OjZYZfcC6au|PU0eJ3&|Dz3TkmQ0V$HDMd3<|*6k3a$r zJPghF!+|ldYoK(N&T@I7rSGKGeD}3@d-~djb-7rq7E9Yw8Qz{IRyh#bBf~P5nKME% z+<n=<`NoZ4^2Ze|8nabb>)X|bFAE-q);>G&{@xxYc59?g<gNnU{V#86Fyn0Fd~uN) z$NLoAX82$St1N@A9`O!;9(L#au*<F-o2xZJq)6uli^3yIf?iixuQqQhS4huQ-9tGU ztR$Gfzd~=REa}xPn@(-%ja7@bv3BmUMkS<NvX)IFhnpUMjs)EQ{Mub$q<PS4uF^5{ z812i<RY$~yTBM&U8D<yUbr?w}Yuq1itE&>JAj!wBTxhgTR-!*CDd}>dxy%NE)^xoa zWp76t-vRjT1-bmS3HZfQMGw~TQCH+AXCa*2e+!5KlEFLEXxyCd$KR910^kbvpBcua zQP~WFbs{^070&}OaJUdY-jV?~F(Dfmni!by@YZ-@NZ}3telLi>0^8D^$O@5intm(R z@y!eMu~LK}8RSRO<P?&Gj=SHw5iTOkzy_4L9JxItrQct+{nYLVV!@ilsKbPlbL!)2 zv}cxmnZJYnwBhALsz&vjR}mL(mN#YUpg3;yrD9$=seX#a)H4NRV#RK1{yWB$^}^?C z&G+3H_T)<zBSSK$hT`}<uX(s@{XnGKp>m(WJ*)Az4eY&|#&#vww9Fk7sLEo)D$hSD z^xEDOA2`@;%&Hl$k{nW52s^l84}a!9AM{<0TW<yr$<Wy^=*PpPPDeyvjfFDYo%GFZ zUp-Ra+Ss!G%9pCATU#Gwe%cyT0=L*!3U%&9UhT+H(ut`P_20v0t(gK_TL89}AmyE6 ztFkZ#WChSPMExbaDSi`>L1Dx|QZkWV+CtWdC@=mcF+4Rnh~zgg5J1oX6Mg{B+kf~r zZFs(CX=ubv^DP^fblxabs7HKx5~>&;RdFd^Q0$mvKM7j^-~JIe`KTEi&!Q*NCZG!( zWN?hZ=?&(cTRp@`k8Hq)#(*g#1jy)yxPrud^7R3b(c+&WBat=*i+`e<{Zrhcxxj$| zdEs5nx4(#MS|?w?I;5OD(Id^&O`d&;L4<?b(Zkep?nU}*P2=B3SwvRilcERDyytc| zpKoBsYA1xrTYA5I1<N_rJKR&wKEg3uCz%=@actKMJxkM$>n52sLFvLVirtNuhD{72 zsE<7zm-j-jZ~M8XRa^lQ)_RRwe^zktP;iH-p8d-`xC>wKmkO-osB*zqv9TKI{!Ehk z)3|=@d5rw%?s)vQ_VY@$=BRNIvE#NU{gt+RZO)$+{OHaZP1t6u(P6DGwZkiuBg_?Q zhHDFQ)n?>)%DuNAL~oOdT~!)u8#n*{wH&m(*hs>X#*E&j^r6lMr_Ler<C_ONTFd|N zbEA$WzYKqud!h&GLT_lxGc>kPxf@#5UirS4xF&ucCqJF_c`z&~K15mlUGeJZCDD-n zYwZU)s{}5cJlj`GzohfdS$1i`<9e-?Up=3MW=bAMiFSrv!5;50CtzgU$^Of+89Rmo zOr&AYbHpG*{CelNU;5)vYv=U})m}>&e&Bfb<m;1rPatPkTO?gS3l;cg9(s{iRn}&5 zh?$HNl_3rsv{oHGS7WAYnD+{Mi1v&&nybE^Ag(*F=*gu8o`-j<N|??IycOdf`7FcJ z>|nZq>>P2;Ab4@P+X3^m6)d)8$nK})MMluk=(rAxeGZIZ4*TtRU0j!Iv2_6V!CO@l zSC)ftWivo|2;uDe+u`G%X8qHuSjC*npjt4+14Pgy(QiSIg+XRNRm(!bq9loFwJj7Z zix9udVMuBiLIbt0iCbB$9i)hgtvEh^i^yGlM+*sDqf`o|UnxNVk1U?1PK@M?I29{p zD?q-`LMRz<nRvis<^p!S08lp?q+g<wt<4MBgkR=Uj2OMd1Y+_ekC+~uAmzqzVMxyD z0XyXUJeDkatjC1jEV#jlJ!_N7<*wZp?xy)8Z!;dBE$ZO3`&*xK2y!yJ8u8F>zjmh9 zI+12=;ZA(Xv2E;v+sh<tjgI#_lms?)Duw#5b-UbC=_x(~3egRzH2iom1Q+zVRp)+i zz1MmHuXevZfhg5$tL7^nJtf#IE<-7G@;Vbv<XV<@G?azp_NKoHRk=L0<-$nIoZBnK zmVJ<~teLk}?I!BT$43wChP@=zRmwvDsFxW1m^!+aF7cFe?9eL)*(GHi<0N|BfOq`S zDA{`=$@SgRDht&-78fphb?ai^*WR=q{bq%(z*0(ND^qKBpl!)`x7E1p3mK1D@?~;s zO1RQ}2v@rIo6FH?n8cO73Fcb9?HjMcDP0Su&Y@uRFDjg{hoI;rMHG>OF~wZi3ix(D zc%7JlGe4<%g+CSz|Dv#Te{dX|X|uhlT<jSA2<}q|eyFH_UX6pNg#DZlR|O^qB^b1H z^kHYc0*;s5f<(g*F)<_=80#CE7zQAiY!p0P%xT-pX+b#Gf6S34X{s;|<!AlBV5E9{ z-T|nA`uE&uK|CXh8bc>cqX@3dXeOIMCuxG344+A;ct=p97=X2Uc@gZr+|2BO&3cA5 z_V#)X*2eZE4P-WN9U3?DyIOB%G(*peO^uEvBrxKWm^4N)R|5DC^A;qB_r7y;SSZst zqF_~k@f}{42n$U=&^&IQ{-Xo_Mbv!q$<R2JUs~)wLBuB#LsO6-nvjeQObjTz#E=9w z++cF}U$sBrZ2xlxf9qW7maV6E1?W7gJ9aDw=VZ2bFsIPM`c^@`XohAg{+zc!%E7i5 zCw`aGczX$5N*r!MRu28HO(E^yE%PMpIPvD1M|1t^j#OY%vgRykKGrPTzEn}ir+e0> zq>cA)9;)7WOSCX4roP&W_`u`!*U^HEjp8Vy4)Mm0Ll!|_E!MZj+PKKWqZ)x3UYglX zSG*qI@;Hc1GS=~_?tId>${hDZy<&$KVOPlqyM1KyPJH3U0?{{49m{*~+PHjtDwQsD zH}TAu>c|6gatF`3ga_2dltQDb8}&Y^^o3+DYvtN(eO#Jy&U)P9gX$^&w)H`-4t*y& zPj9g)^fLNV>N<ND(*3aqo=Pf}fv4oBrI3cbPqryKX(Y_Fy!W*|S0OQL&hz9SGeMf! z(mc|}_qM;KJ1938L<Fpi_W8eNf*`&?bb*69nF$)}A*6p*Cio}1e<r^;UM{PF5ks(3 z$tGI(r|{)7Vuc4^hZ28pj>}u`X7WVx^da}r&Bv%;@=H8NR>|yI;a+B1U0ttqB`SN% zQUADm;z|Qqh28FS^7_<>J5maFLWWY>SDw4-IdG<^p?T5Ff}X9u8OE%h{@0(H&!2jm z+pjCJMDEptfPFBs?pmAIq5iUKCPmI}9cr3NapyG0Wut1#wS9cy#)a0eJnqzYbS@r= zig9QhU}Zm8EU@nIo4NsaiFrj*=}zPAw`-e&N**X!yRK8qH@85W$m3ouzq;#dOl_g> zn-$f!7BssRZrt8{J?rU>kTZ9yS@v7=a01I6t;1XTp7hUYcfOOBr`3TOe{_&AxcaVs zqbf%>^GYKwF(vuaI`DBU4gjD36eAYl=<pda>L+LY-54=~7=y|S$-s!h2R_Qg(2zI$ z55w|bA=LpD@#^Fmd1i#c&$w+PAG4TSma)C1ZkJ&p?#fR?=@Bk-gBvdQQ7<K(u?W*$ z@<!H63qw-1R#w`oazg~SKAnpAS0Z+jZ$oldc-x9~F03*UK4J4<^Wi%Qh@U36)Ly*p z#fT%P<$`($d2xutyTy(<<P~@}HGESr$Gft1v+M75_4@*EQgJcbjY?xV+2<cpKReWE zsi=ob$1BasJL*DT?H*bg_$67@{Ec7Y;hH>~g;pLnC3gy1glCO}T}0>GWxj56`4ZDt zc)fD}yEGjv=}niL;HJ~L!j$JJGmi~yZ<*guv^!Tm+)c093)c}uHm9_FuD*E9E-*-L zCoHf?ZQ&GBJp)Mf_@C0>+rNUQ|EIs-sWltn6!UP7z$^K2wj!M05zdBhOhF$Fijs)= z6#k)LIntk!ZFiRiL^_L>K(uxDBGMUQ)Wj&Zegr!jvHB*CLJaX_f(rjLSvdcbOEBaP zKArJCrqRGx;9yXQV1s!!APmu<#ekkZLFFeL;WKohXSp8Yj~gEqSaRaf=0&9?vsfoy z?rR<))wT*eJ}~~#FIo{faj7BfTI9o#{x>N#SN8AHcrsQ!w9MO2Sl5GVC;jQH*V^Xc z5b3<6W-BY}9o<Xizb+jMlBX1?`?|1S+elqKdLgSZ>z#2%faT10+f$u4c|u1`B;`kl zsT{_=1E-cfy>jD^>uC_n1+i0Co8LNwF3aEGKd1Wi7ff&LZnCr17oVfV@zZ_l4bF!K z7vA@?8hCqSd)UUcUn4`a9yB<MZqj^gI;iLzM=SbmR`lharF-Uo%*x1*+?cX2y1jSj zn9669yIY1E%dq)5duw(n9l_WvuR4{zL(PP6N~4cHI68bGljg4>nWk^*m$WDDpkSWl zYPoaN@)=EqcO@JPvm4r4%G+LNEPvBGFHYoPAiF?KnA&yB9nY$H+Y3GHCkgG{Y&Obp zOnLrcztf@7p7>1aGFI{dPE)zaVbk-2jZJsCC*xaGhO1V>`0P*0t986hk1y%ED(<sA zZB@cN;b#SUFV;OW(3hSwa*^n*HEZ<hVYe>rs>%cR^N*L??77yeXua@J&X`VN>5MR> z<2;+}@{so1$o4#<@|9Uf{ZAd~wwu4rJ+8B)y}{=Rv&XOQW)E`lOY^gZpTsW&E*dDp zE1D3iTNexHzZNfcr*yp*(VBVtL?#6<e6Gvren;?z9(L{m=@KsL2KYp4%xCByO8)!k z#Gct$P<#*)6wsAGqm<CH&}GruQy&C-j-9tJd|Typ8XM-B{-Kz&9N}c|&HN9)ucmz_ z5D<V+0?=d23>QRALGnBQLyW=y#C)5eASV0?BpHCqL?r@{O$<yT0%XEYD&Q3|vEUa9 zrQU!7#YZ82f5!cZ1y(njcnhXJ5%a`RB=Cv&TPcIbUQxwZM3^Ur7Ahf>O!eWM;^~kA zOh$7!P*MWhwyYIzampUaEt6W_YjsrOZb+V)(_H24g?jiEMO6zfzdv-td|l1JxWbZr z(SGeuY1slFTaO~AG{d-p3BOk_yOQQ+6zMbcoUDAdNMG^UP^zU*ve(g-;h%S|W7s)l z-KSaI$!YlG=IWGMMV!_F8nNKkyUd8;eFT+FAHuxOkho}Zz#T>SCwlXLc1QUIre9Il z=>;Y(?2IUVt2lrPllTucg@33bwOS}_x?N-Ly5O07if@HmkRSaRqa7c0Y+VoGouhmc z7vX%IrT|4Gab6&teuVQB;nZLV)}e1oA8(>xQr;H{;dVQTuIhXs)_rxm2$u4s!oji$ zk8le92BGk4$AJ=>;Pt`r>rmsEtGmvXs;A8r1&i7%4(HTK?p#ruOx9l>F#SM+x&W`x zb^84b;wSApDoj;T*`%OqNsnWv5AQ0**%!`>xRj$&Y)N9p)!E;n1*}y%a5AB4`C`w_ ziB?vX%d{e%=lcevhWOaDn`M`F?XB!;9rAcqNL$iR%<21FwsOaoJjv(<tyi0>Mp(^7 zj9$X<i{Aoeuo4^UJl>spc;B!6r1_k)IYOFkuT%rV+Lp)`xfYz5`PtENbqJ+rSp^B( zwq|@Ier+@U)|L6nN~2u|m$H}Rcc;q9FMW4?PU?|+`^||>1$~I-$2g6RHwKhOPbk~( z(fg{YQa>}TVaMowoQJKiT&(Wpgpm7(F4977h0*AGDTPOnRkJHjJQquSKbvx8;pSMn JN{Zr){{gTqsGI-* literal 0 HcmV?d00001 diff --git a/packer/ova/windows/pvscsi/i386/pvscsi.inf b/packer/ova/windows/pvscsi/i386/pvscsi.inf new file mode 100644 index 0000000..58b06ac --- /dev/null +++ b/packer/ova/windows/pvscsi/i386/pvscsi.inf @@ -0,0 +1,221 @@ + +;pvscsi.inf +;This file contains the information required to load the driver for the VMware PVSCSI Controller +; Copyright (C) 2001 - 2019, VMware, Inc. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[version] +Signature="$Windows NT$" +Class=SCSIAdapter +ClassGuid={4D36E97B-E325-11CE-BFC1-08002BE10318} +Provider=%VMWARE% +DriverVer=08/02/2019,1.3.15.0 +CatalogFile=pvscsi.cat + +[ControlFlags] +ExcludeFromSelect = * + +[SourceDisksNames] +1 = %DSKID1%,pvscsi.sys,, + +[SourceDisksFiles] +pvscsi.sys = 1,, + +[Manufacturer] + + + + + + + +%VMWARE%=pvscsi,NTx86.6.2 + + + + +; ################################################## + +; Other architectures are unsupported, as are older versions of Windows on all platforms. +[pvscsi] + + + + + + + + + + + + + + + + + + + + +[pvscsi.NTx86.6.2] +%DEVICE%=DDInstall.x86.vista, PCI\VEN_15AD&DEV_07C0 + + + +; ################################################## + + + + + + +[DDInstall.x86.vista.NT] +CopyFiles=pvscsi.x86.CopyFiles + + +; ################################################## + +[DDInstall.x86.vista.NT.HW] +AddReg=enableMSI.reg +Include=machine.inf +Needs=PciIoSpaceNotRequired + + + +; ################################################## + +[pvscsi.x86.CopyFiles] +pvscsi.sys,,,2 + + +; ################################################## + +[DDInstall.x86.NT.Services] +AddService=pvscsi,2,Service_Install.x86,EventLog_Install + +[DDInstall.x86.vista.NT.Services] +AddService=pvscsi,2,Service_Install.x86,EventLog_Install + + +; ################################################## + +[DestinationDirs] +pvscsi.x86.CopyFiles = 12 +DefaultDestDir=12 + +; ################################################## + +[Service_Install.x86] +DisplayName=%pvscsi.DiskName% +ServiceType=1 ; %SERVICE_KERNEL_DRIVER% +StartType=0 ; %SERVICE_BOOT_START% +ErrorControl=1 ; %SERVICE_ERROR_NORMAL% +ServiceBinary=%12%\pvscsi.sys +LoadOrderGroup=SCSI Miniport + +; We need to force the use of \Driver\pvscsi32 as the driver object name, +; otherwise the crash dump driver loader functions cannot find the driver. +; StartName entry defined in the INF format is supposed to facilitate that, +; but at least on win2k3sp2-32 and win2k8-datacenter-32 the driver installer +; interpretes StartName as the name of the account to start the service under, +; which is an incorrect interpretation for SERVICE_KERNEL_DRIVER type. As a +; work around ObjectName registry entry is added directly using brute-force. + +AddReg=busTypeSAS,pnpsafe_pci_addreg,vmware_installers_addreg +DelReg=driverObjectName.del + + + + + + + + +; ################################################## + +[busTypeSAS] +HKR, "Parameters", "BusType", 0x00010001, 0x0000000A ; BusTypeSAS + +[enableMSI.reg] +HKR, Interrupt Management,, %FLG_ADDREG_KEYONLY% +HKR, Interrupt Management\MessageSignaledInterruptProperties,, %FLG_ADDREG_KEYONLY% +HKR, Interrupt Management\MessageSignaledInterruptProperties, MSISupported, \ + %FLG_ADDREG_TYPE_DWORD%, 1 +HKR, Interrupt Management\MessageSignaledInterruptProperties, MessageNumberLimit, \ + %FLG_ADDREG_TYPE_DWORD%, 1 + +[pnpsafe_pci_addreg] +HKR, "Parameters\PnpInterface", "5", 0x00010001, 0x00000001 + +[vmware_installers_addreg] +HKR,, %pvscsi.installers.value.name%, %FLG_ADDREG_KEYONLY%, %pvscsi.installers.value.windows% +;; FLG_ADDREG_KEYONLY +HKR,, %pvscsi.installers.value.name%, 0x00010002, %pvscsi.installers.value.windows% +;; FLG_ADDREG_NOCLOBBER | FLG_ADDREG_TYPE_MULTI_SZ + +[driverObjectName.del] +HKR, , "ObjectName" + +[EventLog_Install] +AddReg = EventLog_AddReg + +[EventLog_AddReg] +HKR,,EventMessageFile,%FLG_ADDREG_TYPE_EXPAND_SZ%,"%%SystemRoot%%\System32\IoLogMsg.dll" +HKR,,TypesSupported,%FLG_ADDREG_TYPE_DWORD%,7 + +[strings] +pvscsi.installers.value.name="vwdk.installers" +pvscsi.installers.value.windows="Windows" +pvscsi.DiskName="pvscsi Storage Controller Driver" +VMWARE="VMware, Inc." +DEVICE="VMware PVSCSI Controller" +DSKID1="VMware PVSCSI Controller Installation Disk 1" +FLG_ADDREG_KEYONLY = 0x00000010 +FLG_ADDREG_TYPE_DWORD = 0x00010001 +FLG_ADDREG_TYPE_EXPAND_SZ = 0x00020000 +FLG_DELREG_MULTI_SZ_DELSTRING = 0x00018002 diff --git a/packer/ova/windows/pvscsi/i386/pvscsi.sys b/packer/ova/windows/pvscsi/i386/pvscsi.sys new file mode 100644 index 0000000000000000000000000000000000000000..0b5ccf08723b19de959e9a1cc0ae3b0b72ed51d9 GIT binary patch literal 55312 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 literal 0 HcmV?d00001 diff --git a/packer/ova/windows/pvscsi/i386/txtsetup.oem b/packer/ova/windows/pvscsi/i386/txtsetup.oem new file mode 100644 index 0000000..662d6f0 --- /dev/null +++ b/packer/ova/windows/pvscsi/i386/txtsetup.oem @@ -0,0 +1,35 @@ + +; txtsetup.oem file. +; Required to install the pvscsi driver at install time. + +[Disks] +;"directory" should specify the full-path as per the documentation, but only +; relative paths worked during testing. + + + + + + + + + + + +[Defaults] +SCSI = pvscsi + +[SCSI] +pvscsi = "VMware PVSCSI Controller" + +[Files.SCSI.pvscsi] +driver = disk,pvscsi.sys,pvscsi +inf = disk,pvscsi.inf +catalog = disk,pvscsi.cat + +[Config.pvscsi] +value = Parameters\PnpInterface, 5 ,REG_DWORD, 1 +value = Parameters, BusType, REG_DWORD, A + +[HardwareIds.scsi.pvscsi] +id = "PCI\VEN_15AD&DEV_07C0", "pvscsi" diff --git a/packer/ova/windows/sysprep.ps1 b/packer/ova/windows/sysprep.ps1 new file mode 100644 index 0000000..7efb732 --- /dev/null +++ b/packer/ova/windows/sysprep.ps1 @@ -0,0 +1,13 @@ +Write-Output '>>> Sysprepping VM ...' +if( Test-Path $Env:SystemRoot\system32\Sysprep\unattend.xml ) { + Remove-Item $Env:SystemRoot\system32\Sysprep\unattend.xml -Force +} +$unattendedXml = "$ENV:ProgramFiles\Cloudbase Solutions\Cloudbase-Init\conf\Unattend.xml" +$FileExists = Test-Path $unattendedXml +If ($FileExists -eq $True) { + # Use the Cloudbase-init provided unattend file during install + Write-Output "Using cloudbase-init unattend file for sysprep: $unattendedXml" + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /shutdown /quiet /unattend:$unattendedXml +}else { + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /shutdown /quiet +} diff --git a/packer/ova/windows/windows-2004/autounattend.xml b/packer/ova/windows/windows-2004/autounattend.xml new file mode 100644 index 0000000..fd86747 --- /dev/null +++ b/packer/ova/windows/windows-2004/autounattend.xml @@ -0,0 +1,231 @@ +<?xml version="1.0" encoding="utf-8"?> +<!--************************************************* +Windows Server 2004 Answer File + +Installation Notes +Location: +Notes: We currently assume your image is using a licesnsed media, and hard code product keys accordingly +Users will want to modify this file. +Installation Notes: +- We currently assume your image is using a licesnsed media, and hard code product keys accordingly +- ProductKey: must be removed if using an eval version +- The OOBE and UserAccounts sections: might be removed for administrator details +- The Timezone in this file should match the location your using (otherwise you hit race conditions related to time.windows.com) +- We hard code an administrative passcode in here, which administrators may need to modify +- There are many other parameters in here which may need to be changed. Over time we will parameterize these as part of the image-builder process +**************************************************--> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> + <settings pass="windowsPE"> + <component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <DriverPaths> + <PathAndCredentials wcm:action="add" wcm:keyValue="A"> + <Path>a:\</Path> + </PathAndCredentials> + </DriverPaths> + </component> + <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <DiskConfiguration> + <Disk wcm:action="add"> + <CreatePartitions> + <CreatePartition wcm:action="add"> + <Order>1</Order> + <Type>Primary</Type> + <Size>350</Size> + </CreatePartition> + <CreatePartition wcm:action="add"> + <Extend>true</Extend> + <Order>2</Order> + <Type>Primary</Type> + </CreatePartition> + </CreatePartitions> + <ModifyPartitions> + <ModifyPartition wcm:action="add"> + <Order>1</Order> + <Label>System</Label> + <Format>NTFS</Format> + <PartitionID>1</PartitionID> + <TypeID>0x27</TypeID> + </ModifyPartition> + <ModifyPartition wcm:action="add"> + <Order>2</Order> + <PartitionID>2</PartitionID> + <Letter>C</Letter> + <Label>OS</Label> + <Format>NTFS</Format> + </ModifyPartition> + </ModifyPartitions> + <DiskID>0</DiskID> + <WillWipeDisk>true</WillWipeDisk> + </Disk> + </DiskConfiguration> + <ImageInstall> + <OSImage> + <InstallFrom> + <MetaData wcm:action="add"> + <Key>/IMAGE/NAME</Key> + <Value>Windows Server SERVERSTANDARDACORE</Value> + </MetaData> + </InstallFrom> + <InstallTo> + <DiskID>0</DiskID> + <PartitionID>2</PartitionID> + </InstallTo> + </OSImage> + </ImageInstall> + <UserData> + <ProductKey> + <Key>N2KJX-J94YW-TQVFB-DG9YT-724CC</Key> + <WillShowUI>OnError</WillShowUI> + </ProductKey> + <AcceptEula>true</AcceptEula> + <FullName>Administrator</FullName> + <Organization>Organization</Organization> + </UserData> + <EnableFirewall>true</EnableFirewall> + </component> + <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SetupUILanguage> + <UILanguage>en-US</UILanguage> + </SetupUILanguage> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-US</UserLocale> + </component> + </settings> + <settings pass="offlineServicing"> + <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <EnableLUA>false</EnableLUA> + </component> + </settings> + <settings pass="generalize"> + <component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipRearm>1</SkipRearm> + </component> + </settings> + <settings pass="specialize"> + <component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <RunSynchronous> + <RunSynchronousCommand wcm:action="add"> + <WillReboot>Always</WillReboot> + <Path>e:\setup64.exe /s /v "/qb REBOOT=R ADDLOCAL=ALL"</Path> + <Order>1</Order> + </RunSynchronousCommand> + </RunSynchronous> + </component> + <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-US</UserLocale> + </component> + <component name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipAutoActivation>true</SkipAutoActivation> + </component> + <component name="Microsoft-Windows-SQMApi" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <CEIPEnabled>0</CEIPEnabled> + </component> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <ProductKey>N2KJX-J94YW-TQVFB-DG9YT-724CC</ProductKey> + </component> + </settings> + <settings pass="oobeSystem"> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <RegisteredOrganization>Organization</RegisteredOrganization> + <RegisteredOwner>Owner</RegisteredOwner> + <DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet> + <TimeZone>Pacific Standard Time</TimeZone> + <UserAccounts> + <AdministratorPassword> + <Value>S3cr3t0!</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Password> + <Value>S3cr3t0!</Value> + <PlainText>true</PlainText> + </Password> + <Name>Administrator</Name> + <Group>Administrators</Group> + <DisplayName>Administrator</DisplayName> + <Description>Administrator</Description> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c powershell -Command &quot;Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force&quot;</CommandLine> + <Description>Set Execution Policy 64 Bit</Description> + <Order>1</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>2</Order> + <Description>Set Execution Policy 32 Bit</Description> + <CommandLine>%SystemRoot%\SysWOW64\cmd.exe /c powershell -Command &quot;Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force&quot;</CommandLine> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateFileSizePercent /t REG_DWORD /d 0 /f</CommandLine> + <Description>Zero Hibernation File</Description> + <Order>3</Order> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateEnabled /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable Hibernation</Description> + <Order>4</Order> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>5</Order> + <CommandLine>cmd.exe /c wmic useraccount where &quot;name=&apos;Administrator&apos;&quot; set PasswordExpires=FALSE</CommandLine> + <Description>Disable password expiration for Administrator user</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c %SystemDrive%\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\enable-winrm.ps1</CommandLine> + <Description>Enable WinRM</Description> + <Order>6</Order> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c a:\disable-network-discovery.cmd</CommandLine> + <Description>Disable Network Discovery</Description> + <Order>7</Order> + </SynchronousCommand> + </FirstLogonCommands> + <AutoLogon> + <Password> + <Value>S3cr3t0!</Value> + <PlainText>true</PlainText> + </Password> + <Enabled>true</Enabled> + <Username>Administrator</Username> + </AutoLogon> + </component> + </settings> + <cpi:offlineImage cpi:source="wim:c:/windows-2004/sources/install.wim#Windows Server SERVERSTANDARDACORE" + xmlns:cpi="urn:schemas-microsoft-com:cpi" /> +</unattend> diff --git a/packer/ova/windows/windows-2019/autounattend.xml b/packer/ova/windows/windows-2019/autounattend.xml new file mode 100644 index 0000000..cd4aef2 --- /dev/null +++ b/packer/ova/windows/windows-2019/autounattend.xml @@ -0,0 +1,255 @@ +<?xml version="1.0" encoding="utf-8"?> +<!--************************************************* +Windows Server 2019 Answer File Generator +Created using Windows AFG found at: +;http://www.windowsafg.com + +Installation Notes: +- We currently assume your image is using a licesnsed media, and hard code product keys accordingly +- ProductKey: must be removed if using an eval version +- The OOBE and UserAccounts sections: might be removed for administrator details +- The Timezone in this file should match the location your using (otherwise you hit race conditions related to time.windows.com) +- We hard code an administrative passcode in here, which administrators may need to modify +- There are many other parameters in here which may need to be changed. Over time we will parameterize these as part of the image-builder process +**************************************************--> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> + <settings pass="windowsPE"> + <component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <DriverPaths> + <PathAndCredentials wcm:action="add" wcm:keyValue="A"> + <Path>a:\</Path> + </PathAndCredentials> + </DriverPaths> + </component> + <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <DiskConfiguration> + <Disk wcm:action="add"> + <CreatePartitions> + <CreatePartition wcm:action="add"> + <Order>1</Order> + <Size>350</Size> + <Type>Primary</Type> + </CreatePartition> + <CreatePartition wcm:action="add"> + <Order>2</Order> + <Extend>true</Extend> + <Type>Primary</Type> + </CreatePartition> + </CreatePartitions> + <ModifyPartitions> + <ModifyPartition wcm:action="add"> + <Format>NTFS</Format> + <Label>System</Label> + <Order>1</Order> + <PartitionID>1</PartitionID> + <TypeID>0x27</TypeID> + </ModifyPartition> + <ModifyPartition wcm:action="add"> + <Order>2</Order> + <PartitionID>2</PartitionID> + <Letter>C</Letter> + <Label>OS</Label> + <Format>NTFS</Format> + </ModifyPartition> + </ModifyPartitions> + <DiskID>0</DiskID> + <WillWipeDisk>true</WillWipeDisk> + </Disk> + </DiskConfiguration> + <ImageInstall> + <OSImage> + <InstallTo> + <DiskID>0</DiskID> + <PartitionID>2</PartitionID> + </InstallTo> + <InstallFrom> + <MetaData wcm:action="add"> + <Key>/IMAGE/NAME</Key> + <Value>Windows Server 2019 SERVERSTANDARDCORE</Value> + </MetaData> + </InstallFrom> + </OSImage> + </ImageInstall> + <UserData> + <AcceptEula>true</AcceptEula> + <FullName>Administrator</FullName> + <Organization>Organization</Organization> + <ProductKey> + <Key>N69G4-B89J2-4G8F4-WWYCC-J464C</Key> + <WillShowUI>OnError</WillShowUI> + </ProductKey> + </UserData> + <EnableFirewall>true</EnableFirewall> + </component> + <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SetupUILanguage> + <UILanguage>en-US</UILanguage> + </SetupUILanguage> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-US</UserLocale> + </component> + </settings> + <settings pass="offlineServicing"> + <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <EnableLUA>false</EnableLUA> + </component> + </settings> + <settings pass="generalize"> + <component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipRearm>1</SkipRearm> + </component> + </settings> + <settings pass="specialize"> + <component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <RunSynchronous> + <RunSynchronousCommand wcm:action="add"> + <WillReboot>Always</WillReboot> + <Path>e:\setup64.exe /s /v "/qb REBOOT=R ADDLOCAL=ALL"</Path> + <Order>1</Order> + </RunSynchronousCommand> + </RunSynchronous> + </component> + <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-US</UserLocale> + </component> + <component name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipAutoActivation>true</SkipAutoActivation> + </component> + <component name="Microsoft-Windows-SQMApi" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <CEIPEnabled>0</CEIPEnabled> + </component> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <ComputerName></ComputerName> + <ProductKey>N69G4-B89J2-4G8F4-WWYCC-J464C</ProductKey> + </component> + </settings> + <settings pass="oobeSystem"> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <AutoLogon> + <Password> + <Value>S3cr3t0!</Value> + <PlainText>true</PlainText> + </Password> + <Enabled>true</Enabled> + <Username>Administrator</Username> + </AutoLogon> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <Order>1</Order> + <Description>Set Execution Policy 64 Bit</Description> + <CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>2</Order> + <Description>Set Execution Policy 32 Bit</Description> + <CommandLine>%SystemDrive%\Windows\SysWOW64\cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v HideFileExt /t REG_DWORD /d 0 /f</CommandLine> + <Order>3</Order> + <Description>Show file extensions in Explorer</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\Console /v QuickEdit /t REG_DWORD /d 1 /f</CommandLine> + <Order>4</Order> + <Description>Enable QuickEdit mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v Start_ShowRun /t REG_DWORD /d 1 /f</CommandLine> + <Order>5</Order> + <Description>Show Run command in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v StartMenuAdminTools /t REG_DWORD /d 1 /f</CommandLine> + <Order>6</Order> + <Description>Show Administrative Tools in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateFileSizePercent /t REG_DWORD /d 0 /f</CommandLine> + <Order>7</Order> + <Description>Zero Hibernation File</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateEnabled /t REG_DWORD /d 0 /f</CommandLine> + <Order>8</Order> + <Description>Disable Hibernation Mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE</CommandLine> + <Order>9</Order> + <Description>Disable password expiration for Administrator user</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c %SystemDrive%\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\enable-winrm.ps1</CommandLine> + <Description>Enable WinRM</Description> + <Order>10</Order> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c a:\disable-network-discovery.cmd</CommandLine> + <Description>Disable Network Discovery</Description> + <Order>11</Order> + </SynchronousCommand> + </FirstLogonCommands> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Work</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + <SkipMachineOOBE>true</SkipMachineOOBE> + <SkipUserOOBE>true</SkipUserOOBE> + </OOBE> + <RegisteredOrganization>Organization</RegisteredOrganization> + <RegisteredOwner>Owner</RegisteredOwner> + <DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet> + <TimeZone>Pacific Standard Time</TimeZone> + <UserAccounts> + <AdministratorPassword> + <Value>S3cr3t0!</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Description>Administrator</Description> + <DisplayName>Administrator</DisplayName> + <Group>Administrators</Group> + <Name>Administrator</Name> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + </component> + </settings> +</unattend> diff --git a/packer/ova/windows/windows-2022/autounattend.xml b/packer/ova/windows/windows-2022/autounattend.xml new file mode 100644 index 0000000..5688b74 --- /dev/null +++ b/packer/ova/windows/windows-2022/autounattend.xml @@ -0,0 +1,256 @@ +<!--************************************************* +Windows Server 2019 Answer File Generator +Created using Windows AFG found at: +;http://www.windowsafg.com + +Installation Notes: +- We currently assume your image is using a licesnsed media, and hard code product keys accordingly +- ProductKey: must be removed if using an eval version +- The OOBE and UserAccounts sections: might be removed for administrator details +- The Timezone in this file should match the location your using (otherwise you hit race conditions related to time.windows.com) +- We hard code an administrative passcode in here, which administrators may need to modify +- There are many other parameters in here which may need to be changed. Over time we will parameterize these as part of the image-builder process +**************************************************--> + +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> + <settings pass="windowsPE"> + <component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <DriverPaths> + <PathAndCredentials wcm:action="add" wcm:keyValue="A"> + <Path>a:\</Path> + </PathAndCredentials> + </DriverPaths> + </component> + <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <DiskConfiguration> + <Disk wcm:action="add"> + <CreatePartitions> + <CreatePartition wcm:action="add"> + <Order>1</Order> + <Size>350</Size> + <Type>Primary</Type> + </CreatePartition> + <CreatePartition wcm:action="add"> + <Order>2</Order> + <Extend>true</Extend> + <Type>Primary</Type> + </CreatePartition> + </CreatePartitions> + <ModifyPartitions> + <ModifyPartition wcm:action="add"> + <Format>NTFS</Format> + <Label>System</Label> + <Order>1</Order> + <PartitionID>1</PartitionID> + <TypeID>0x27</TypeID> + </ModifyPartition> + <ModifyPartition wcm:action="add"> + <Order>2</Order> + <PartitionID>2</PartitionID> + <Letter>C</Letter> + <Label>OS</Label> + <Format>NTFS</Format> + </ModifyPartition> + </ModifyPartitions> + <DiskID>0</DiskID> + <WillWipeDisk>true</WillWipeDisk> + </Disk> + </DiskConfiguration> + <ImageInstall> + <OSImage> + <InstallTo> + <DiskID>0</DiskID> + <PartitionID>2</PartitionID> + </InstallTo> + <InstallFrom> + <MetaData wcm:action="add"> + <Key>/IMAGE/NAME</Key> + <Value>Windows Server 2022 SERVERSTANDARDCORE</Value> + </MetaData> + </InstallFrom> + </OSImage> + </ImageInstall> + <UserData> + <AcceptEula>true</AcceptEula> + <FullName>Administrator</FullName> + <Organization>Organization</Organization> + <ProductKey> + <Key>VDYBN-27WPP-V4HQT-9VMD4-VMK7H</Key> + <WillShowUI>OnError</WillShowUI> + </ProductKey> + </UserData> + <EnableFirewall>true</EnableFirewall> + </component> + <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SetupUILanguage> + <UILanguage>en-US</UILanguage> + </SetupUILanguage> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-US</UserLocale> + </component> + </settings> + <settings pass="offlineServicing"> + <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <EnableLUA>false</EnableLUA> + </component> + </settings> + <settings pass="generalize"> + <component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipRearm>1</SkipRearm> + </component> + </settings> + <settings pass="specialize"> + <component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <RunSynchronous> + <RunSynchronousCommand wcm:action="add"> + <WillReboot>Always</WillReboot> + <Path>e:\setup64.exe /s /v "/qb REBOOT=R ADDLOCAL=ALL"</Path> + <Order>1</Order> + </RunSynchronousCommand> + </RunSynchronous> + </component> + <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-US</UserLocale> + </component> + <component name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipAutoActivation>true</SkipAutoActivation> + </component> + <component name="Microsoft-Windows-SQMApi" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <CEIPEnabled>0</CEIPEnabled> + </component> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <ComputerName></ComputerName> + <ProductKey>VDYBN-27WPP-V4HQT-9VMD4-VMK7H</ProductKey> + </component> + </settings> + <settings pass="oobeSystem"> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <AutoLogon> + <Password> + <Value>S3cr3t0!</Value> + <PlainText>true</PlainText> + </Password> + <Enabled>true</Enabled> + <Username>Administrator</Username> + </AutoLogon> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <Order>1</Order> + <Description>Set Execution Policy 64 Bit</Description> + <CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>2</Order> + <Description>Set Execution Policy 32 Bit</Description> + <CommandLine>%SystemDrive%\Windows\SysWOW64\cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v HideFileExt /t REG_DWORD /d 0 /f</CommandLine> + <Order>3</Order> + <Description>Show file extensions in Explorer</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\Console /v QuickEdit /t REG_DWORD /d 1 /f</CommandLine> + <Order>4</Order> + <Description>Enable QuickEdit mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v Start_ShowRun /t REG_DWORD /d 1 /f</CommandLine> + <Order>5</Order> + <Description>Show Run command in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v StartMenuAdminTools /t REG_DWORD /d 1 /f</CommandLine> + <Order>6</Order> + <Description>Show Administrative Tools in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateFileSizePercent /t REG_DWORD /d 0 /f</CommandLine> + <Order>7</Order> + <Description>Zero Hibernation File</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateEnabled /t REG_DWORD /d 0 /f</CommandLine> + <Order>8</Order> + <Description>Disable Hibernation Mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE</CommandLine> + <Order>9</Order> + <Description>Disable password expiration for Administrator user</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c %SystemDrive%\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\enable-winrm.ps1</CommandLine> + <Description>Enable WinRM</Description> + <Order>10</Order> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c a:\disable-network-discovery.cmd</CommandLine> + <Description>Disable Network Discovery</Description> + <Order>11</Order> + </SynchronousCommand> + </FirstLogonCommands> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Work</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + <SkipMachineOOBE>true</SkipMachineOOBE> + <SkipUserOOBE>true</SkipUserOOBE> + </OOBE> + <RegisteredOrganization>Organization</RegisteredOrganization> + <RegisteredOwner>Owner</RegisteredOwner> + <DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet> + <TimeZone>Pacific Standard Time</TimeZone> + <UserAccounts> + <AdministratorPassword> + <Value>S3cr3t0!</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Description>Administrator</Description> + <DisplayName>Administrator</DisplayName> + <Group>Administrators</Group> + <Name>Administrator</Name> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + </component> + </settings> +</unattend> diff --git a/packer/powervs/centos-8.json b/packer/powervs/centos-8.json new file mode 100644 index 0000000..eb1c1a2 --- /dev/null +++ b/packer/powervs/centos-8.json @@ -0,0 +1,9 @@ +{ + "build_name": "centos-streams8", + "epel_rpm_gpg_key": "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm", + "source_cos_bucket": "power-oss-bucket", + "source_cos_object": "centos-streams-8.ova.gz", + "source_cos_region": "us-south", + "ssh_username": "root" +} diff --git a/packer/powervs/packer.json b/packer/powervs/packer.json new file mode 100644 index 0000000..b3fb49f --- /dev/null +++ b/packer/powervs/packer.json @@ -0,0 +1,107 @@ +{ + "builders": [ + { + "account_id": "{{user `account_id`}}", + "api_key": "{{user `apikey`}}", + "capture": { + "cos": { + "access_key": "{{user `capture_cos_access_key`}}", + "bucket": "{{user `capture_cos_bucket`}}", + "region": "{{user `capture_cos_region`}}", + "secret_key": "{{user `capture_cos_secret_key`}}" + }, + "name": "capibm-powervs-{{user `build_name`}}-{{user `kubernetes_rpm_version` | clean_resource_name}}-{{user `build_timestamp`}}" + }, + "instance_name": "capibm-{{user `build_name`}}-{{user `build_timestamp`}}", + "key_pair_name": "{{user `key_pair_name`}}", + "region": "{{user `region`}}", + "service_instance_id": "{{user `service_instance_id`}}", + "source": { + "cos": { + "bucket": "{{user `source_cos_bucket`}}", + "object": "{{user `source_cos_object`}}", + "region": "{{user `source_cos_region`}}" + } + }, + "ssh_private_key_file": "{{user `ssh_private_key_file`}}", + "ssh_timeout": "{{user `ssh_timeout`}}", + "ssh_username": "{{user `ssh_username`}}", + "type": "powervs", + "zone": "{{user `zone`}}" + } + ], + "post-processors": [ + { + "custom_data": { + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}" + }, + "output": "{{user `manifest_output`}}", + "type": "manifest" + } + ], + "provisioners": [ + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/node.yml", + "type": "ansible" + } + ], + "variables": { + "account_id": "", + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_scp_extra_args": "", + "ansible_user_vars": "", + "apikey": "", + "build_timestamp": "{{timestamp}}", + "capture_cos_access_key": "", + "capture_cos_bucket": "", + "capture_cos_region": "", + "capture_cos_secret_key": "", + "containerd_sha256": null, + "containerd_url": null, + "containerd_version": null, + "crictl_url": null, + "crictl_version": null, + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "key_pair_name": "", + "kubernetes_cni_deb_version": null, + "kubernetes_cni_http_source": null, + "kubernetes_cni_rpm_version": null, + "kubernetes_cni_semver": null, + "kubernetes_cni_source_type": null, + "kubernetes_container_registry": null, + "kubernetes_deb_gpg_key": null, + "kubernetes_deb_repo": null, + "kubernetes_deb_version": null, + "kubernetes_http_source": null, + "kubernetes_load_additional_imgs": null, + "kubernetes_rpm_gpg_check": null, + "kubernetes_rpm_gpg_key": null, + "kubernetes_rpm_repo": null, + "kubernetes_rpm_version": null, + "kubernetes_semver": null, + "kubernetes_source_type": null, + "manifest_output": "manifest.json", + "python_path": "", + "region": "", + "service_instance_id": "", + "ssh_private_key_file": "", + "ssh_timeout": "30m", + "zone": "" + } +} diff --git a/packer/qemu/OWNERS b/packer/qemu/OWNERS new file mode 100644 index 0000000..e651a62 --- /dev/null +++ b/packer/qemu/OWNERS @@ -0,0 +1,11 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - cluster-api-openstack-maintainers + - cluster-api-cloudstack-maintainers + +reviewers: + - cluster-api-openstack-maintainers + - image-builder-openstack-reviewers + - cluster-api-cloudstack-maintainers + - image-builder-cloudstack-reviewers diff --git a/packer/qemu/README.md b/packer/qemu/README.md new file mode 100644 index 0000000..6c51ee0 --- /dev/null +++ b/packer/qemu/README.md @@ -0,0 +1,6 @@ +To build an image using a specific version of Kubernetes use the "PACKER_FLAGS" env var like in the example below: + +PACKER_FLAGS="--var 'kubernetes_rpm_version=1.25.3-0' --var 'kubernetes_semver=v1.25.3' --var 'kubernetes_series=v1.25' --var 'kubernetes_deb_version=1.25.3-00'" make build-kubevirt-qemu-ubuntu-2004 + +P.S: In order to change disk size(defaults to 20GB as of 31.10.22) you can update PACKER_FLAGS with: +--var 'disk_size=<disk size in mb>' \ No newline at end of file diff --git a/packer/qemu/linux b/packer/qemu/linux new file mode 120000 index 0000000..2df9464 --- /dev/null +++ b/packer/qemu/linux @@ -0,0 +1 @@ +../ova/linux \ No newline at end of file diff --git a/packer/qemu/packer.json b/packer/qemu/packer.json new file mode 100644 index 0000000..58e4027 --- /dev/null +++ b/packer/qemu/packer.json @@ -0,0 +1,194 @@ +{ + "builders": [ + { + "accelerator": "{{user `accelerator`}}", + "boot_command": [ + "{{user `boot_command_prefix`}}", + "{{user `boot_media_path`}}", + "{{user `boot_command_suffix`}}" + ], + "boot_wait": "{{user `boot_wait`}}", + "cpus": "{{user `cpus`}}", + "disk_compression": "{{ user `disk_compression`}}", + "disk_discard": "{{user `disk_discard`}}", + "disk_interface": "virtio-scsi", + "disk_size": "{{user `disk_size`}}", + "firmware": "{{user `firmware`}}", + "format": "{{user `format`}}", + "headless": "{{user `headless`}}", + "http_directory": "{{user `http_directory`}}", + "iso_checksum": "{{user `iso_checksum_type`}}:{{user `iso_checksum`}}", + "iso_url": "{{user `iso_url`}}", + "memory": "{{user `memory`}}", + "net_device": "virtio-net", + "output_directory": "{{user `output_directory`}}", + "qemu_binary": "{{user `qemu_binary`}}", + "shutdown_command": "echo '{{user `ssh_password`}}' | sudo -S -E sh -c 'usermod -L {{user `ssh_username`}} && {{user `shutdown_command`}}'", + "ssh_password": "{{user `ssh_password`}}", + "ssh_timeout": "2h", + "ssh_username": "{{user `ssh_username`}}", + "type": "qemu", + "vm_name": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}" + } + ], + "post-processors": [ + { + "environment_vars": [ + "CUSTOM_POST_PROCESSOR={{user `custom_post_processor`}}" + ], + "inline": [ + "if [ \"$CUSTOM_POST_PROCESSOR\" != \"true\" ]; then exit 0; fi", + "{{user `custom_post_processor_command`}}" + ], + "name": "custom-post-processor", + "type": "shell-local" + }, + { + "environment_vars": [ + "OUTPUT_DIR={{user `output_directory`}}", + "ARTIFACT_NAME={{user `artifact_name`}}", + "KUBEVIRT={{user `kubevirt`}}" + ], + "inline": [ + "if [ \"$KUBEVIRT\" != \"true\" ]; then", + "exit 0", + "else", + "bash ./packer/qemu/scripts/build_kubevirt_image.sh {{user `build_name`}}-container-disk", + "fi" + ], + "name": "kubevirt", + "type": "shell-local" + } + ], + "provisioners": [ + { + "execute_command": "BUILD_NAME={{user `build_name`}}; if [[ \"${BUILD_NAME}\" == *\"flatcar\"* ]]; then sudo {{.Vars}} -S -E bash '{{.Path}}'; fi", + "script": "./packer/files/flatcar/scripts/bootstrap-flatcar.sh", + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'", + "KUBEVIRT={{user `kubevirt`}}" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/firstboot.yml", + "type": "ansible", + "user": "builder" + }, + { + "expect_disconnect": true, + "inline": [ + "sudo reboot now" + ], + "inline_shebang": "/bin/bash -e", + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'", + "KUBEVIRT={{user `kubevirt`}}" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/node.yml", + "type": "ansible", + "user": "builder" + }, + { + "arch": "{{user `goss_arch`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": true, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "ARCH": "amd64", + "OS": "{{user `distro_name` | lower}}", + "PROVIDER": "qemu", + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_deb_version": "{{ user `kubernetes_cni_deb_version` }}", + "kubernetes_cni_rpm_version": "{{ split (user `kubernetes_cni_rpm_version`) \"-\" 0 }}", + "kubernetes_cni_source_type": "{{user `kubernetes_cni_source_type`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver` | replace \"v\" \"\" 1}}", + "kubernetes_deb_version": "{{ user `kubernetes_deb_version` }}", + "kubernetes_rpm_version": "{{ split (user `kubernetes_rpm_version`) \"-\" 0 }}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "kubernetes_version": "{{user `kubernetes_semver` | replace \"v\" \"\" 1}}" + }, + "version": "{{user `goss_version`}}" + } + ], + "variables": { + "accelerator": "kvm", + "ansible_common_vars": "", + "ansible_extra_vars": "ansible_python_interpreter=/usr/bin/python3", + "ansible_user_vars": "", + "artifact_name": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", + "boot_media_path": "http://{{ .HTTPIP }}:{{ .HTTPPort }}", + "boot_wait": "10s", + "build_timestamp": "{{timestamp}}", + "containerd_sha256": null, + "containerd_url": "https://github.com/containerd/containerd/releases/download/v{{user `containerd_version`}}/cri-containerd-cni-{{user `containerd_version`}}-linux-amd64.tar.gz", + "containerd_version": null, + "cpus": "1", + "crictl_url": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-amd64.tar.gz", + "crictl_version": null, + "disk_compression": "false", + "disk_discard": "unmap", + "disk_size": "20480", + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "firmware": "", + "format": "qcow2", + "headless": "true", + "http_directory": "./packer/qemu/linux/{{user `distro_name`}}/http/", + "kubernetes_cni_deb_version": null, + "kubernetes_cni_http_source": null, + "kubernetes_cni_semver": null, + "kubernetes_cni_source_type": null, + "kubernetes_container_registry": null, + "kubernetes_deb_gpg_key": null, + "kubernetes_deb_repo": null, + "kubernetes_deb_version": null, + "kubernetes_http_source": null, + "kubernetes_load_additional_imgs": null, + "kubernetes_rpm_gpg_check": null, + "kubernetes_rpm_gpg_key": null, + "kubernetes_rpm_repo": null, + "kubernetes_rpm_version": null, + "kubernetes_semver": null, + "kubernetes_series": null, + "kubernetes_source_type": null, + "machine_id_mode": "444", + "memory": "2048", + "oem_id": "", + "output_directory": "./output/{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", + "python_path": "", + "qemu_binary": "qemu-system-x86_64", + "ssh_password": "builder", + "ssh_username": "builder" + } +} diff --git a/packer/qemu/qemu-centos-7.json b/packer/qemu/qemu-centos-7.json new file mode 100644 index 0000000..58f7130 --- /dev/null +++ b/packer/qemu/qemu-centos-7.json @@ -0,0 +1,15 @@ +{ + "ansible_extra_vars": "ansible_python_interpreter=/usr/bin/python extra_arguments =vvvv", + "boot_command_prefix": "<tab> text ks=", + "boot_command_suffix": "/7/ks.cfg<enter><wait>", + "build_name": "centos-7", + "distro_arch": "amd64", + "distro_name": "centos", + "distro_version": "7", + "guest_os_type": "centos7-64", + "iso_checksum": "07b94e6b1a0b0260b94c83d6bb76b26bf7a310dc78d7a9c7432809fb9bc6194a", + "iso_checksum_type": "sha256", + "iso_url": "https://mirrors.edge.kernel.org/centos/7.9.2009/isos/x86_64/CentOS-7-x86_64-Minimal-2009.iso", + "os_display_name": "CentOS 7", + "shutdown_command": "sys-unconfig" +} diff --git a/packer/qemu/qemu-flatcar.json b/packer/qemu/qemu-flatcar.json new file mode 100644 index 0000000..201dad6 --- /dev/null +++ b/packer/qemu/qemu-flatcar.json @@ -0,0 +1,25 @@ +{ + "ansible_extra_vars": "ansible_python_interpreter=/opt/bin/python oem_id={{user `oem_id`}}", + "boot_command_prefix": "sudo systemctl mask sshd.socket --now<enter>curl -sLo /tmp/ignition.json ", + "boot_command_suffix": "/bootstrap.json<enter>sed -i \"s|BUILDERPASSWORDHASH|$(mkpasswd -5 {{user `ssh_password`}})|\" /tmp/ignition.json<enter>sudo flatcar-install -d /dev/sda -C {{user `channel_name`}} -V {{user `release_version`}} -i /tmp/ignition.json<enter>sudo reboot<enter>", + "boot_wait": "120s", + "build_name": "flatcar-{{env `FLATCAR_CHANNEL`}}-{{env `FLATCAR_VERSION`}}", + "channel_name": "{{env `FLATCAR_CHANNEL`}}", + "crictl_source_type": "http", + "distro_name": "flatcar", + "guest_os_type": "linux-64", + "http_directory": "./packer/files/flatcar/ignition/", + "iso_checksum": "https://{{env `FLATCAR_CHANNEL`}}.release.flatcar-linux.net/amd64-usr/{{env `FLATCAR_VERSION`}}/flatcar_production_iso_image.iso.DIGESTS.asc", + "iso_checksum_type": "file", + "iso_url": "https://{{env `FLATCAR_CHANNEL`}}.release.flatcar-linux.net/amd64-usr/{{env `FLATCAR_VERSION`}}/flatcar_production_iso_image.iso", + "kubernetes_cni_source_type": "http", + "kubernetes_source_type": "http", + "oem_id": "{{env `OEM_ID`}}", + "os_display_name": "Flatcar Container Linux ({{env `FLATCAR_CHANNEL`}} channel release {{env `FLATCAR_VERSION`}})", + "python_path": "/opt/bin/builder-env/site-packages", + "release_version": "{{env `FLATCAR_VERSION`}}", + "shutdown_command": "shutdown -P now", + "systemd_prefix": "/etc/systemd", + "sysusr_prefix": "/opt", + "sysusrlocal_prefix": "/opt" +} diff --git a/packer/qemu/qemu-rhel-8.json b/packer/qemu/qemu-rhel-8.json new file mode 100644 index 0000000..7bb3adf --- /dev/null +++ b/packer/qemu/qemu-rhel-8.json @@ -0,0 +1,15 @@ +{ + "boot_command_prefix": "<tab> text inst.ks=", + "boot_command_suffix": "/8/ks.cfg<enter><wait>", + "build_name": "rhel-8", + "distro_name": "rhel", + "distro_version": "8", + "epel_rpm_gpg_key": "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8", + "guest_os_type": "RedHat_64", + "iso_checksum": "48f955712454c32718dcde858dea5aca574376a1d7a4b0ed6908ac0b85597811", + "iso_checksum_type": "sha256", + "iso_url": "rhel-8.4-x86_64-dvd.iso", + "os_display_name": "RHEL 8", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm", + "shutdown_command": "shutdown -P now" +} diff --git a/packer/qemu/qemu-rockylinux-8.json b/packer/qemu/qemu-rockylinux-8.json new file mode 100644 index 0000000..11d852d --- /dev/null +++ b/packer/qemu/qemu-rockylinux-8.json @@ -0,0 +1,16 @@ +{ + "boot_command_prefix": "<up><tab> text inst.ks=", + "boot_command_suffix": "/8/ks.cfg<enter><wait><enter>", + "build_name": "rockylinux-8", + "distro_arch": "amd64", + "distro_name": "rockylinux", + "distro_version": "8", + "epel_rpm_gpg_key": "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8", + "guest_os_type": "centos8-64", + "iso_checksum": "13c3e7fca1fd32df61695584baafc14fa28d62816d0813116d23744f5394624b", + "iso_checksum_type": "sha256", + "iso_url": "https://download.rockylinux.org/pub/rocky/8/isos/x86_64/Rocky-8.7-x86_64-minimal.iso", + "os_display_name": "RockyLinux 8", + "redhat_epel_rpm": "https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm", + "shutdown_command": "/sbin/halt -h -p" +} diff --git a/packer/qemu/qemu-ubuntu-1804.json b/packer/qemu/qemu-ubuntu-1804.json new file mode 100644 index 0000000..70bd6cd --- /dev/null +++ b/packer/qemu/qemu-ubuntu-1804.json @@ -0,0 +1,12 @@ +{ + "boot_command_prefix": "<esc><wait><esc><wait><enter><wait>/install/vmlinuz auto console-setup/ask_detect=false console-setup/layoutcode=us console-setup/modelcode=pc105 debconf/frontend=noninteractive debian-installer=en_US fb=false initrd=/install/initrd.gz kbd-chooser/method=us keyboard-configuration/layout=USA keyboard-configuration/variant=USA locale=en_US netcfg/get_domain=local netcfg/get_hostname=localhost grub-installer/bootdev=/dev/sda preseed/url=", + "boot_command_suffix": "/18.04/preseed.cfg -- <enter>", + "build_name": "ubuntu-1804", + "distro_name": "ubuntu", + "guest_os_type": "ubuntu-64", + "iso_checksum": "f5cbb8104348f0097a8e513b10173a07dbc6684595e331cb06f93f385d0aecf6", + "iso_checksum_type": "sha256", + "iso_url": "http://cdimage.ubuntu.com/releases/18.04/release/ubuntu-18.04.6-server-amd64.iso", + "os_display_name": "Ubuntu 18.04", + "shutdown_command": "shutdown -P now" +} diff --git a/packer/qemu/qemu-ubuntu-2004-efi.json b/packer/qemu/qemu-ubuntu-2004-efi.json new file mode 100644 index 0000000..c84597f --- /dev/null +++ b/packer/qemu/qemu-ubuntu-2004-efi.json @@ -0,0 +1,13 @@ +{ + "boot_command_prefix": "<esc><wait><esc><wait><enter><wait>linux /install/vmlinuz auto console-setup/ask_detect=false console-setup/layoutcode=us console-setup/modelcode=pc105 debconf/frontend=noninteractive debian-installer=en_US fb=false kbd-chooser/method=us keyboard-configuration/layout=USA keyboard-configuration/variant=USA locale=en_US netcfg/get_domain=local netcfg/get_hostname=localhost preseed/url=", + "boot_command_suffix": "/20.04/preseed-efi.cfg -- <wait><enter>initrd /install/initrd.gz<enter>boot<enter><wait>", + "build_name": "ubuntu-2004", + "distro_name": "ubuntu", + "firmware": "OVMF.fd", + "guest_os_type": "ubuntu-64", + "iso_checksum": "f11bda2f2caed8f420802b59f382c25160b114ccc665dbac9c5046e7fceaced2", + "iso_checksum_type": "sha256", + "iso_url": "http://cdimage.ubuntu.com/ubuntu-legacy-server/releases/20.04/release/ubuntu-20.04.1-legacy-server-amd64.iso", + "os_display_name": "Ubuntu 20.04", + "shutdown_command": "shutdown -P now" +} diff --git a/packer/qemu/qemu-ubuntu-2004.json b/packer/qemu/qemu-ubuntu-2004.json new file mode 100644 index 0000000..1a19728 --- /dev/null +++ b/packer/qemu/qemu-ubuntu-2004.json @@ -0,0 +1,12 @@ +{ + "boot_command_prefix": "<esc><wait><esc><wait><enter><wait>/install/vmlinuz auto console-setup/ask_detect=false console-setup/layoutcode=us console-setup/modelcode=pc105 debconf/frontend=noninteractive debian-installer=en_US fb=false initrd=/install/initrd.gz kbd-chooser/method=us keyboard-configuration/layout=USA keyboard-configuration/variant=USA locale=en_US netcfg/get_domain=local netcfg/get_hostname=localhost grub-installer/bootdev=/dev/sda preseed/url=", + "boot_command_suffix": "/20.04/preseed.cfg -- <wait><enter><wait>", + "build_name": "ubuntu-2004", + "distro_name": "ubuntu", + "guest_os_type": "ubuntu-64", + "iso_checksum": "f11bda2f2caed8f420802b59f382c25160b114ccc665dbac9c5046e7fceaced2", + "iso_checksum_type": "sha256", + "iso_url": "http://cdimage.ubuntu.com/ubuntu-legacy-server/releases/20.04/release/ubuntu-20.04.1-legacy-server-amd64.iso", + "os_display_name": "Ubuntu 20.04", + "shutdown_command": "shutdown -P now" +} diff --git a/packer/qemu/qemu-ubuntu-2204.json b/packer/qemu/qemu-ubuntu-2204.json new file mode 100644 index 0000000..2bfdfb2 --- /dev/null +++ b/packer/qemu/qemu-ubuntu-2204.json @@ -0,0 +1,12 @@ +{ + "boot_command_prefix": "c<wait>linux /casper/vmlinuz --- autoinstall ds='nocloud-net;s=http://{{ .HTTPIP }}:{{ .HTTPPort }}/22.04/'<enter><wait>initrd /casper/initrd<enter><wait>boot<enter>", + "build_name": "ubuntu-2204", + "distro_name": "ubuntu", + "guest_os_type": "ubuntu-64", + "iso_checksum": "10f19c5b2b8d6db711582e0e27f5116296c34fe4b313ba45f9b201a5007056cb", + "iso_checksum_type": "sha256", + "iso_url": "https://releases.ubuntu.com/22.04/ubuntu-22.04.1-live-server-amd64.iso", + "os_display_name": "Ubuntu 22.04", + "shutdown_command": "shutdown -P now", + "unmount_iso": "true" +} diff --git a/packer/qemu/scripts/build_kubevirt_image.sh b/packer/qemu/scripts/build_kubevirt_image.sh new file mode 100644 index 0000000..1fb0277 --- /dev/null +++ b/packer/qemu/scripts/build_kubevirt_image.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +echo "OUTPUT_DIR:$OUTPUT_DIR" +echo "ARTIFACT_NAME:$ARTIFACT_NAME" +echo "########" +env +cd $OUTPUT_DIR + +echo "FROM registry.access.redhat.com/ubi8/ubi:latest AS builder +ADD --chown=107:107 $ARTIFACT_NAME /disk/image.qcow2 + +FROM scratch +COPY --from=builder /disk/* /disk/" > ./kubevirt-Dockerfile + +docker build -f ./kubevirt-Dockerfile . -t $1 \ No newline at end of file diff --git a/packer/raw/OWNERS b/packer/raw/OWNERS new file mode 100644 index 0000000..f3b10c3 --- /dev/null +++ b/packer/raw/OWNERS @@ -0,0 +1,8 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - image-builder-raw-maintainers + +reviewers: + - image-builder-raw-maintainers + - image-builder-raw-reviewers diff --git a/packer/raw/linux/ubuntu/http/18.04/preseed.cfg b/packer/raw/linux/ubuntu/http/18.04/preseed.cfg new file mode 100644 index 0000000..37918c4 --- /dev/null +++ b/packer/raw/linux/ubuntu/http/18.04/preseed.cfg @@ -0,0 +1,15 @@ +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +d-i preseed/include string ../base/preseed.cfg diff --git a/packer/raw/linux/ubuntu/http/20.04/preseed-efi.cfg b/packer/raw/linux/ubuntu/http/20.04/preseed-efi.cfg new file mode 100644 index 0000000..b628670 --- /dev/null +++ b/packer/raw/linux/ubuntu/http/20.04/preseed-efi.cfg @@ -0,0 +1,15 @@ +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +d-i preseed/include string ../base/preseed-efi.cfg diff --git a/packer/raw/linux/ubuntu/http/20.04/preseed.cfg b/packer/raw/linux/ubuntu/http/20.04/preseed.cfg new file mode 100644 index 0000000..37918c4 --- /dev/null +++ b/packer/raw/linux/ubuntu/http/20.04/preseed.cfg @@ -0,0 +1,15 @@ +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +d-i preseed/include string ../base/preseed.cfg diff --git a/packer/raw/linux/ubuntu/http/base/preseed-efi.cfg b/packer/raw/linux/ubuntu/http/base/preseed-efi.cfg new file mode 100644 index 0000000..14cb400 --- /dev/null +++ b/packer/raw/linux/ubuntu/http/base/preseed-efi.cfg @@ -0,0 +1,128 @@ +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Configure the locale +d-i debian-installer/locale string en_US.utf8 +d-i console-setup/ask_detect boolean false +d-i console-setup/layout string us + +# Configure the clock +d-i time/zone string UTC +d-i clock-setup/utc-auto boolean true +d-i clock-setup/utc boolean true + +# Configure the keyboard +d-i kbd-chooser/method select American English + +# Configure networking +d-i netcfg/wireless_wep string + +# Select the kernel +d-i base-installer/kernel/override-image string linux-server + +# Configure a non-interactive install +debconf debconf/frontend select Noninteractive + +# Configure the base installation +d-i pkgsel/install-language-support boolean false +d-i pkgsel/language-packs multiselect +tasksel tasksel/first multiselect # standard, ubuntu-server + + +### Simple GPT configuration w/o LVM +d-i partman-auto/disk string /dev/sda + +d-i partman/alignment string cylinder +d-i partman/confirm_write_new_label boolean true +d-i partman-basicfilesystems/choose_label string gpt +d-i partman-basicfilesystems/default_label string gpt +d-i partman-partitioning/choose_label string gpt +d-i partman-partitioning/default_label string gpt +d-i partman/choose_label string gpt +d-i partman/default_label string gpt + +d-i partman-auto/method string regular +d-i partman-auto/choose_recipe select gpt-boot-root-swap +d-i partman-auto/expert_recipe string \ + gpt-boot-root-swap :: \ + 1 1 1 free \ + $bios_boot{ } \ + method{ biosgrub } . \ + 200 200 200 fat32 \ + $primary{ } \ + method{ efi } format{ } . \ + # 512 512 512 ext3 \ + # $primary{ } $bootable{ } \ + # method{ format } format{ } \ + # use_filesystem{ } filesystem{ ext3 } \ + # mountpoint{ /boot } . \ + 512 20000 -1 ext4 \ + $primary{ } \ + method{ format } format{ } \ + use_filesystem{ } filesystem{ ext4 } \ + mountpoint{ / } . + +d-i partman-partitioning/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true + +# Create the default user. +d-i passwd/user-fullname string builder +d-i passwd/username string builder +d-i passwd/user-password password builder +d-i passwd/user-password-again password builder +d-i user-setup/encrypt-home boolean false +d-i user-setup/allow-password-weak boolean true + +# Disable upgrading packages upon installation. +d-i pkgsel/upgrade select none +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i finish-install/reboot_in_progress note +d-i pkgsel/update-policy select none + +# Disable use of the apt mirror during base install +# This means only packages available in the ISO can be installed +d-i apt-setup/use_mirror boolean false + +# Disable the security repo as well (it's on by default) +d-i apt-setup/services-select multiselect none + +# Customize the list of packages installed. +d-i pkgsel/include string openssh-server + + +# Ensure questions about these packages do not bother the installer. +libssl1.1 libssl1.1/restart-without-asking boolean true +libssl1.1:amd64 libssl1.1/restart-without-asking boolean true +libssl1.1 libssl1.1/restart-services string +libssl1.1:amd64 libssl1.1/restart-services string + + +# This command runs after all other steps; it: +# 1. Ensures the "builder" user doesn't require a password to use sudo +# 2. Cleans up any packages that are no longer required +# 3. Cleans the package cache +# 4. Removes the cached list of packages +# 5. Disables swapfiles +# 6. Removes the existing swapfile +# 7. Removes the swapfile entry from /etc/fstab +d-i preseed/late_command string \ + echo 'builder ALL=(ALL) NOPASSWD: ALL' >/target/etc/sudoers.d/builder ; \ + in-target chmod 440 /etc/sudoers.d/builder ; \ + in-target swapoff -a ; \ + in-target rm -f /swapfile ; \ + in-target sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab ; \ + in-target rm -f /etc/udev/rules.d/70-persistent-net.rules diff --git a/packer/raw/linux/ubuntu/http/base/preseed.cfg b/packer/raw/linux/ubuntu/http/base/preseed.cfg new file mode 100644 index 0000000..e6ff331 --- /dev/null +++ b/packer/raw/linux/ubuntu/http/base/preseed.cfg @@ -0,0 +1,126 @@ +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Configure the locale +d-i debian-installer/locale string en_US.utf8 +d-i debian-installer/add-kernel-opts console=ttyS0 +d-i console-setup/ask_detect boolean false +d-i console-setup/layout string us + +# Configure the clock +d-i time/zone string UTC +d-i clock-setup/utc-auto boolean true +d-i clock-setup/utc boolean true + +# Configure the keyboard +d-i kbd-chooser/method select American English + +# Configure networking +d-i netcfg/wireless_wep string + +# Select the kernel +d-i base-installer/kernel/override-image string linux-server + +# Configure a non-interactive install +debconf debconf/frontend select Noninteractive + +# Configure the base installation +d-i pkgsel/install-language-support boolean false +d-i pkgsel/language-packs multiselect +tasksel tasksel/first multiselect # standard, ubuntu-server + +# Create a single-partition with no swap space. For more information +# on how partitioning is configured, please refer to +# https://github.com/xobs/debian-installer/blob/master/doc/devel/partman-auto-recipe.txt. +d-i partman-auto/method string regular +d-i partman-lvm/device_remove_lvm boolean true +d-i partman-md/device_remove_md boolean true +d-i partman-lvm/confirm boolean true +d-i partman-auto-lvm/guided_size string max + +# Again, this creates a single-partition with no swap. Kubernetes +# really dislikes the idea of anyone else managing memory. +d-i partman-auto/expert_recipe string \ + slash :: \ + 0 0 -1 ext4 \ + $primary{ } $bootable{ } \ + method{ format } format{ } \ + use_filesystem{ } filesystem{ ext4 } \ + mountpoint{ / } \ + . + +d-i partman-partitioning/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true +d-i partman-basicfilesystems/no_swap boolean false +d-i partman-md/confirm boolean true +d-i partman-partitioning/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true +d-i partman-md/confirm_nooverwrite boolean true +d-i partman-lvm/confirm_nooverwrite boolean true +d-i partman-partitioning/no_bootable_gpt_biosgrub boolean true +d-i partman-partitioning/no_bootable_gpt_efi boolean false +d-i partman-efi/non_efi_system boolean false + +# Create the default user. +d-i passwd/user-fullname string builder +d-i passwd/username string builder +d-i passwd/user-password password builder +d-i passwd/user-password-again password builder +d-i user-setup/encrypt-home boolean false +d-i user-setup/allow-password-weak boolean true + +# Disable upgrading packages upon installation. +d-i pkgsel/upgrade select none +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i finish-install/reboot_in_progress note +d-i pkgsel/update-policy select none + +# Disable use of the apt mirror during base install +# This means only packages available in the ISO can be installed +d-i apt-setup/use_mirror boolean false + +# Disable the security repo as well (it's on by default) +d-i apt-setup/services-select multiselect none + +# Customize the list of packages installed. +d-i pkgsel/include string openssh-server + + +# Ensure questions about these packages do not bother the installer. +libssl1.1 libssl1.1/restart-without-asking boolean true +libssl1.1:amd64 libssl1.1/restart-without-asking boolean true +libssl1.1 libssl1.1/restart-services string +libssl1.1:amd64 libssl1.1/restart-services string + + +# This command runs after all other steps; it: +# 1. Ensures the "builder" user doesn't require a password to use sudo +# 2. Cleans up any packages that are no longer required +# 3. Cleans the package cache +# 4. Removes the cached list of packages +# 5. Disables swapfiles +# 6. Removes the existing swapfile +# 7. Removes the swapfile entry from /etc/fstab +d-i preseed/late_command string \ + echo 'builder ALL=(ALL) NOPASSWD: ALL' >/target/etc/sudoers.d/builder ; \ + in-target chmod 440 /etc/sudoers.d/builder ; \ + in-target swapoff -a ; \ + in-target rm -f /swapfile ; \ + in-target sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab ; \ + in-target rm -f /etc/udev/rules.d/70-persistent-net.rules diff --git a/packer/raw/packer.json b/packer/raw/packer.json new file mode 100644 index 0000000..315b242 --- /dev/null +++ b/packer/raw/packer.json @@ -0,0 +1,179 @@ +{ + "builders": [ + { + "accelerator": "{{user `accelerator`}}", + "boot_command": [ + "{{user `boot_command_prefix`}}", + "{{user `boot_media_path`}}", + "{{user `boot_command_suffix`}}" + ], + "boot_wait": "{{user `boot_wait`}}", + "cpus": "{{user `cpus`}}", + "disk_compression": "{{ user `disk_compression`}}", + "disk_discard": "{{user `disk_discard`}}", + "disk_interface": "virtio-scsi", + "disk_size": "{{user `disk_size`}}", + "firmware": "{{user `firmware`}}", + "format": "{{user `format`}}", + "headless": "{{user `headless`}}", + "http_directory": "{{user `http_directory`}}", + "iso_checksum": "{{user `iso_checksum_type`}}:{{user `iso_checksum`}}", + "iso_url": "{{user `iso_url`}}", + "memory": "{{user `memory`}}", + "net_device": "virtio-net", + "output_directory": "{{user `output_directory`}}", + "qemu_binary": "{{user `qemu_binary`}}", + "shutdown_command": "echo '{{user `ssh_password`}}' | sudo -S -E sh -c 'usermod -L {{user `ssh_username`}} && {{user `shutdown_command`}}'", + "ssh_password": "{{user `ssh_password`}}", + "ssh_timeout": "2h", + "ssh_username": "{{user `ssh_username`}}", + "type": "qemu", + "vm_name": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}" + } + ], + "post-processors": [ + { + "output": "./output/{{user `build_name`}}-kube-{{user `kubernetes_semver`}}.gz", + "type": "compress" + }, + { + "environment_vars": [ + "CUSTOM_POST_PROCESSOR={{user `custom_post_processor`}}" + ], + "inline": [ + "if [ \"$CUSTOM_POST_PROCESSOR\" != \"true\" ]; then exit 0; fi", + "{{user `custom_post_processor_command`}}" + ], + "name": "custom-post-processor", + "type": "shell-local" + } + ], + "provisioners": [ + { + "execute_command": "BUILD_NAME={{user `build_name`}}; if [[ \"${BUILD_NAME}\" == *\"flatcar\"* ]]; then sudo {{.Vars}} -S -E bash '{{.Path}}'; fi", + "script": "./packer/files/flatcar/scripts/bootstrap-flatcar.sh", + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} {{user `ansible_common_ssh_args`}}'" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/firstboot.yml", + "type": "ansible", + "user": "builder" + }, + { + "expect_disconnect": true, + "inline": [ + "sudo reboot now" + ], + "inline_shebang": "/bin/bash -e", + "type": "shell" + }, + { + "ansible_env_vars": [ + "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} -o IdentitiesOnly=yes'" + ], + "extra_arguments": [ + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}", + "--scp-extra-args", + "{{user `ansible_scp_extra_args`}}" + ], + "playbook_file": "./ansible/node.yml", + "type": "ansible", + "user": "builder" + }, + { + "arch": "{{user `goss_arch`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": true, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "ARCH": "amd64", + "OS": "{{user `distro_name` | lower}}", + "PROVIDER": "raw", + "containerd_version": "{{user `containerd_version`}}", + "kubernetes_cni_deb_version": "{{ user `kubernetes_cni_deb_version` }}", + "kubernetes_cni_rpm_version": "{{ split (user `kubernetes_cni_rpm_version`) \"-\" 0 }}", + "kubernetes_cni_source_type": "{{user `kubernetes_cni_source_type`}}", + "kubernetes_cni_version": "{{user `kubernetes_cni_semver` | replace \"v\" \"\" 1}}", + "kubernetes_deb_version": "{{ user `kubernetes_deb_version` }}", + "kubernetes_rpm_version": "{{ split (user `kubernetes_rpm_version`) \"-\" 0 }}", + "kubernetes_source_type": "{{user `kubernetes_source_type`}}", + "kubernetes_version": "{{user `kubernetes_semver` | replace \"v\" \"\" 1}}" + }, + "version": "{{user `goss_version`}}" + } + ], + "variables": { + "accelerator": "kvm", + "ansible_common_vars": "", + "ansible_extra_vars": "ansible_python_interpreter=/usr/bin/python3", + "ansible_scp_extra_args": "", + "ansible_user_vars": "", + "boot_media_path": "http://{{ .HTTPIP }}:{{ .HTTPPort }}", + "boot_wait": "10s", + "build_timestamp": "{{timestamp}}", + "containerd_sha256": null, + "containerd_url": "https://github.com/containerd/containerd/releases/download/v{{user `containerd_version`}}/cri-containerd-cni-{{user `containerd_version`}}-linux-amd64.tar.gz", + "containerd_version": null, + "cpus": "1", + "crictl_url": "https://github.com/kubernetes-sigs/cri-tools/releases/download/v{{user `crictl_version`}}/crictl-v{{user `crictl_version`}}-linux-amd64.tar.gz", + "crictl_version": null, + "disk_compression": "false", + "disk_discard": "unmap", + "disk_size": "6144", + "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", + "firmware": "", + "format": "raw", + "headless": "true", + "http_directory": "./packer/raw/linux/{{user `distro_name`}}/http/", + "kubernetes_cni_deb_version": null, + "kubernetes_cni_http_source": null, + "kubernetes_cni_semver": null, + "kubernetes_cni_source_type": null, + "kubernetes_container_registry": null, + "kubernetes_deb_gpg_key": null, + "kubernetes_deb_repo": null, + "kubernetes_deb_version": null, + "kubernetes_http_source": null, + "kubernetes_load_additional_imgs": null, + "kubernetes_rpm_gpg_check": null, + "kubernetes_rpm_gpg_key": null, + "kubernetes_rpm_repo": null, + "kubernetes_rpm_version": null, + "kubernetes_semver": null, + "kubernetes_series": null, + "kubernetes_source_type": null, + "machine_id_mode": "444", + "memory": "2048", + "output_directory": "./output/{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", + "python_path": "", + "qemu_binary": "qemu-system-x86_64", + "ssh_password": "builder", + "ssh_username": "builder" + } +} diff --git a/packer/raw/raw-flatcar.json b/packer/raw/raw-flatcar.json new file mode 100644 index 0000000..a517a9e --- /dev/null +++ b/packer/raw/raw-flatcar.json @@ -0,0 +1,24 @@ +{ + "ansible_extra_vars": "ansible_python_interpreter=/opt/bin/python", + "boot_command_prefix": "sudo systemctl mask sshd.socket --now<enter>curl -sLo /tmp/ignition.json ", + "boot_command_suffix": "/bootstrap.json<enter>sed -i \"s|BUILDERPASSWORDHASH|$(mkpasswd -5 {{user `ssh_password`}})|\" /tmp/ignition.json<enter>sudo flatcar-install -d /dev/sda -C {{user `channel_name`}} -V {{user `release_version`}} -i /tmp/ignition.json<enter>sudo reboot<enter>", + "boot_wait": "120s", + "build_name": "flatcar-{{env `FLATCAR_CHANNEL`}}-{{env `FLATCAR_VERSION`}}", + "channel_name": "{{env `FLATCAR_CHANNEL`}}", + "crictl_source_type": "http", + "distro_name": "flatcar", + "guest_os_type": "linux-64", + "http_directory": "./packer/files/flatcar/ignition/", + "iso_checksum": "https://{{env `FLATCAR_CHANNEL`}}.release.flatcar-linux.net/amd64-usr/{{env `FLATCAR_VERSION`}}/flatcar_production_iso_image.iso.DIGESTS.asc", + "iso_checksum_type": "file", + "iso_url": "https://{{env `FLATCAR_CHANNEL`}}.release.flatcar-linux.net/amd64-usr/{{env `FLATCAR_VERSION`}}/flatcar_production_iso_image.iso", + "kubernetes_cni_source_type": "http", + "kubernetes_source_type": "http", + "os_display_name": "Flatcar Container Linux ({{env `FLATCAR_CHANNEL`}} channel release {{env `FLATCAR_VERSION`}})", + "python_path": "/opt/bin/builder-env/site-packages", + "release_version": "{{env `FLATCAR_VERSION`}}", + "shutdown_command": "shutdown -P now", + "systemd_prefix": "/etc/systemd", + "sysusr_prefix": "/opt", + "sysusrlocal_prefix": "/opt" +} diff --git a/packer/raw/raw-ubuntu-1804.json b/packer/raw/raw-ubuntu-1804.json new file mode 100644 index 0000000..46ef18d --- /dev/null +++ b/packer/raw/raw-ubuntu-1804.json @@ -0,0 +1,13 @@ +{ + "boot_command_prefix": "<esc><wait><esc><wait><enter><wait>/install/vmlinuz auto console-setup/ask_detect=false console-setup/layoutcode=us console-setup/modelcode=pc105 debconf/frontend=noninteractive debian-installer=en_US fb=false initrd=/install/initrd.gz kbd-chooser/method=us keyboard-configuration/layout=USA keyboard-configuration/variant=USA locale=en_US netcfg/get_domain=local netcfg/get_hostname=localhost grub-installer/bootdev=/dev/sda preseed/url=", + "boot_command_suffix": "/18.04/preseed.cfg -- <enter>", + "build_name": "ubuntu-1804", + "build_target": "raw", + "distro_name": "ubuntu", + "guest_os_type": "ubuntu-64", + "iso_checksum": "f5cbb8104348f0097a8e513b10173a07dbc6684595e331cb06f93f385d0aecf6", + "iso_checksum_type": "sha256", + "iso_url": "http://cdimage.ubuntu.com/releases/18.04/release/ubuntu-18.04.6-server-amd64.iso", + "os_display_name": "Ubuntu 18.04", + "shutdown_command": "shutdown -P now" +} diff --git a/packer/raw/raw-ubuntu-2004-efi.json b/packer/raw/raw-ubuntu-2004-efi.json new file mode 100644 index 0000000..7ed3ac8 --- /dev/null +++ b/packer/raw/raw-ubuntu-2004-efi.json @@ -0,0 +1,14 @@ +{ + "boot_command_prefix": "<esc><wait><esc><wait><enter><wait>linux /install/vmlinuz auto console-setup/ask_detect=false console-setup/layoutcode=us console-setup/modelcode=pc105 debconf/frontend=noninteractive debian-installer=en_US fb=false kbd-chooser/method=us keyboard-configuration/layout=USA keyboard-configuration/variant=USA locale=en_US netcfg/get_domain=local netcfg/get_hostname=localhost preseed/url=", + "boot_command_suffix": "/20.04/preseed-efi.cfg -- <wait><enter>initrd /install/initrd.gz<enter>boot<enter><wait>", + "build_name": "ubuntu-2004", + "build_target": "raw", + "distro_name": "ubuntu", + "firmware": "OVMF.fd", + "guest_os_type": "ubuntu-64", + "iso_checksum": "f11bda2f2caed8f420802b59f382c25160b114ccc665dbac9c5046e7fceaced2", + "iso_checksum_type": "sha256", + "iso_url": "http://cdimage.ubuntu.com/ubuntu-legacy-server/releases/20.04/release/ubuntu-20.04.1-legacy-server-amd64.iso", + "os_display_name": "Ubuntu 20.04", + "shutdown_command": "shutdown -P now" +} diff --git a/packer/raw/raw-ubuntu-2004.json b/packer/raw/raw-ubuntu-2004.json new file mode 100644 index 0000000..9e78e63 --- /dev/null +++ b/packer/raw/raw-ubuntu-2004.json @@ -0,0 +1,13 @@ +{ + "boot_command_prefix": "<esc><wait><esc><wait><enter><wait>/install/vmlinuz auto console-setup/ask_detect=false console-setup/layoutcode=us console-setup/modelcode=pc105 debconf/frontend=noninteractive debian-installer=en_US fb=false initrd=/install/initrd.gz kbd-chooser/method=us keyboard-configuration/layout=USA keyboard-configuration/variant=USA locale=en_US netcfg/get_domain=local netcfg/get_hostname=localhost grub-installer/bootdev=/dev/sda preseed/url=", + "boot_command_suffix": "/20.04/preseed.cfg -- <wait><enter><wait>", + "build_name": "ubuntu-2004", + "build_target": "raw", + "distro_name": "ubuntu", + "guest_os_type": "ubuntu-64", + "iso_checksum": "f11bda2f2caed8f420802b59f382c25160b114ccc665dbac9c5046e7fceaced2", + "iso_checksum_type": "sha256", + "iso_url": "http://cdimage.ubuntu.com/ubuntu-legacy-server/releases/20.04/release/ubuntu-20.04.1-legacy-server-amd64.iso", + "os_display_name": "Ubuntu 20.04", + "shutdown_command": "shutdown -P now" +} diff --git a/packer/vbox/OWNERS b/packer/vbox/OWNERS new file mode 100644 index 0000000..7890663 --- /dev/null +++ b/packer/vbox/OWNERS @@ -0,0 +1,5 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - image-builder-windows-maintainers + diff --git a/packer/vbox/packer-common.json b/packer/vbox/packer-common.json new file mode 100644 index 0000000..052d06c --- /dev/null +++ b/packer/vbox/packer-common.json @@ -0,0 +1,8 @@ +{ + "boot_wait": "10s", + "cpu": "4", + "memory": "8192", + "ssh_password": "vagrant", + "ssh_timeout": "60m", + "ssh_username": "vagrant" +} diff --git a/packer/vbox/packer-windows.json b/packer/vbox/packer-windows.json new file mode 100644 index 0000000..782c3e1 --- /dev/null +++ b/packer/vbox/packer-windows.json @@ -0,0 +1,125 @@ +{ + "builders": [ + { + "boot_wait": "{{user `boot_wait`}}", + "communicator": "winrm", + "cpus": "{{user `cpu`}}", + "disk_size": "{{user `disk_size`}}", + "floppy_files": [ + "./packer/vbox/windows/{{user `build_name`}}/autounattend.xml", + "./packer/vbox/windows/enable-winrm.ps1", + "./packer/vbox/windows/sysprep.ps1" + ], + "guest_additions_mode": "disable", + "guest_os_type": "{{user `local_guest_os_type`}}", + "iso_checksum": "{{user `iso_checksum`}}", + "iso_urls": [ + "{{user `os_iso_url`}}" + ], + "memory": "{{user `memory`}}", + "name": "virtualbox-iso", + "output_directory": "{{user `output_dir`}}", + "shutdown_command": "powershell A:/sysprep.ps1", + "shutdown_timeout": "1h", + "type": "virtualbox-iso", + "vm_name": "{{user `build_version`}}", + "winrm_password": "S3cr3t0!", + "winrm_timeout": "4h", + "winrm_username": "Administrator" + } + ], + "post-processors": [ + { + "keep_input_artifact": true, + "output": "./output/windows-2019.box", + "type": "vagrant", + "vagrantfile_template": "./packer/vbox/vagrantfile-windows_2019.template" + } + ], + "provisioners": [ + { + "extra_arguments": [ + "-e", + "ansible_winrm_scheme=http", + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}" + ], + "playbook_file": "ansible/windows/node_windows.yml", + "type": "ansible", + "use_proxy": false, + "user": "Administrator" + }, + { + "restart_check_command": "powershell -command \"& {if ((get-content C:\\ProgramData\\lastboot.txt) -eq (Get-WmiObject win32_operatingsystem).LastBootUpTime) {Write-Output 'Sleeping for 600 seconds to wait for reboot'; start-sleep 600} else {Write-Output 'Reboot complete'}}\"", + "restart_command": "powershell \"& {(Get-WmiObject win32_operatingsystem).LastBootUpTime > C:\\ProgramData\\lastboot.txt; Restart-Computer -force}\"", + "type": "windows-restart" + }, + { + "arch": "{{user `goss_arch`}}", + "download_path": "{{user `goss_download_path`}}", + "format": "{{user `goss_format`}}", + "format_options": "{{user `goss_format_options`}}", + "goss_file": "{{user `goss_entry_file`}}", + "inspect": "{{user `goss_inspect_mode`}}", + "remote_folder": "{{user `goss_remote_folder`}}", + "remote_path": "{{user `goss_remote_path`}}", + "skip_install": "{{user `goss_skip_install`}}", + "target_os": "Windows", + "tests": [ + "{{user `goss_tests_dir`}}" + ], + "type": "goss", + "url": "{{user `goss_url`}}", + "use_sudo": false, + "vars_env": { + "GOSS_MAX_CONCURRENT": "1", + "GOSS_USE_ALPHA": "1" + }, + "vars_file": "{{user `goss_vars_file`}}", + "vars_inline": { + "OS": "{{user `distro_name` | lower}}", + "PROVIDER": "virtualbox", + "containerd_version": "{{user `containerd_version`}}", + "distribution_version": "{{user `distro_version`}}", + "docker_ee_version": "{{user `docker_ee_version`}}", + "kubernetes_version": "{{user `kubernetes_semver`}}", + "pause_image": "{{user `pause_image`}}", + "runtime": "{{user `runtime`}}", + "ssh_source_url": "{{user `ssh_source_url`}}" + }, + "version": "{{user `goss_version`}}" + } + ], + "variables": { + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_user_vars": "", + "build_name": null, + "build_timestamp": "{{timestamp}}", + "build_version": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", + "cloudbase_init_url": "https://github.com/cloudbase/cloudbase-init/releases/download/{{user `cloudbase_init_version`}}/CloudbaseInitSetup_{{user `cloudbase_init_version` | replace_all `.` `_` }}_x64.msi", + "cloudbase_plugins": "cloudbaseinit.plugins.windows.createuser.CreateUserPlugin, cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin, cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin, cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin, cloudbaseinit.plugins.common.mtu.MTUPlugin, cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin, cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin, cloudbaseinit.plugins.common.userdata.UserDataPlugin, cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin, cloudbaseinit.plugins.windows.createuser.CreateUserPlugin, cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin", + "cloudbase_plugins_unattend": "cloudbaseinit.plugins.common.mtu.MTUPlugin", + "containerd_sha256": null, + "containerd_url": "", + "containerd_version": null, + "disable_hypervisor": null, + "disk_size": "81920", + "ib_version": "{{env `IB_VERSION`}}", + "kubernetes_base_url": "https://kubernetesreleases.blob.core.windows.net/kubernetes/{{user `kubernetes_semver`}}/binaries/node/windows/{{user `kubernetes_goarch`}}", + "kubernetes_http_package_url": "", + "kubernetes_typed_version": "kube-{{user `kubernetes_semver`}}", + "manifest_output": "manifest.json", + "netbios_host_name_compatibility": null, + "nssm_url": null, + "output_dir": "./output/{{user `build_version`}}", + "prepull": null, + "windows_service_manager": null, + "windows_updates_categories": null, + "windows_updates_kbs": null + } +} diff --git a/packer/vbox/vagrantfile-windows_2019.template b/packer/vbox/vagrantfile-windows_2019.template new file mode 100644 index 0000000..ac77601 --- /dev/null +++ b/packer/vbox/vagrantfile-windows_2019.template @@ -0,0 +1,24 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.require_version ">= 1.6.2" + +Vagrant.configure("2") do |config| + config.vm.define "vagrant-windows-2019" + config.vm.box = "windows_2019" + config.vm.communicator = "winrm" + + config.vm.guest = :windows + config.windows.halt_timeout = 15 + + config.vm.network :forwarded_port, guest: 3389, host: 3389, id: "rdp", auto_correct: true + + config.vm.provider :virtualbox do |v, override| + #v.gui = true + v.customize ["modifyvm", :id, "--memory", 6144] + v.customize ["modifyvm", :id, "--cpus", 2] + v.customize ["modifyvm", :id, "--vram", 128] + v.customize ["modifyvm", :id, "--clipboard", "bidirectional"] + v.customize ["setextradata", "global", "GUI/SuppressMessages", "all" ] + end +end diff --git a/packer/vbox/windows-2019.json b/packer/vbox/windows-2019.json new file mode 100644 index 0000000..2dc1f45 --- /dev/null +++ b/packer/vbox/windows-2019.json @@ -0,0 +1,10 @@ +{ + "build_name": "windows-2019", + "distro_arch": "amd64", + "distro_name": "windows", + "distro_version": "2019", + "iso_checksum": "none", + "local_guest_os_type": "windows9srv-64", + "os_display_name": "Windows Server 2019", + "os_iso_url": "file:/path/en_windows_server_2019_x64_dvd_4cb967d8.iso" +} diff --git a/packer/vbox/windows/enable-winrm.ps1 b/packer/vbox/windows/enable-winrm.ps1 new file mode 100644 index 0000000..e6f30b1 --- /dev/null +++ b/packer/vbox/windows/enable-winrm.ps1 @@ -0,0 +1,18 @@ + +$NetworkListManager = [Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]"{DCB00C01-570F-4A9B-8D69-199FDBA5723B}")) +$Connections = $NetworkListManager.GetNetworkConnections() +$Connections | ForEach-Object { $_.GetNetwork().SetCategory(1) } + +Enable-PSRemoting -Force +winrm quickconfig -q +winrm quickconfig -transport:http +winrm set winrm/config '@{MaxTimeoutms="1800000"}' +winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="800"}' +winrm set winrm/config/service '@{AllowUnencrypted="true"}' +winrm set winrm/config/service/auth '@{Basic="true"}' +winrm set winrm/config/client/auth '@{Basic="true"}' +winrm set winrm/config/listener?Address=*+Transport=HTTP '@{Port="5985"}' +netsh advfirewall firewall set rule group="Windows Remote Administration" new enable=yes +netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=allow +Set-Service winrm -startuptype "auto" +Restart-Service winrm diff --git a/packer/vbox/windows/sysprep.ps1 b/packer/vbox/windows/sysprep.ps1 new file mode 100644 index 0000000..7efb732 --- /dev/null +++ b/packer/vbox/windows/sysprep.ps1 @@ -0,0 +1,13 @@ +Write-Output '>>> Sysprepping VM ...' +if( Test-Path $Env:SystemRoot\system32\Sysprep\unattend.xml ) { + Remove-Item $Env:SystemRoot\system32\Sysprep\unattend.xml -Force +} +$unattendedXml = "$ENV:ProgramFiles\Cloudbase Solutions\Cloudbase-Init\conf\Unattend.xml" +$FileExists = Test-Path $unattendedXml +If ($FileExists -eq $True) { + # Use the Cloudbase-init provided unattend file during install + Write-Output "Using cloudbase-init unattend file for sysprep: $unattendedXml" + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /shutdown /quiet /unattend:$unattendedXml +}else { + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /shutdown /quiet +} diff --git a/packer/vbox/windows/windows-2019/autounattend.xml b/packer/vbox/windows/windows-2019/autounattend.xml new file mode 100644 index 0000000..396caa8 --- /dev/null +++ b/packer/vbox/windows/windows-2019/autounattend.xml @@ -0,0 +1,250 @@ +<!--************************************************* +Windows Server 2019 Answer File Generator +Created using Windows AFG found at: +;http://www.windowsafg.com + +Installation Notes: +- We currently assume your image is using a licesnsed media, and hard code product keys accordingly +- ProductKey: must be removed if using an eval version +- The OOBE and UserAccounts sections: might be removed for administrator details +- The Timezone in this file should match the location your using (otherwise you hit race conditions related to time.windows.com) +- We hard code an administrative passcode in here, which administrators may need to modify +- There are many other parameters in here which may need to be changed. Over time we will parameterize these as part of the image-builder process +**************************************************--> + +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> + <settings pass="windowsPE"> + <component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <DriverPaths> + <PathAndCredentials wcm:action="add" wcm:keyValue="A"> + <Path>a:\</Path> + </PathAndCredentials> + </DriverPaths> + </component> + <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <DiskConfiguration> + <Disk wcm:action="add"> + <CreatePartitions> + <CreatePartition wcm:action="add"> + <Order>1</Order> + <Size>350</Size> + <Type>Primary</Type> + </CreatePartition> + <CreatePartition wcm:action="add"> + <Order>2</Order> + <Extend>true</Extend> + <Type>Primary</Type> + </CreatePartition> + </CreatePartitions> + <ModifyPartitions> + <ModifyPartition wcm:action="add"> + <Format>NTFS</Format> + <Label>System</Label> + <Order>1</Order> + <PartitionID>1</PartitionID> + <TypeID>0x27</TypeID> + </ModifyPartition> + <ModifyPartition wcm:action="add"> + <Order>2</Order> + <PartitionID>2</PartitionID> + <Letter>C</Letter> + <Label>OS</Label> + <Format>NTFS</Format> + </ModifyPartition> + </ModifyPartitions> + <DiskID>0</DiskID> + <WillWipeDisk>true</WillWipeDisk> + </Disk> + </DiskConfiguration> + <ImageInstall> + <OSImage> + <InstallTo> + <DiskID>0</DiskID> + <PartitionID>2</PartitionID> + </InstallTo> + <InstallFrom> + <MetaData wcm:action="add"> + <Key>/IMAGE/NAME</Key> + <Value>Windows Server 2019 SERVERSTANDARDCORE</Value> + </MetaData> + </InstallFrom> + </OSImage> + </ImageInstall> + <UserData> + <AcceptEula>true</AcceptEula> + <FullName>Administrator</FullName> + <Organization>Organization</Organization> + <!-- <ProductKey> --> + <!-- <Key>N69G4-B89J2-4G8F4-WWYCC-J464C</Key> --> + <!-- <WillShowUI>OnError</WillShowUI> --> + <!-- </ProductKey> --> + </UserData> + <EnableFirewall>true</EnableFirewall> + </component> + <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SetupUILanguage> + <UILanguage>en-US</UILanguage> + </SetupUILanguage> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-US</UserLocale> + </component> + </settings> + <settings pass="offlineServicing"> + <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <EnableLUA>false</EnableLUA> + </component> + </settings> + <settings pass="generalize"> + <component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipRearm>1</SkipRearm> + </component> + </settings> + <settings pass="specialize"> + <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-US</UserLocale> + </component> + <component name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipAutoActivation>true</SkipAutoActivation> + </component> + <component name="Microsoft-Windows-SQMApi" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <CEIPEnabled>0</CEIPEnabled> + </component> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <ComputerName></ComputerName> + <ProductKey>N69G4-B89J2-4G8F4-WWYCC-J464C</ProductKey> + </component> + </settings> + <settings pass="oobeSystem"> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" + xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <AutoLogon> + <Password> + <Value>S3cr3t0!</Value> + <PlainText>true</PlainText> + </Password> + <Enabled>true</Enabled> + <Username>Administrator</Username> + </AutoLogon> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <Order>1</Order> + <Description>Set Execution Policy 64 Bit</Description> + <CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>2</Order> + <Description>Set Execution Policy 32 Bit</Description> + <CommandLine>%SystemDrive%\Windows\SysWOW64\cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v HideFileExt /t REG_DWORD /d 0 /f</CommandLine> + <Order>3</Order> + <Description>Show file extensions in Explorer</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\Console /v QuickEdit /t REG_DWORD /d 1 /f</CommandLine> + <Order>4</Order> + <Description>Enable QuickEdit mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v Start_ShowRun /t REG_DWORD /d 1 /f</CommandLine> + <Order>5</Order> + <Description>Show Run command in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v StartMenuAdminTools /t REG_DWORD /d 1 /f</CommandLine> + <Order>6</Order> + <Description>Show Administrative Tools in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateFileSizePercent /t REG_DWORD /d 0 /f</CommandLine> + <Order>7</Order> + <Description>Zero Hibernation File</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateEnabled /t REG_DWORD /d 0 /f</CommandLine> + <Order>8</Order> + <Description>Disable Hibernation Mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE</CommandLine> + <Order>9</Order> + <Description>Disable password expiration for Administrator user</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c a:\install-vm-tools.cmd</CommandLine> + <Order>10</Order> + <Description>Install VMware Tools</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c %SystemDrive%\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\enable-winrm.ps1</CommandLine> + <Description>Enable WinRM</Description> + <Order>11</Order> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c a:\disable-network-discovery.cmd</CommandLine> + <Description>Disable Network Discovery</Description> + <Order>12</Order> + </SynchronousCommand> + </FirstLogonCommands> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Work</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + <SkipMachineOOBE>true</SkipMachineOOBE> + <SkipUserOOBE>true</SkipUserOOBE> + </OOBE> + <RegisteredOrganization>Organization</RegisteredOrganization> + <RegisteredOwner>Owner</RegisteredOwner> + <DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet> + <TimeZone>Pacific Standard Time</TimeZone> + <UserAccounts> + <AdministratorPassword> + <Value>S3cr3t0!</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Description>Administrator</Description> + <DisplayName>Administrator</DisplayName> + <Group>Administrators</Group> + <Name>Administrator</Name> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + </component> + </settings> +</unattend> diff --git a/scripts/ci-azure-e2e.sh b/scripts/ci-azure-e2e.sh new file mode 100755 index 0000000..2fbacd7 --- /dev/null +++ b/scripts/ci-azure-e2e.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### + +# This script is executed by presubmit `pull-cluster-api-provider-azure-e2e` +# To run locally, set AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +CAPI_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +cd "${CAPI_ROOT}" || exit 1 + +export ARTIFACTS="${ARTIFACTS:-${PWD}/_artifacts}" +mkdir -p "${ARTIFACTS}/azure-sigs" "${ARTIFACTS}/azure-vhds" + +# Get list of Azure target names from common file +source azure_targets.sh + +# Convert single line entries into arrays +IFS=' ' read -r -a VHD_CI_TARGETS <<< "${VHD_CI_TARGETS}" +IFS=' ' read -r -a SIG_CI_TARGETS <<< "${SIG_CI_TARGETS}" +IFS=' ' read -r -a SIG_GEN2_CI_TARGETS <<< "${SIG_GEN2_CI_TARGETS}" + +# Append the "gen2" targets to the original SIG list +for element in "${SIG_GEN2_CI_TARGETS[@]}" +do + SIG_CI_TARGETS+=("${element}-gen2") +done + +# shellcheck source=parse-prow-creds.sh +source "packer/azure/scripts/parse-prow-creds.sh" + +# Verify the required Environment Variables are present. +: "${AZURE_SUBSCRIPTION_ID:?Environment variable empty or not defined.}" +: "${AZURE_TENANT_ID:?Environment variable empty or not defined.}" +: "${AZURE_CLIENT_ID:?Environment variable empty or not defined.}" +: "${AZURE_CLIENT_SECRET:?Environment variable empty or not defined.}" + +get_random_region() { + local REGIONS=("eastus" "eastus2" "southcentralus" "westus2" "westeurope") + echo "${REGIONS[${RANDOM} % ${#REGIONS[@]}]}" +} + +export PATH=${PWD}/.local/bin:$PATH +export PATH=${PYTHON_BIN_DIR:-"/root/.local/bin"}:$PATH + +export AZURE_LOCATION="${AZURE_LOCATION:-$(get_random_region)}" +export RESOURCE_GROUP_NAME="image-builder-e2e-$(head /dev/urandom | LC_ALL=C tr -dc a-z0-9 | head -c 6 ; echo '')" + +# timestamp is in RFC-3339 format to match kubetest +export TIMESTAMP="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" +export JOB_NAME="${JOB_NAME:-"image-builder-e2e"}" +export TAGS="creationTimestamp=${TIMESTAMP} jobName=${JOB_NAME}" + +cleanup() { + az group delete -n ${RESOURCE_GROUP_NAME} --yes --no-wait || true +} + +trap cleanup EXIT + +make deps-azure + +# Latest Flatcar version is often available on Azure with a delay, so resolve ourselves +az login --service-principal -u ${AZURE_CLIENT_ID} -p ${AZURE_CLIENT_SECRET} --tenant ${AZURE_TENANT_ID} +get_flatcar_version() { + az vm image show --urn kinvolk:flatcar-container-linux-free:stable:latest --query 'name' -o tsv +} +export FLATCAR_VERSION="$(get_flatcar_version)" + +# Pre-pulling windows images takes 10-20 mins +# Disable them for CI runs so don't run into timeouts +export PACKER_VAR_FILES="packer/azure/scripts/disable-windows-prepull.json scripts/ci-disable-goss-inspect.json" + +declare -A PIDS +if [[ "${AZURE_BUILD_FORMAT:-vhd}" == "sig" ]]; then + for target in ${SIG_CI_TARGETS[@]}; + do + make build-azure-sig-${target} > ${ARTIFACTS}/azure-sigs/${target}.log 2>&1 & + PIDS["sig-${target}"]=$! + done +else + for target in ${VHD_CI_TARGETS[@]}; + do + make build-azure-vhd-${target} > ${ARTIFACTS}/azure-vhds/${target}.log 2>&1 & + PIDS["vhd-${target}"]=$! + done +fi + +# need to unset errexit so that failed child tasks don't cause script to exit +set +o errexit +exit_err=false +for target in "${!PIDS[@]}"; do + wait ${PIDS[$target]} + if [[ $? -ne 0 ]]; then + exit_err=true + echo "${target}: FAILED. See logs in the artifacts folder." + else + echo "${target}: SUCCESS" + fi +done + +if [[ "${exit_err}" = true ]]; then + exit 1 +fi diff --git a/scripts/ci-container-image.sh b/scripts/ci-container-image.sh new file mode 100755 index 0000000..ff52227 --- /dev/null +++ b/scripts/ci-container-image.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +CAPI_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +cd "${CAPI_ROOT}" || exit 1 + +make docker-build diff --git a/scripts/ci-disable-goss-inspect.json b/scripts/ci-disable-goss-inspect.json new file mode 100644 index 0000000..379f064 --- /dev/null +++ b/scripts/ci-disable-goss-inspect.json @@ -0,0 +1,3 @@ +{ + "goss_inspect_mode": "false" +} diff --git a/scripts/ci-gce-nightly.sh b/scripts/ci-gce-nightly.sh new file mode 100755 index 0000000..6d2ca43 --- /dev/null +++ b/scripts/ci-gce-nightly.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################################################################################ +# usage: ci-gce-nightly.sh +# This program build all images for capi gce for the nightly build +################################################################################ + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +CAPI_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +cd "${CAPI_ROOT}" || exit 1 + +# Verify the required Environment Variables are present. +: "${GCP_PROJECT:?Environment variable empty or not defined.}" + +# to list and check if have the properly access to the service account +gcloud auth list + +# assume we are running in the CI environment as root +# Add a user for ansible to work properly +groupadd -r packer && useradd -m -s /bin/bash -r -g packer packer +chown -R packer:packer /home/prow/go/src/sigs.k8s.io/image-builder +# use the packer user to run the build + +# build image for 1.23 +# using PACKER_FLAGS=-force to overwrite the previous image and keep the same name +su - packer -c "bash -c 'cd /home/prow/go/src/sigs.k8s.io/image-builder/images/capi && PATH=$PATH:~packer/.local/bin:/home/prow/go/src/sigs.k8s.io/image-builder/images/capi/.local/bin GCP_PROJECT_ID=$GCP_PROJECT PACKER_VAR_FILES=packer/gce/ci/nightly/overwrite-1-23.json PACKER_FLAGS=-force make deps-gce build-gce-all'" + +# build image for 1.24 +# using PACKER_FLAGS=-force to overwrite the previous image and keep the same name +su - packer -c "bash -c 'cd /home/prow/go/src/sigs.k8s.io/image-builder/images/capi && PATH=$PATH:~packer/.local/bin:/home/prow/go/src/sigs.k8s.io/image-builder/images/capi/.local/bin GCP_PROJECT_ID=$GCP_PROJECT PACKER_VAR_FILES=packer/gce/ci/nightly/overwrite-1-24.json PACKER_FLAGS=-force make deps-gce build-gce-all'" + +# build image for 1.25 +# using PACKER_FLAGS=-force to overwrite the previous image and keep the same name +su - packer -c "bash -c 'cd /home/prow/go/src/sigs.k8s.io/image-builder/images/capi && PATH=$PATH:~packer/.local/bin:/home/prow/go/src/sigs.k8s.io/image-builder/images/capi/.local/bin GCP_PROJECT_ID=$GCP_PROJECT PACKER_VAR_FILES=packer/gce/ci/nightly/overwrite-1-25.json PACKER_FLAGS=-force make deps-gce build-gce-all'" + +# build image for 1.26 +# using PACKER_FLAGS=-force to overwrite the previous image and keep the same name +su - packer -c "bash -c 'cd /home/prow/go/src/sigs.k8s.io/image-builder/images/capi && PATH=$PATH:~packer/.local/bin:/home/prow/go/src/sigs.k8s.io/image-builder/images/capi/.local/bin GCP_PROJECT_ID=$GCP_PROJECT PACKER_VAR_FILES=packer/gce/ci/nightly/overwrite-1-26.json PACKER_FLAGS=-force make deps-gce build-gce-all'" + +echo "Displaying the generated image information" +filter="name~cluster-api-ubuntu-*" +gcloud compute images list --project "$GCP_PROJECT" \ + --no-standard-images --filter="${filter}" + +echo "Making images public to use in CI" +(gcloud compute images list --project "$GCP_PROJECT" --no-standard-images --filter="${filter}" --format="value(name[])" | \ +awk '{print "gcloud compute images add-iam-policy-binding --project '"$GCP_PROJECT"' " $1 " --member='"'allAuthenticatedUsers'"' --role='"'roles/compute.imageUser'"' \n"}' | bash) diff --git a/scripts/ci-gce.sh b/scripts/ci-gce.sh new file mode 100755 index 0000000..a4510af --- /dev/null +++ b/scripts/ci-gce.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################################################################################ +# usage: ci-gce.sh +# This program build all images for capi gce +################################################################################ + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +CAPI_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +cd "${CAPI_ROOT}" || exit 1 + +# shellcheck source=ensure-go.sh +source "./hack/ensure-go.sh" +# shellcheck source=ensure-boskosctl.sh +source "./hack/ensure-boskosctl.sh" + +# Verify the required Environment Variables are present. +: "${GOOGLE_APPLICATION_CREDENTIALS:?Environment variable empty or not defined.}" + +function boskosctlwrapper() { + boskosctl --server-url http://"${BOSKOS_HOST}" --owner-name "cluster-api-provider-gcp" "${@}" +} + +cleanup() { + echo "Cleaning up image" + filter="name~cluster-api-ubuntu-*" + (gcloud compute images list --project "$GCP_PROJECT" \ + --no-standard-images --format="table[no-heading](name)" --filter="${filter}" \ + | awk '{print "gcloud compute images delete --quiet --project '"$GCP_PROJECT"' "$1" " "\n"}' \ + | bash ) || true + + # stop boskos heartbeat + if [ -n "${BOSKOS_HOST:-}" ]; then + boskosctlwrapper release --name "${RESOURCE_NAME}" --target-state used + fi + + exit "${test_status}" +} +trap cleanup EXIT + +if [[ -z "$GOOGLE_APPLICATION_CREDENTIALS" ]]; then + cat <<EOF +GOOGLE_APPLICATION_CREDENTIALS is not set. +Please set this to the path of the service account used to run this script. +EOF + return 2 +else + gcloud auth activate-service-account --key-file="${GOOGLE_APPLICATION_CREDENTIALS}" +fi + +# If BOSKOS_HOST is set then acquire an GCP account from Boskos. +if [ -n "${BOSKOS_HOST:-}" ]; then + echo "Boskos acquire - ${BOSKOS_HOST}" + export BOSKOS_RESOURCE="$( boskosctlwrapper acquire --type gce-project --state free --target-state busy --timeout 1h )" + export RESOURCE_NAME=$(echo $BOSKOS_RESOURCE | jq -r ".name") + export GCP_PROJECT=$(echo $BOSKOS_RESOURCE | jq -r ".name") + + # send a heartbeat in the background to keep the lease while using the resource + echo "Starting Boskos HeartBeat" + boskosctlwrapper heartbeat --resource "${BOSKOS_RESOURCE}" & +fi + +# assume we are running in the CI environment as root +# Add a user for ansible to work properly +groupadd -r packer && useradd -m -s /bin/bash -r -g packer packer +chown -R packer:packer /home/prow/go/src/sigs.k8s.io/image-builder +# use the packer user to run the build +su - packer -c "bash -c 'cd /home/prow/go/src/sigs.k8s.io/image-builder/images/capi && PATH=$PATH:~packer/.local/bin:/home/prow/go/src/sigs.k8s.io/image-builder/images/capi/.local/bin GCP_PROJECT_ID=$GCP_PROJECT GOOGLE_APPLICATION_CREDENTIALS=$GOOGLE_APPLICATION_CREDENTIALS PACKER_VAR_FILES=scripts/ci-disable-goss-inspect.json make deps-gce build-gce-all'" +test_status="${?}" + +echo "Displaying the generated image information" +filter="name~cluster-api-ubuntu-*" +gcloud compute images list --project "$GCP_PROJECT" --no-standard-images --filter="${filter}" + +exit "${test_status}" diff --git a/scripts/ci-goss-populate.sh b/scripts/ci-goss-populate.sh new file mode 100755 index 0000000..9d84bb1 --- /dev/null +++ b/scripts/ci-goss-populate.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +CAPI_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +cd "${CAPI_ROOT}" || exit 1 + +source hack/utils.sh +ensure_py3 + +_version="v0.3.16" +_bin_url="https://github.com/aelsabbahy/goss/releases/download/${_version}/goss-linux-amd64" + +if ! command -v goss >/dev/null 2>&1; then + if [[ ${HOSTOS} == "linux" ]]; then + curl -SsL "${_bin_url}" -o goss + chmod +x goss + mkdir -p "${PWD}/.local/bin" + mv goss "${PWD}/.local/bin" + export PATH=${PWD}/.local/bin:$PATH + fi +fi + +export GOSS_USE_ALPHA=1 +hack/generate-goss-specs.py diff --git a/scripts/ci-json-sort.sh b/scripts/ci-json-sort.sh new file mode 100755 index 0000000..563ce5d --- /dev/null +++ b/scripts/ci-json-sort.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +CAPI_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +cd "${CAPI_ROOT}" || exit 1 + +cleanup() { + returnCode="$?" + exit "${returnCode}" +} + +trap cleanup EXIT + +json_files=$(find . -type f -name "*.json" | sort -u) +for f in ${json_files} +do + if ! diff <(jq -S . ${f}) ${f} >> /dev/null; then + echo "json files are not sorted!! Please sort them with \"make json-sort\" in \"images/capi\" before commit" + echo "Unsorted file: ${f}" + exit 1 + fi +done diff --git a/scripts/ci-outscale-nightly.sh b/scripts/ci-outscale-nightly.sh new file mode 100755 index 0000000..7a011b2 --- /dev/null +++ b/scripts/ci-outscale-nightly.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -o errexit +set -o nounset +set -o pipefail + +PACKER_VAR_FILES=packer/outscale/ci/nightly/overwrite-1-21.json make build-osc-all +PACKER_VAR_FILES=packer/outscale/ci/nightly/overwrite-1-22.json make build-osc-all +PACKER_VAR_FILES=packer/outscale/ci/nightly/overwrite-1-23.json make build-osc-all +PACKER_VAR_FILES=packer/outscale/ci/nightly/overwrite-1-24.json make build-osc-all +PACKER_VAR_FILES=packer/outscale/ci/nightly/overwrite-1-25.json make build-osc-all diff --git a/scripts/ci-ova.sh b/scripts/ci-ova.sh new file mode 100755 index 0000000..172dbec --- /dev/null +++ b/scripts/ci-ova.sh @@ -0,0 +1,157 @@ +#!/bin/bash + +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit # exits immediately on any unexpected error (does not bypass traps) +set -o nounset # will error if variables are used without first being defined +set -o pipefail # any non-zero exit code in a piped command causes the pipeline to fail with that code + +CAPI_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +cd "${CAPI_ROOT}" || exit 1 + +export ARTIFACTS="${ARTIFACTS:-${PWD}/_artifacts}" +TARGETS=("ubuntu-1804" "ubuntu-2004" "ubuntu-2204" "photon-3" "photon-4" "centos-7" "rockylinux-8" "flatcar") + +on_exit() { + # kill the VPN + docker kill vpn +} + +cleanup_build_vm() { + # Setup govc to delete build VM after + curl -L https://github.com/vmware/govmomi/releases/download/v0.23.0/govc_linux_amd64.gz | gunzip > govc + chmod +x govc + mv govc /usr/local/bin/govc + + for target in ${TARGETS[@]}; + do + govc vm.destroy capv-ci-${target}-${TIMESTAMP} + done + +} + +trap on_exit EXIT + +export PATH=${PWD}/.local/bin:$PATH +export PATH=${PYTHON_BIN_DIR:-"/root/.local/bin"}:$PATH +export GC_KIND="false" +export TIMESTAMP="$(date -u '+%Y%m%dT%H%M%S')" +export GOVC_DATACENTER="SDDC-Datacenter" +export GOVC_INSECURE=true + +cat << EOF > packer/ova/vsphere.json +{ + "vcenter_server":"${GOVC_URL}", + "insecure_connection": "${GOVC_INSECURE}", + "username":"${GOVC_USERNAME}", + "password":"${GOVC_PASSWORD}", + "datastore":"WorkloadDatastore", + "datacenter":"${GOVC_DATACENTER}", + "cluster": "Cluster-1", + "network": "sddc-cgw-network-8", + "folder": "Workloads/ci/imagebuilder" +} +EOF + +# Since access to esxi is blocked due to firewall rules, +# `export`, `post-processor` sections from `packer-node.json` are removed. +cat packer/ova/packer-node.json | jq 'del(.builders[] | select( .name == "vsphere" ).export)' > packer/ova/packer-node.json.tmp && mv packer/ova/packer-node.json.tmp packer/ova/packer-node.json +cat packer/ova/packer-node.json | jq 'del(.builders[] | select( .name == "vsphere-clone" ).export)' > packer/ova/packer-node.json.tmp && mv packer/ova/packer-node.json.tmp packer/ova/packer-node.json +cat packer/ova/packer-node.json | jq 'del(."post-processors"[])' > packer/ova/packer-node.json.tmp && mv packer/ova/packer-node.json.tmp packer/ova/packer-node.json + +# Run the vpn client in container +docker run --rm -d --name vpn -v "${HOME}/.openvpn/:${HOME}/.openvpn/" \ + -w "${HOME}/.openvpn/" --cap-add=NET_ADMIN --net=host --device=/dev/net/tun \ + gcr.io/cluster-api-provider-vsphere/extra/openvpn:latest + +# Tail the vpn logs +docker logs vpn + +# install deps and build all images +make deps-ova + +declare -A PIDS +for target in ${TARGETS[@]}; +do + export PACKER_VAR_FILES="ci-${target}.json scripts/ci-disable-goss-inspect.json" + if [[ "${target}" == 'photon-3' ]]; then +cat << EOF > ci-${target}.json +{ +"build_version": "capv-ci-${target}-${TIMESTAMP}", +"linked_clone": "true", +"template": "base-photon-3-20220623" +} +EOF + make build-node-ova-vsphere-clone-${target} > ${ARTIFACTS}/${target}.log 2>&1 & + + elif [[ "${target}" == 'photon-4' ]]; then +cat << EOF > ci-${target}.json +{ +"build_version": "capv-ci-${target}-${TIMESTAMP}", +"linked_clone": "true", +"template": "base-photon-4" +} +EOF + make build-node-ova-vsphere-clone-${target} > ${ARTIFACTS}/${target}.log 2>&1 & + + elif [[ "${target}" == 'rockylinux-8' ]]; then + cat << EOF > ci-${target}.json +{ +"build_version": "capv-ci-${target}-${TIMESTAMP}", +"linked_clone": "true", +"template": "base-rockylinux-8-20220623" +} +EOF + make build-node-ova-vsphere-clone-${target} > ${ARTIFACTS}/${target}.log 2>&1 & + + elif [[ "${target}" == 'ubuntu-2204' ]]; then + cat << EOF > ci-${target}.json +{ +"build_version": "capv-ci-${target}-${TIMESTAMP}", +"linked_clone": "true", +"template": "base-ubuntu-2204" +} +EOF + make build-node-ova-vsphere-clone-${target} > ${ARTIFACTS}/${target}.log 2>&1 & + + else +cat << EOF > ci-${target}.json +{ +"build_version": "capv-ci-${target}-${TIMESTAMP}" +} +EOF + make build-node-ova-vsphere-${target} > ${ARTIFACTS}/${target}.log 2>&1 & + fi + PIDS["${target}"]=$! +done + +# need to unset errexit so that failed child tasks don't cause script to exit +set +o errexit +exit_err=false +for target in "${!PIDS[@]}"; do + wait "${PIDS[$target]}" + if [[ $? -ne 0 ]]; then + exit_err=true + echo "${target}: FAILED. See logs in the artifacts folder." + else + echo "${target}: SUCCESS" + fi +done +set -o errexit + +cleanup_build_vm +if [[ "${exit_err}" = true ]]; then + exit 1 +fi diff --git a/scripts/ci-packer-validate.sh b/scripts/ci-packer-validate.sh new file mode 100755 index 0000000..e70f040 --- /dev/null +++ b/scripts/ci-packer-validate.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +CAPI_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +cd "${CAPI_ROOT}" || exit 1 + +export PATH=${PWD}/.local/bin:$PATH +export PATH=${PYTHON_BIN_DIR:-"${HOME}/.local/bin"}:$PATH + +# OCI packer builder requires a valid private key file, hence creating a temporary one +openssl genrsa -out /tmp/oci_api_key.pem 2048 + +AZURE_LOCATION=fake RESOURCE_GROUP_NAME=fake STORAGE_ACCOUNT_NAME=fake \ + DIGITALOCEAN_ACCESS_TOKEN=fake GCP_PROJECT_ID=fake \ + OCI_AVAILABILITY_DOMAIN=fake OCI_SUBNET_OCID=fake OCI_USER_FINGERPRINT=fake \ + OCI_TENANCY_OCID=fake OCI_USER_OCID=fake OCI_USER_KEY_FILE=/tmp/oci_api_key.pem \ + make validate-all