2 Commits

Author SHA1 Message Date
b4f62b465d Update packer windows-update plugin
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-25 13:32:57 +02:00
060ac37dc0 Update VMware Tools 2021-10-25 13:12:00 +02:00
95 changed files with 997 additions and 1779 deletions

View File

@@ -14,56 +14,54 @@ steps:
- name: Debugging information
image: bv11-cr01.bessems.eu/library/packer-extended
commands:
- ansible --version
- ovftool --version
- packer --version
- yamllint --version
- name: Kubernetes Bootstrap Appliance
- packer --version
- pwsh --version
- ovftool --version
- name: Windows 10
image: bv11-cr01.bessems.eu/library/packer-extended
pull: always
commands:
- sed -i -e "s/<<img-productkey>>/$${PRODUCTKEY}/" packer/preseed/Windows10/Autounattend.xml
- |
sed -i -e "s/<<img-password>>/$${SSH_PASSWORD}/g" \
packer/preseed/UbuntuServer22.04/user-data
sed -i -e "s/<<img-password>>/$${WINRM_PASSWORD}/g" \
packer/preseed/Windows10/Autounattend.xml \
packer/preseed/Windows10/Sysprep_Unattend.xml
- |
yamllint -d "{extends: relaxed, rules: {line-length: disable}}" \
ansible \
packer/preseed/UbuntuServer22.04/user-data \
scripts
- |
ansible-galaxy install \
-r ansible/requirements.yml
yamllint -d "{extends: relaxed, rules: {line-length: disable}}" scripts
- |
packer init -upgrade \
./packer
- |
packer validate \
-var vm_name=$DRONE_BUILD_NUMBER-${DRONE_COMMIT_SHA:0:10} \
-var vm_guestos=k8sbootstrap \
-var vm_guestos=win10 \
-var repo_username=$${REPO_USERNAME} \
-var repo_password=$${REPO_PASSWORD} \
-var vsphere_password=$${VSPHERE_PASSWORD} \
-var ssh_password=$${SSH_PASSWORD} \
-var winrm_password=$${WINRM_PASSWORD} \
./packer
- |
packer build \
-on-error=cleanup -timestamp-ui \
-on-error=cleanup \
-var vm_name=$DRONE_BUILD_NUMBER-${DRONE_COMMIT_SHA:0:10} \
-var vm_guestos=k8sbootstrap \
-var vm_guestos=win10 \
-var repo_username=$${REPO_USERNAME} \
-var repo_password=$${REPO_PASSWORD} \
-var vsphere_password=$${VSPHERE_PASSWORD} \
-var ssh_password=$${SSH_PASSWORD} \
-var winrm_password=$${WINRM_PASSWORD} \
./packer
environment:
VSPHERE_PASSWORD:
from_secret: vsphere_password
SSH_PASSWORD:
from_secret: ssh_password
WINRM_PASSWORD:
from_secret: winrm_password
REPO_USERNAME:
from_secret: repo_username
REPO_PASSWORD:
from_secret: repo_password
PRODUCTKEY:
from_secret: prodkey_win10
# PACKER_LOG: 1
volumes:
- name: output

View File

@@ -1 +1 @@
# Packer.Images [![Build Status](https://ci.spamasaurus.com/api/badges/djpbessems/Packer.Images/status.svg?ref=refs/heads/Kubernetes.Bootstrap.Appliance)](https://ci.spamasaurus.com/djpbessems/Packer.Images)
# Packer.Images [![Build Status](https://ci.spamasaurus.com/api/badges/djpbessems/Packer.Images/status.svg?ref=refs/heads/Windows10)](https://ci.spamasaurus.com/djpbessems/Packer.Images)

View File

@@ -1,3 +0,0 @@
[defaults]
deprecation_warnings = False
remote_tmp = /tmp/.ansible-${USER}/tmp

View File

@@ -1,12 +0,0 @@
---
- hosts: all
gather_facts: false
vars_files:
- metacluster.yml
- workloadcluster.yml
become: true
roles:
- os
- firstboot
- appliance
- metacluster

View File

@@ -1,4 +0,0 @@
collections:
- ansible.utils
- community.general
- kubernetes.core

View File

@@ -1,27 +0,0 @@
---
- name: Initialize tempfolder
ansible.builtin.tempfile:
state: directory
register: archive
- name: Download & extract archived static binary
ansible.builtin.unarchive:
src: "{{ item.url }}"
dest: "{{ archive.path }}"
remote_src: yes
extra_opts: "{{ item.extra_opts | default(omit) }}"
- name: Install extracted binary
ansible.builtin.copy:
src: "{{ archive.path }}/{{ item.filename }}"
dest: /usr/local/bin/{{ item.filename }}
remote_src: yes
owner: root
group: root
mode: 0755
- name: Cleanup tempfolder
ansible.builtin.file:
path: "{{ archive.path }}"
state: absent
when: archive.path is defined

View File

@@ -1,53 +0,0 @@
# - name: Create folder structure(s)
# ansible.builtin.file:
# path: "{{ item }}"
# state: directory
# loop:
# - /foo
- name: Download & install static binaries
ansible.builtin.get_url:
url: "{{ item.url }}"
url_username: "{{ item.username | default(omit) }}"
url_password: "{{ item.password | default(omit) }}"
dest: /usr/local/bin/{{ item.filename }}
owner: root
group: root
mode: 0755
loop: "{{ dependencies.static_binaries | selectattr('archive', 'undefined') }}"
loop_control:
label: "{{ item.filename }}"
- name: Download, extract & install archived static binaries
include_tasks: dependencies.archive_compressed.yml
loop: "{{ dependencies.static_binaries | rejectattr('archive', 'undefined') | selectattr('archive', 'equalto', 'compressed') }}"
loop_control:
label: "{{ item.filename }}"
- name: Install ansible-galaxy collections
ansible.builtin.shell:
cmd: ansible-galaxy collection install {{ item }}
loop: "{{ dependencies.ansible_galaxy_collections }}"
- name: Install distro packages
ansible.builtin.apt:
pkg: "{{ dependencies.packages.apt }}"
state: latest
update_cache: yes
install_recommends: no
- name: Upgrade all packages
ansible.builtin.apt:
name: '*'
state: latest
update_cache: yes
- name: Install additional python packages
ansible.builtin.pip:
name: "{{ dependencies.packages.pip }}"
state: latest
- name: Cleanup apt cache
ansible.builtin.apt:
autoremove: yes
purge: yes

View File

@@ -1,2 +0,0 @@
- name: Install & configure dependencies
import_tasks: dependencies.yml

View File

@@ -1,24 +0,0 @@
---
- hosts: 127.0.0.1
connection: local
gather_facts: false
vars_files:
- metacluster.yml
# become: true
roles:
- vapp
- network
- users
- disks
- metacluster
- workloadcluster
- tty
- cleanup
handlers:
- name: Apply manifests
kubernetes.core.k8s:
src: "{{ item }}"
state: present
kubeconfig: "{{ kubeconfig.path }}"
with_fileglob: /var/lib/rancher/k3s/server/manifests/*.yaml
ignore_errors: yes

View File

@@ -1,8 +0,0 @@
- name: Disable crontab job
ansible.builtin.cron:
name: firstboot
state: absent
- name: Reboot host
ansible.builtin.shell:
cmd: /usr/sbin/reboot now

View File

@@ -1,24 +0,0 @@
- name: Create volume group
community.general.lvg:
vg: longhorn_vg
pvs:
- /dev/sdb
pvresize: yes
- name: Create logical volume
community.general.lvol:
vg: longhorn_vg
lv: longhorn_lv
size: 100%VG
- name: Create filesystem
community.general.filesystem:
dev: /dev/mapper/longhorn_vg-longhorn_lv
fstype: ext4
- name: Mount dynamic disk
ansible.posix.mount:
path: /mnt/blockstorage
src: /dev/mapper/longhorn_vg-longhorn_lv
fstype: ext4
state: mounted

View File

@@ -1,398 +0,0 @@
- name: Gather service facts
ansible.builtin.service_facts:
# Module requires no attributes
- name: Install K3s
ansible.builtin.command:
cmd: ./install.sh
chdir: /opt/metacluster/k3s
environment:
INSTALL_K3S_SKIP_DOWNLOAD: 'true'
INSTALL_K3S_EXEC: 'server --cluster-init --disable local-storage'
when: ansible_facts.services['k3s.service'] is undefined
- name: Ensure API availability
ansible.utils.cli_parse:
command: curl -k https://{{ vapp['guestinfo.ipaddress'] }}:6443/livez?verbose
parser:
name: ansible.utils.json
set_fact: api_readycheck
ignore_errors: yes
until: api_readycheck.apiVersion is defined
retries: 3
delay: 30
- name: Install kubectl tab-completion
ansible.builtin.shell:
cmd: kubectl completion bash | tee /etc/bash_completion.d/kubectl
- name: Extract container images
ansible.builtin.unarchive:
src: /opt/metacluster/container-images/image-tarballs.tgz
dest: /opt/metacluster/container-images
list_files: yes
register: imagetarballs
- name: Import container images
ansible.builtin.command:
cmd: k3s ctr image import {{ item }}
chdir: /opt/metacluster/container-images
loop: "{{ imagetarballs.files }}"
- name: Initialize tempfile
ansible.builtin.tempfile:
state: file
register: kubeconfig
- name: Retrieve kubeconfig
ansible.builtin.command:
cmd: kubectl config view --raw
register: kubectl_config
- name: Store kubeconfig in tempfile
ansible.builtin.copy:
dest: "{{ kubeconfig.path }}"
content: "{{ kubectl_config.stdout }}"
mode: 0600
no_log: true
- name: Configure traefik dashboard ingress
ansible.builtin.template:
src: ingressroute.j2
dest: /var/lib/rancher/k3s/server/manifests/{{ _template.name }}-manifest.yaml
owner: root
group: root
mode: 0600
vars:
_template:
name: traefik-dashboard
namespace: kube-system
config: |2
entryPoints:
- web
- websecure
routes:
- kind: Rule
match: Host(`ingress.{{ vapp['metacluster.fqdn'] }}`)
services:
- kind: TraefikService
name: api@internal
notify:
- Apply manifests
- name: Trigger handlers
ansible.builtin.meta: flush_handlers
- name: Create component entries in /etc/hosts
ansible.builtin.lineinfile:
path: /etc/hosts
line: "{{ vapp['guestinfo.ipaddress'] }} {{ item + '.' + vapp['metacluster.fqdn'] }}"
state: present
loop:
# TODO: Make this list dynamic
- git
- gitops
- ingress
- registry
- storage
- name: Install longhorn chart
kubernetes.core.helm:
name: longhorn
chart_ref: /opt/metacluster/helm-charts/longhorn
release_namespace: longhorn-system
create_namespace: yes
wait: yes
kubeconfig: "{{ kubeconfig.path }}"
values: "{{ components.longhorn.chart_values }}"
- name: Install step-ca chart
kubernetes.core.helm:
name: step-certificates
chart_ref: /opt/metacluster/helm-charts/step-certificates
release_namespace: step-ca
create_namespace: yes
wait: yes
kubeconfig: "{{ kubeconfig.path }}"
values: "{{ components.stepcertificates.chart_values }}"
- name: Retrieve step-ca configuration
kubernetes.core.k8s_log:
kind: Job
name: step-certificates
namespace: step-ca
kubeconfig: "{{ kubeconfig.path }}"
register: stepca_bootstraplog
- name: Install root CA in system truststore
ansible.builtin.shell:
cmd: |
step ca bootstrap \
--ca-url={{ stepca_bootstraplog.log | regex_search('CA URL: (.+)', '\\1') | first }} \
--fingerprint={{ stepca_bootstraplog.log | regex_search('CA Fingerprint: (.+)', '\\1') | first }} \
--install
- name: Install harbor chart
kubernetes.core.helm:
name: harbor
chart_ref: /opt/metacluster/helm-charts/harbor
release_namespace: harbor
create_namespace: yes
wait: yes
kubeconfig: "{{ kubeconfig.path }}"
values: "{{ components.harbor.chart_values }}"
- name: Push images to registry
ansible.builtin.shell:
cmd: >-
skopeo copy \
--insecure-policy \
--dest-tls-verify=false \
--dest-creds admin:{{ vapp['guestinfo.rootpw'] }} \
docker-archive:./{{ item }} \
docker://registry.{{ vapp['metacluster.fqdn'] }}/library/$( \
skopeo list-tags \
--insecure-policy \
docker-archive:./{{ item }} | \
jq -r '.Tags[0]')
chdir: /opt/metacluster/container-images/
loop: "{{ imagetarballs.files }}"
- name: Configure K3s node for private registry
ansible.builtin.template:
dest: /etc/rancher/k3s/registries.yaml
src: registries.j2
notify:
- Apply manifests
- name: Trigger handlers
ansible.builtin.meta: flush_handlers
- block:
- name: Install gitea chart
kubernetes.core.helm:
name: gitea
chart_ref: /opt/metacluster/helm-charts/gitea
release_namespace: gitea
create_namespace: yes
wait: yes
kubeconfig: "{{ kubeconfig.path }}"
values: "{{ components.gitea.chart_values }}"
- name: Configure additional SSH ingress
ansible.builtin.template:
src: ingressroutetcp.j2
dest: /var/lib/rancher/k3s/server/manifests/{{ _template.name }}-manifest.yaml
owner: root
group: root
mode: 0600
vars:
_template:
name: gitea-ssh
namespace: gitea
config: |2
entryPoints:
- ssh
routes:
- match: HostSNI(`*`)
services:
- name: gitea-ssh
port: 22
notify:
- Apply manifests
- name: Trigger handlers
ansible.builtin.meta: flush_handlers
- name: Ensure gitea API availability
ansible.utils.cli_parse:
# Available from Gitea 1.17.x
# command: curl -k https://git.{{ vapp['metacluster.fqdn'] }}/api/healtz
command: curl -k https://git.{{ vapp['metacluster.fqdn'] }}/api/v1/version
parser:
name: ansible.utils.json
set_fact: api_readycheck
ignore_errors:
until: api_readycheck.version is defined
retries: 3
delay: 30
- name: Generate gitea API token
ansible.builtin.uri:
url: https://git.{{ vapp['metacluster.fqdn'] }}/api/v1/users/administrator/tokens
method: POST
user: administrator
password: "{{ vapp['guestinfo.rootpw'] }}"
force_basic_auth: yes
body:
name: token_init_{{ lookup('password', '/dev/null length=5 chars=ascii_letters,digits') }}
register: gitea_api_token
- name: Retrieve existing gitea configuration
ansible.builtin.uri:
url: https://git.{{ vapp['metacluster.fqdn'] }}/api/v1/repos/search
method: GET
register: gitea_existing_config
- ansible.builtin.uri:
url: "{{ item }}"
method: GET
headers:
Authorization: token {{ gitea_api_token.json.sha1 }}
register: gitea_existing_config_TST
loop:
# - https://git.{{ vapp['metacluster.fqdn'] }}/api/v1/orgs
- https://git.{{ vapp['metacluster.fqdn'] }}/api/v1/repos/search
- https://git.{{ vapp['metacluster.fqdn'] }}/api/v1/users/administrator/keys
ignore_errors: yes
- debug:
var: gitea_existing_config_TST.results[].json.data
- block:
- name: Register SSH public key
ansible.builtin.uri:
url: https://git.{{ vapp['metacluster.fqdn'] }}/api/v1/user/keys
method: POST
headers:
Authorization: token {{ gitea_api_token.json.sha1 }}
body:
key: "{{ gitops_sshkey.public_key }}"
read_only: false
title: GitOps
- name: Create organization(s)
ansible.builtin.uri:
url: https://git.{{ vapp['metacluster.fqdn'] }}/api/v1/orgs
method: POST
headers:
Authorization: token {{ gitea_api_token.json.sha1 }}
body: "{{ item }}"
loop:
- full_name: Meta-cluster
description: Meta-cluster configuration items
username: mc
website: https://git.{{ vapp['metacluster.fqdn'] }}/mc
location: '[...]'
visibility: public
- full_name: Workload-cluster
description: Workload-cluster configuration items
username: wl
website: https://git.{{ vapp['metacluster.fqdn'] }}/wl
location: '[...]'
visibility: public
loop_control:
label: "{{ item.full_name }}"
- name: Create repositories
ansible.builtin.uri:
url: https://git.{{ vapp['metacluster.fqdn'] }}/api/v1/orgs/{{ item.organization }}/repos
method: POST
headers:
Authorization: token {{ gitea_api_token.json.sha1 }}
body: "{{ item.body }}"
loop:
- organization: mc
body:
name: GitOps.Config
# auto_init: true
# default_branch: main
description: GitOps manifests
- organization: wl
body:
name: Template.GitOps.Config
# auto_init: true
# default_branch: main
description: GitOps manifests
loop_control:
label: "{{ item.organization + '/' + item.body.name }}"
- name: Rebase/Push source gitops repository
ansible.builtin.shell:
cmd: |
git config --local http.sslVerify false
git remote set-url origin https://administrator:{{ vapp['guestinfo.rootpw'] | urlencode }}@git.{{ vapp['metacluster.fqdn'] }}/mc/GitOps.Config.git
git push
chdir: /opt/metacluster/git-repositories/gitops
when: (gitea_existing_config.json is undefined) or (gitea_existing_config.json.data | length == 0)
module_defaults:
ansible.builtin.uri:
validate_certs: no
status_code: [200, 201]
body_format: json
- block:
- name: Install argo-cd chart
kubernetes.core.helm:
name: argo-cd
chart_ref: /opt/metacluster/helm-charts/argo-cd
release_namespace: argo-cd
create_namespace: yes
wait: yes
kubeconfig: "{{ kubeconfig.path }}"
values: "{{ components.argocd.chart_values }}"
- name: Ensure argo-cd API availability
ansible.utils.cli_parse:
command: curl -k https://gitops.{{ vapp['metacluster.fqdn'] }}/api/version
parser:
name: ansible.utils.json
set_fact: api_readycheck
ignore_errors:
until: api_readycheck.Version is defined
retries: 3
delay: 30
- name: Generate argo-cd API token
ansible.builtin.uri:
url: https://gitops.{{ vapp['metacluster.fqdn'] }}/api/v1/session
method: POST
force_basic_auth: yes
body:
username: admin
password: "{{ vapp['guestinfo.rootpw'] }}"
register: argocd_api_token
# - name: Create umbrella application
# ansible.builtin.template:
#
- name: Configure metacluster-gitops repository
ansible.builtin.template:
src: gitrepo.j2
dest: /var/lib/rancher/k3s/server/manifests/{{ _template.name }}-manifest.yaml
owner: root
group: root
mode: 0600
vars:
_template:
name: argocd-gitrepo-metacluster
namespace: argo-cd
uid: "{{ lookup('ansible.builtin.password', '/dev/null length=5 chars=ascii_lowercase,digits seed=inventory_hostname') }}"
privatekey: "{{ lookup('ansible.builtin.file', '~/.ssh/git_rsa_id') | indent(4, true) }}"
notify:
- Apply manifests
- name: Trigger handlers
ansible.builtin.meta: flush_handlers
module_defaults:
ansible.builtin.uri:
validate_certs: no
status_code: [200, 201]
body_format: json
- name: Delete container image tarballs/archives
ansible.builtin.file:
path: "{{ item }}"
state: absent
with_fileglob: /opt/metacluster/container-images/*.tar
- name: Cleanup tempfile
ansible.builtin.file:
path: "{{ kubeconfig.path }}"
state: absent
when: kubeconfig.path is defined

View File

@@ -1,12 +0,0 @@
- name: Set hostname
ansible.builtin.hostname:
name: "{{ vapp['guestinfo.hostname'] }}"
- name: Create netplan configuration file
ansible.builtin.template:
src: netplan.j2
dest: /etc/netplan/00-installer-config.yaml
- name: Apply netplan configuration
ansible.builtin.shell:
cmd: /usr/sbin/netplan apply

View File

@@ -1,10 +0,0 @@
network:
version: 2
ethernets:
ens192:
addresses:
- {{ vapp['guestinfo.ipaddress'] }}/{{ vapp['guestinfo.prefixlength'] }}
gateway4: {{ vapp['guestinfo.gateway'] }}
nameservers:
addresses:
- {{ vapp['guestinfo.dnsserver'] }}

View File

@@ -1,20 +0,0 @@
- name: Create folder structure(s)
ansible.builtin.file:
path: "{{ item }}"
state: directory
loop:
- /opt/firstboot
- name: Save tty script file
ansible.builtin.template:
src: tty.j2
dest: /opt/firstboot/tty.sh
owner: root
group: root
mode: 0700
- name: Create @reboot crontab job
ansible.builtin.cron:
name: tty.consolemessage
special_time: reboot
job: /opt/firstboot/tty.sh

View File

@@ -1,37 +0,0 @@
- name: Set root password
ansible.builtin.user:
name: root
password: "{{ vapp['guestinfo.rootpw'] | password_hash('sha512', 65534 | random(seed=vapp['guestinfo.hostname']) | string) }}"
generate_ssh_key: yes
ssh_key_bits: 2048
ssh_key_file: .ssh/id_rsa
- name: Save root SSH publickey
ansible.builtin.lineinfile:
path: /root/.ssh/authorized_keys
line: "{{ vapp['guestinfo.rootsshkey'] }}"
- name: Disable SSH password authentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regex: "{{ item.regex }}"
line: "{{ item.line }}"
state: "{{ item.state }}"
loop:
- regex: '^#PasswordAuthentication'
line: 'PasswordAuthentication no'
state: present
- regex: '^PasswordAuthentication yes'
line: 'PasswordAuthentication yes'
state: absent
- name: Create dedicated SSH keypair
community.crypto.openssh_keypair:
path: /root/.ssh/git_rsa_id
register: gitops_sshkey
- name: Delete 'ubuntu' user
ansible.builtin.user:
name: ubuntu
state: absent
remove: yes

View File

@@ -1,38 +0,0 @@
- name: Store current ovfEnvironment
ansible.builtin.shell:
cmd: /usr/bin/vmtoolsd --cmd "info-get guestinfo.ovfEnv"
register: ovfenv
- name: Parse XML for MoRef ID
community.general.xml:
xmlstring: "{{ ovfenv.stdout }}"
namespaces:
ns: http://schemas.dmtf.org/ovf/environment/1
ve: http://www.vmware.com/schema/ovfenv
xpath: /ns:Environment
content: attribute
register: environment_attribute
- name: Store MoRef ID
ansible.builtin.set_fact:
moref_id: "{{ ((environment_attribute.matches[0].values() | list)[0].values() | list)[1] }}"
- name: Parse XML for vApp properties
community.general.xml:
xmlstring: "{{ ovfenv.stdout }}"
namespaces:
ns: http://schemas.dmtf.org/ovf/environment/1
xpath: /ns:Environment/ns:PropertySection/ns:Property
content: attribute
register: property_section
- name: Assign vApp properties to dictionary
ansible.builtin.set_fact:
vapp: >-
{{ vapp | default({}) | combine({
((item.values() | list)[0].values() | list)[0]:
((item.values() | list)[0].values() | list)[1]})
}}
loop: "{{ property_section.matches }}"
loop_control:
label: "{{ ((item.values() | list)[0].values() | list)[0] }}"

View File

@@ -1,106 +0,0 @@
- block:
- name: Gather hypervisor details
ansible.builtin.shell:
cmd: govc ls -L {{ item.moref }} | awk -F/ '{print ${{ item.part }}}'
environment:
GOVC_INSECURE: '1'
GOVC_URL: "{{ vapp['hv.fqdn'] }}"
GOVC_USERNAME: "{{ vapp['hv.username'] }}"
GOVC_PASSWORD: "{{ vapp['hv.password'] }}"
register: govc_inventory
loop:
- attribute: cluster
moref: >-
$(govc object.collect -json VirtualMachine:{{ moref_id }} | \
jq -r '.[] | select(.Name == "runtime").Val.Host | .Type + ":" + .Value')
part: (NF-1)
- attribute: datacenter
moref: VirtualMachine:{{ moref_id }}
part: 2
- attribute: datastore
moref: >-
$(govc object.collect -json VirtualMachine:{{ moref_id }} | \
jq -r '.[] | select(.Name == "datastore").Val.ManagedObjectReference | .[].Type + ":" + .[].Value')
part: NF
- attribute: folder
moref: >-
$(govc object.collect -json VirtualMachine:{{ moref_id }} | \
jq -r '.[] | select(.Name == "parent").Val | .Type + ":" + .Value')
part: 0
# - attribute: host
# moref: >-
# $(govc object.collect -json VirtualMachine:{{ moref_id }} | \
# jq -r '.[] | select(.Name == "runtime").Val.Host | .Type + ":" + .Value')
# part: NF
- attribute: network
moref: >-
$(govc object.collect -json VirtualMachine:{{ moref_id }} | \
jq -r '.[] | select(.Name == "network").Val.ManagedObjectReference | .[].Type + ":" + .[].Value')
part: NF
loop_control:
label: "{{ item.attribute }}"
- name: Store hypervisor details in dictionary
ansible.builtin.set_fact:
vcenter_info: "{{ vcenter_info | default({}) | combine({ item.item.attribute : item.stdout }) }}"
loop: "{{ govc_inventory.results }}"
loop_control:
label: "{{ item.item.attribute }}"
- block:
- name: Check for existing templates on hypervisor
community.vmware.vmware_guest_info:
name: "{{ (item | basename | split('.'))[:-1] | join('.') }}"
register: existing_ova
with_fileglob: /opt/workloadcluster/node-templates/*.ova
ignore_errors: yes
- name: Parse OVA files for network mappings
ansible.builtin.shell:
cmd: govc import.spec -json {{ item }}
environment:
GOVC_INSECURE: '1'
GOVC_URL: "{{ vapp['hv.fqdn'] }}"
GOVC_USERNAME: "{{ vapp['hv.username'] }}"
GOVC_PASSWORD: "{{ vapp['hv.password'] }}"
register: ova_spec
when: existing_ova.results[index] is failed
with_fileglob: /opt/workloadcluster/node-templates/*.ova
loop_control:
index_var: index
- name: Deploy OVA templates on hypervisor
community.vmware.vmware_deploy_ovf:
cluster: "{{ vcenter_info.cluster }}"
datastore: "{{ vcenter_info.datastore }}"
folder: "{{ vcenter_info.folder }}"
name: "{{ (item | basename | split('.'))[:-1] | join('.') }}"
networks: "{u'{{ ova_spec.results[index].stdout | from_json | json_query('NetworkMapping[0].Name') }}':u'{{ vcenter_info.network }}'}"
allow_duplicates: no
power_on: false
ovf: "{{ item }}"
register: ova_deploy
when: existing_ova.results[index] is failed
with_fileglob: /opt/workloadcluster/node-templates/*.ova
loop_control:
index_var: index
- name: Mark deployed VM's as templates
community.vmware.vmware_guest:
name: "{{ item.instance.hw_name }}"
is_template: yes
when: ova_deploy.results[index] is not skipped
loop: "{{ ova_deploy.results }}"
loop_control:
index_var: index
label: "{{ item.item }}"
module_defaults:
group/vmware:
hostname: "{{ vapp['hv.fqdn'] }}"
validate_certs: no
username: "{{ vapp['hv.username'] }}"
password: "{{ vapp['hv.password'] }}"
datacenter: "{{ vcenter_info.datacenter }}"

View File

@@ -1,13 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ _template.name }}-{{ _template.uid }}
namespace: {{ _template.namespace }}
labels:
argocd.argoproj.io/secret-type: repository
stringData:
url: ssh://git@gitea-ssh.gitea.svc.cluster.local/mc/GitOps.Config.git
name: {{ _template.name }}
insecure: 'true'
sshPrivateKey: |
{{ _template.privatekey }}

View File

@@ -1,7 +0,0 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: {{ _template.name }}
namespace: {{ _template.namespace }}
spec:
{{ _template.config }}

View File

@@ -1,7 +0,0 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: {{ _template.name }}
namespace: {{ _template.namespace }}
spec:
{{ _template.config }}

View File

@@ -1,25 +0,0 @@
mirrors:
cr.step.cm:
endpoint:
- https://registry.{{ vapp['metacluster.fqdn'] }}
rewrite:
"(.*)": "library/cr.step.sm/$1"
docker.io:
endpoint:
- https://registry.{{ vapp['metacluster.fqdn'] }}
rewrite:
"(.*)": "library/docker.io/$1"
ghcr.io:
endpoint:
- https://registry.{{ vapp['metacluster.fqdn'] }}
rewrite:
"(.*)": "library/ghcr.io/$1"
quay.io:
endpoint:
- https://registry.{{ vapp['metacluster.fqdn'] }}
rewrite:
"(.*)": "library/quay.io/$1"
configs:
registry.{{ vapp['metacluster.fqdn'] }}:
tls:
insecure_skip_verify: true

View File

@@ -1,42 +0,0 @@
#!/bin/bash
export TERM=linux
BGRN='\033[1;92m'
BGRY='\033[1;30m'
BBLU='\033[1;34m'
BRED='\033[1;91m'
BWHI='\033[1;97m'
CBLA='\033[?16;0;30c' # Hide blinking cursor
DFLT='\033[0m' # Reset colour
LCLR='\033[K' # Clear to end of line
PRST='\033[0;0H' # Reset cursor position
# COMPONENTS=('ca' 'ingress' 'storage' 'registry' 'git' 'gitops')
COMPONENTS=('ca' 'storage' 'registry' 'git' 'gitops')
FQDN='{{ vapp['metacluster.fqdn'] }}'
IPADDRESS='{{ vapp['guestinfo.ipaddress'] }}'
# Waiting to allow boot sequence to finish; crude!
sleep 30
clear > /dev/tty1
while /bin/true; do
echo -e "${PRST}" > /dev/tty1
echo -e "\n\n\t${DFLT}To manage this appliance, please connect to one of the following:${LCLR}\n" > /dev/tty1
for c in "${COMPONENTS[@]}"; do
STATUS=$(curl -ks "https://${c}.${FQDN}" -o /dev/null -w '%{http_code}')
if [[ "${STATUS}" -eq "200" ]]; then
echo -e "\t [${BGRN}+${DFLT}] ${BBLU}https://${c}.${FQDN}${DFLT}${LCLR}" > /dev/tty1
else
echo -e "\t [${BRED}-${DFLT}] ${BBLU}https://${c}.${FQDN}${DFLT}${LCLR}" > /dev/tty1
fi
done
echo -e "\n\t${BGRY}Note that your DNS zone ${DFLT}must have${BGRY} respective records defined,\n\teach pointing to: ${DFLT}${IPADDRESS}${LCLR}" > /dev/tty1
echo -e "${CBLA}" > /dev/tty1
sleep 1
done

View File

@@ -1,26 +0,0 @@
- name: Create destination folder
ansible.builtin.file:
path: /opt/firstboot
state: directory
- name: Create firstboot script file
ansible.builtin.template:
src: firstboot.j2
dest: /opt/firstboot/firstboot.sh
owner: root
group: root
mode: o+x
- name: Create @reboot crontab job
ansible.builtin.cron:
name: firstboot
special_time: reboot
job: "/opt/firstboot/firstboot.sh >/dev/tty1 2>&1"
- name: Copy payload folder
ansible.builtin.copy:
src: ansible_payload/
dest: /opt/firstboot/ansible/
owner: root
group: root
mode: '0644'

View File

@@ -1,4 +0,0 @@
#!/bin/bash
# Apply firstboot configuration w/ ansible
/usr/local/bin/ansible-playbook /opt/firstboot/ansible/playbook.yml | tee -a /var/log/firstboot.log > /dev/tty1 2>&1

View File

@@ -1,4 +0,0 @@
- name: Zero-out disk
ansible.builtin.shell:
cmd: nice -n 10 dd bs=1M count=$(df -m . | awk '/[0-9]%/{print $(NF-2)}') if=/dev/zero of=./zero; sync; sync; rm -f ./zero
chdir: /opt/metacluster

View File

@@ -1,75 +0,0 @@
- name: Create folder structure(s)
ansible.builtin.file:
path: "{{ item }}"
state: directory
loop:
- /opt/metacluster/helm-charts
- /opt/metacluster/container-images
- name: Add helm repositories
kubernetes.core.helm_repository:
name: "{{ item.name }}"
repo_url: "{{ item.url }}"
state: present
loop: "{{ platform.helm_repositories }}"
- name: Fetch helm charts
ansible.builtin.command:
cmd: helm fetch {{ item.value.helm.chart }} --untar --version {{ item.value.helm.version }}
chdir: /opt/metacluster/helm-charts
loop: "{{ lookup('ansible.builtin.dict', components) }}"
loop_control:
label: "{{ item.key }}"
- block:
- name: Aggregate chart_values into dict
ansible.builtin.set_fact:
chart_values: "{{ chart_values | default({}) | combine({ (item.key | regex_replace('[^A-Za-z0-9]', '')): { 'chart_values': (item.value.helm.chart_values | from_yaml) } }) }}"
when: item.value.helm.chart_values is defined
loop: "{{ lookup('ansible.builtin.dict', components) }}"
loop_control:
label: "{{ item.key }}"
- name: Write dict to vars_file
ansible.builtin.copy:
dest: /opt/firstboot/ansible/vars/metacluster.yml
content: "{{ { 'components': chart_values } | to_nice_yaml(indent=2, width=4096) }}"
- name: Parse helm charts for container images
ansible.builtin.shell:
cmd: "{{ item.value.helm.parse_logic }}"
chdir: /opt/metacluster/helm-charts/{{ item.key }}
register: containerimages
loop: "{{ lookup('ansible.builtin.dict', components) }}"
loop_control:
label: "{{ item.key }}"
- name: Pull and store containerimages
ansible.builtin.shell:
cmd: >-
skopeo copy \
--insecure-policy \
--retry-times=5 \
docker://{{ item }} \
docker-archive:./{{ ( item | regex_findall('[^/:]+'))[-2] }}.tar:{{ item }}
chdir: /opt/metacluster/container-images
loop: "{{ ((containerimages.results | map(attribute='stdout_lines') | flatten) + dependencies.container_images) | unique }}"
# - name: Inject manifests
# ansible.builtin.template:
# src: "{{ item.type }}.j2"
# dest: /var/lib/rancher/k3s/server/manifests/{{ item.name }}-manifest.yaml
# owner: root
# group: root
# mode: 0600
# loop: "{{ lookup('ansible.builtin.dict', components) | map(attribute='value.manifests') | list | select('defined') | flatten }}"
# loop_control:
# label: "{{ item.type + '/' + item.name }}"
- name: Compress tarballs
community.general.archive:
dest: /opt/metacluster/container-images/image-tarballs.tgz
path: /opt/metacluster/container-images/*
format: xz
remove: yes

View File

@@ -1,40 +0,0 @@
- name: Create folder structure(s)
ansible.builtin.file:
path: "{{ item }}"
state: directory
loop:
- /var/lib/rancher/k3s/agent/images
- /var/lib/rancher/k3s/server/manifests
- /opt/metacluster/k3s
- name: Download & install K3s binary
ansible.builtin.get_url:
url: https://github.com/k3s-io/k3s/releases/download/{{ platform.k3s.version }}/k3s
dest: /usr/local/bin/k3s
owner: root
group: root
mode: 0755
- name: Download K3s images tarball
ansible.builtin.get_url:
url: https://github.com/k3s-io/k3s/releases/download/{{ platform.k3s.version }}/k3s-airgap-images-amd64.tar.gz
dest: /var/lib/rancher/k3s/agent/images
- name: Download K3s install script
ansible.builtin.get_url:
url: https://get.k3s.io
dest: /opt/metacluster/k3s/install.sh
owner: root
group: root
mode: 0755
- name: Inject manifests
ansible.builtin.template:
src: helmchartconfig.j2
dest: /var/lib/rancher/k3s/server/manifests/{{ item.name }}-config.yaml
owner: root
group: root
mode: 0600
loop: "{{ platform.packaged_components }}"
loop_control:
label: "{{ item.name }}"

View File

@@ -1,11 +0,0 @@
- name: Pre-stage K3s components
import_tasks: k3s.yml
- name: Pre-stage meta-cluster components
import_tasks: components.yml
- name: Pre-stage meta-cluster configuration and workload-cluster components
import_tasks: staging.yml
- name: Cleanup
import_tasks: cleanup.yml

View File

@@ -1,21 +0,0 @@
- name: Create folder structure(s)
ansible.builtin.file:
path: "{{ item }}"
state: directory
loop:
- /opt/metacluster/git-repositories/gitops
- /opt/workloadcluster/node-templates
- name: Clone git repository
ansible.builtin.git:
repo: "{{ platform.gitops.repository.uri }}"
version: "{{ platform.gitops.repository.revision }}"
dest: /opt/metacluster/git-repositories/gitops
- name: Download node-template images
ansible.builtin.uri:
url: "{{ item.url }}"
dest: /opt/workloadcluster/node-templates/{{ downstream.node_templates.prefix }}{{ item.name }}
loop: "{{ downstream.node_templates.images }}"
loop_control:
label: "{{ downstream.node_templates.prefix }}{{ item.name }}"

View File

@@ -1,8 +0,0 @@
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: {{ item.name }}
namespace: {{ item.namespace }}
spec:
valuesContent: |-
{{ item.config }}

View File

@@ -1,13 +0,0 @@
- name: Delete cloud-init package
ansible.builtin.apt:
name: cloud-init
state: absent
purge: yes
- name: Delete cloud-init files
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /etc/cloud
- /var/lib/cloud

View File

@@ -1,5 +0,0 @@
- name: Enable crontab logging
ansible.builtin.lineinfile:
path: /etc/rsyslog.d/50-default.conf
regexp: '^#cron\.\*.*'
line: "cron.*\t\t\t\t./var/log/cron.log"

View File

@@ -1,17 +0,0 @@
- name: Disable tty logins
import_tasks: tty.yml
- name: Remove snapd
import_tasks: snapd.yml
- name: Remove cloud-init
import_tasks: cloud-init.yml
- name: Configure default logging
import_tasks: logging.yml
- name: Configure services
import_tasks: services.yml
- name: Install packages
import_tasks: packages.yml

View File

@@ -1,24 +0,0 @@
- name: Install additional packages
ansible.builtin.apt:
pkg: "{{ packages.apt }}"
state: latest
update_cache: yes
install_recommends: no
- name: Upgrade all packages
ansible.builtin.apt:
name: '*'
state: latest
update_cache: yes
- name: Install additional python packages
ansible.builtin.pip:
name: "{{ item }}"
executable: pip3
state: latest
loop: "{{ packages.pip }}"
- name: Cleanup
ansible.builtin.apt:
autoremove: yes
purge: yes

View File

@@ -1,5 +0,0 @@
- name: Disable & mask networkd-wait-online
ansible.builtin.systemd:
name: systemd-networkd-wait-online
enabled: no
masked: yes

View File

@@ -1,19 +0,0 @@
- name: Delete snapd package
ansible.builtin.apt:
name: snapd
state: absent
purge: yes
- name: Delete leftover files
ansible.builtin.file:
path: /root/snap
state: absent
- name: Hold snapd package
ansible.builtin.dpkg_selections:
name: snapd
selection: hold
- name: Reload systemd unit configurations
ansible.builtin.systemd:
daemon_reload: yes

View File

@@ -1,16 +0,0 @@
- name: Disable extra tty
ansible.builtin.lineinfile:
path: /etc/systemd/logind.conf
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
loop:
- regexp: '^#NAutoVTs='
line: 'NAutoVTs=1'
- regexp: '^#ReserveVT='
line: 'ReserveVT=11'
- name: Mask getty@tty1 service
ansible.builtin.systemd:
name: getty@tty1
enabled: no
masked: yes

View File

@@ -1,12 +0,0 @@
packages:
apt:
- jq
- python3-pip
pip:
- ansible-core
- jinja2
- lxml
- markupsafe
- pip
- setuptools
- wheel

View File

@@ -1,192 +0,0 @@
platform:
k3s:
version: v1.24.1+k3s1
gitops:
repository:
uri: https://code.spamasaurus.com/djpbessems/GitOps.MetaCluster.git
# revision: v0.1.0
revision: HEAD
packaged_components:
- name: traefik
namespace: kube-system
config: |2
additionalArguments:
- "--certificatesResolvers.stepca.acme.caserver=https://step-certificates.step-ca.svc.cluster.local/acme/acme/directory"
- "--certificatesResolvers.stepca.acme.email=admin"
- "--certificatesResolvers.stepca.acme.httpChallenge=true"
- "--certificatesResolvers.stepca.acme.httpChallenge.entryPoint=web"
- "--certificatesResolvers.stepca.acme.storage=/data/acme.json"
- "--certificatesResolvers.stepca.acme.tlsChallenge=true"
globalArguments: []
ingressRoute:
dashboard:
enabled: false
ports:
ssh:
port: 8022
protocol: TCP
web:
redirectTo: websecure
helm_repositories:
- name: longhorn
url: https://charts.longhorn.io
- name: harbor
url: https://helm.goharbor.io
- name: gitea-charts
url: https://dl.gitea.io/charts/
- name: argo
url: https://argoproj.github.io/argo-helm
- name: sealed-secrets
url: https://bitnami-labs.github.io/sealed-secrets
- name: smallstep
url: https://smallstep.github.io/helm-charts/
components:
longhorn:
helm:
version: 1.3.0
chart: longhorn/longhorn
parse_logic: cat values.yaml | yq eval '.. | select(has("repository")) | .repository + ":" + .tag'
chart_values: !unsafe |
defaultSettings:
defaultDataPath: /mnt/blockstorage
defaultReplicaCount: 1
ingress:
enabled: true
host: storage.{{ vapp['metacluster.fqdn'] }}
persistence:
defaultClassReplicaCount: 1
step-certificates:
helm:
version: 1.18.2+20220324
chart: smallstep/step-certificates
parse_logic: helm template . | yq --no-doc eval '.. | .image? | select(.)' | sed '/:/!s/$/:latest/' | sort -u
chart_values: !unsafe |
inject:
secrets:
ca_password: "{{ vapp['guestinfo.rootpw'] | b64encode }}"
provisioner_password: "{{ vapp['guestinfo.rootpw'] | b64encode }}"
ingress:
enabled: true
hosts:
- host: ca.{{ vapp['metacluster.fqdn'] }}
paths:
- path: /
pathType: Prefix
service:
targetPort: 9000
harbor:
helm:
version: 1.9.1 # (= Harbor v2.5.1)
chart: harbor/harbor
parse_logic: helm template . | yq --no-doc eval '.. | .image? | select(.)' | sort -u | awk '!/ /'
chart_values: !unsafe |
expose:
ingress:
annotations: {}
hosts:
core: registry.{{ vapp['metacluster.fqdn'] }}
tls:
certSource: none
enabled: false
externalURL: https://registry.{{ vapp['metacluster.fqdn'] }}
harborAdminPassword: "{{ vapp['guestinfo.rootpw'] }}"
notary:
enabled: false
gitea:
helm:
version: v5.0.9 # (= Gitea v1.16.8)
chart: gitea-charts/gitea
parse_logic: helm template . | yq --no-doc eval '.. | .image? | select(.)' | sort -u | sed '/:/!s/$/:latest/'
chart_values: !unsafe |
config:
offlineMode: true
gitea:
admin:
username: administrator
password: "{{ vapp['guestinfo.rootpw'] }}"
email: admin@{{ vapp['metacluster.fqdn'] }}
image:
pullPolicy: IfNotPresent
ingress:
enabled: true
hosts:
- host: git.{{ vapp['metacluster.fqdn'] }}
paths:
- path: /
pathType: Prefix
service:
ssh:
type: ClusterIP
port: 22
clusterIP:
argo-cd:
helm:
version: 4.9.7 # (= ArgoCD v2.4.2)
chart: argo/argo-cd
parse_logic: helm template . | yq --no-doc eval '.. | .image? | select(.)' | sort -u | awk '!/ /'
chart_values: !unsafe |
server:
extraArgs:
- --insecure
ingress:
enabled: true
hosts:
- gitops.{{ vapp['metacluster.fqdn'] }}
configs:
secret:
argocdServerAdminPassword: "{{ vapp['guestinfo.rootpw'] | password_hash('bcrypt') }}"
sealed-secrets:
helm:
version: 2.4.0 # (= SealedSecrets v0.18.1)
chart: sealed-secrets/sealed-secrets
parse_logic: helm template . | yq --no-doc eval '.. | .image? | select(.)' | sort -u | awk '!/ /'
dependencies:
ansible_galaxy_collections:
- ansible.posix
- ansible.utils
- community.crypto
- community.general
- community.vmware
- kubernetes.core
container_images:
- vmware/powerclicore:12.7
static_binaries:
- filename: govc
url: https://github.com/vmware/govmomi/releases/download/v0.29.0/govc_Linux_x86_64.tar.gz
archive: compressed
- filename: helm
url: https://get.helm.sh/helm-v3.9.0-linux-amd64.tar.gz
archive: compressed
extra_opts: --strip-components=1
- filename: skopeo
url: https://code.spamasaurus.com/api/packages/djpbessems/generic/skopeo/v1.9.1/skopeo
- filename: step
url: https://dl.step.sm/gh-release/cli/gh-release-header/v0.21.0/step_linux_0.21.0_amd64.tar.gz
archive: compressed
extra_opts: --strip-components=2
- filename: yq
url: http://github.com/mikefarah/yq/releases/download/v4.25.3/yq_linux_amd64
packages:
apt:
- lvm2
pip:
- jmespath
- kubernetes
- passlib
- pyvmomi

View File

@@ -1,7 +0,0 @@
downstream:
node_templates:
prefix: NodeTmpl_
images:
- url: https://stable.release.flatcar-linux.net/amd64-usr/3227.2.0/flatcar_production_vmware_ova.ova
name: flatcar.ova

View File

@@ -0,0 +1,16 @@
script = <<-EOH
$nic = get-netadapter
Get-NetAdapterBinding InterfaceAlias $nic.name ComponentID ms_tcpip6
EOH
control "ipv6" do
title 'Disabled network protocol IPv6'
desc '
This test assures that IPv6 is disabled
'
describe powershell(script) do
its('stdout') { should match 'False' }
end
end

View File

@@ -0,0 +1,29 @@
script = <<-EOH
# Initialize variable to empty array
$NonCompliantServices = @()
# Specify relevant services
$Services = @(
"wuauserv",
"W3SVC",
"XboxGipSvc",
"XblGameSave"
)
# Enumerate all services
$NonCompliantServices += Get-Service $Services -ErrorAction 'SilentlyContinue' | Where-Object {$_.StartType -ne 'Disabled'}
# Output; 'True' or list of noncompliant services
Write-Output ($True, $NonCompliantServices)[!($NonCompliantServices.Count -eq 0)]
EOH
control "disabled_services" do
title 'Disabled services'
desc '
This test assures that all unneeded services are set to "disabled".
'
describe powershell(script) do
its('stdout') { should match 'True' }
end
end

View File

@@ -0,0 +1,29 @@
script = <<-EOH
# Initialize variable to empty array
$LogicalDisks = @()
# Enumerate all logicaldisks
# DriveType:
# Unknown (0)
# No Root Directory (1)
# Removable Disk (2)
# Local Disk (3)
# Network Drive (4)
# Compact Disc (5)
# RAM Disk (6)
$LogicalDisks += Get-WmiObject -Class 'win32_logicaldisk' -Filter 'DriveType=3'
# Filter/Quantify
($LogicalDisks.Count -eq 1) -and (($LogicalDisks | Where-Object {$_.DeviceID -ne 'C:'}).Count -eq 0)
EOH
control "single_disk" do
title 'Single Disk'
desc '
This test assures that only a single disk (C:) is available
'
describe powershell(script) do
its('stdout') { should match 'True' }
end
end

View File

@@ -0,0 +1,54 @@
control "software_installed-7zip" do
title 'Included Default Applications: 7-Zip'
desc '
This test assures that the software application "7-Zip" is installed.
'
describe chocolatey_package('7zip.install') do
it { should be_installed }
end
end
# control "software_installed-dotnetfx" do
# title 'Included Default Applications: .NET'
# desc '
# This test assures that the software application ".NET" is installed.
# '
# describe chocolatey_package('dotnetfx') do
# it { should be_installed }
# end
# end
# control "software_installed-foxitreader" do
# title 'Included Default Applications: Foxit Reader'
# desc '
# This test assures that the software application "Foxit Reader" is installed.
# '
# describe chocolatey_package('foxitreader') do
# it { should be_installed }
# end
# end
# control "software_installed-notepadplusplus" do
# title 'Included Default Applications: Notepad++'
# desc '
# This test assures that the software application "Notepad++" is installed.
# '
# describe chocolatey_package('notepadplusplus') do
# it { should be_installed }
# end
# end
# control "software_installed-putty" do
# title 'Included Default Applications: Putty'
# desc '
# This test assures that the software application "PuTTy" is installed.
# '
# describe chocolatey_package('putty') do
# it { should be_installed }
# end
# end

View File

@@ -0,0 +1,10 @@
---
name: Windows 10 IoT Enterprise
title: Windows 10 IoT Enterprise InSpec Tests
summary: Unit test for Windows 10 IoT Enterprise
version: 1.0.0
maintainer: https://code.spamasaurus.com/djpbessems
copyright: https://code.spamasaurus.com/djpbessems
license: Proprietary
supports:
- platform-family: windows

View File

@@ -1,4 +0,0 @@
iso_url = "sn.itch.fyi/Repository/iso/Canonical/Ubuntu%20Server%2022.04/ubuntu-22.04-live-server-amd64.iso"
iso_checksum = "sha256:84AEAF7823C8C61BAA0AE862D0A06B03409394800000B3235854A6B38EB4856F"
// iso_url = "sn.itch.fyi/Repository/iso/Canonical/Ubuntu%20Server%2020.04/ubuntu-20.04.2-live-server-amd64.iso"
// iso_checksum = "sha256:D1F2BF834BBE9BB43FAF16F9BE992A6F3935E65BE0EDECE1DEE2AA6EB1767423"

View File

@@ -1,101 +0,0 @@
packer {
required_plugins {
}
}
source "vsphere-iso" "k8sbootstrap" {
vcenter_server = var.vcenter_server
username = var.vsphere_username
password = var.vsphere_password
insecure_connection = "true"
vm_name = "${var.vm_guestos}-${var.vm_name}"
datacenter = var.vsphere_datacenter
cluster = var.vsphere_cluster
host = var.vsphere_host
folder = var.vsphere_folder
datastore = var.vsphere_datastore
guest_os_type = "ubuntu64Guest"
boot_order = "disk,cdrom"
boot_command = [
"e<down><down><down><end>",
" autoinstall ds=nocloud;",
"<F10>"
]
boot_wait = "2s"
communicator = "ssh"
ssh_username = "ubuntu"
ssh_password = var.ssh_password
ssh_timeout = "20m"
ssh_handshake_attempts = "100"
ssh_pty = true
CPUs = 2
RAM = 8192
network_adapters {
network = var.vsphere_network
network_card = "vmxnet3"
}
storage {
disk_size = 76800
disk_thin_provisioned = true
}
disk_controller_type = ["pvscsi"]
usb_controller = ["xhci"]
cd_files = [
"packer/preseed/UbuntuServer22.04/user-data",
"packer/preseed/UbuntuServer22.04/meta-data"
]
cd_label = "cidata"
iso_url = local.iso_authenticatedurl
iso_checksum = var.iso_checksum
shutdown_command = "echo '${var.ssh_password}' | sudo -S shutdown -P now"
shutdown_timeout = "5m"
export {
images = false
output_directory = "/scratch/k8sbootstrap"
}
remove_cdrom = true
}
build {
sources = [
"source.vsphere-iso.k8sbootstrap"
]
provisioner "ansible" {
pause_before = "2m30s"
playbook_file = "ansible/playbook.yml"
user = "ubuntu"
ansible_env_vars = [
"ANSIBLE_CONFIG=ansible/ansible.cfg"
]
use_proxy = "false"
extra_arguments = [
"--extra-vars", "ansible_ssh_pass=${var.ssh_password}",
"--extra-vars", "repo_username=${var.repo_username}",
"--extra-vars", "repo_password=${var.repo_password}"
]
}
post-processor "shell-local" {
inline = [
"pwsh -command \"& scripts/Update-OvfConfiguration.ps1 \\",
" -OVFFile '/scratch/k8sbootstrap/${var.vm_guestos}-${var.vm_name}.ovf' \\",
" -Parameter @{'appliance.name'='${var.vm_guestos}';'appliance.version'='${var.vm_name}'}\"",
"pwsh -file scripts/Update-Manifest.ps1 \\",
" -ManifestFileName '/scratch/k8sbootstrap/${var.vm_guestos}-${var.vm_name}.mf'",
"ovftool --acceptAllEulas --allowExtraConfig --overwrite \\",
" '/scratch/k8sbootstrap/${var.vm_guestos}-${var.vm_name}.ovf' \\",
" /output/Kubernetes.Bootstrap.Appliance.ova"
]
}
}

View File

@@ -1,29 +0,0 @@
#cloud-config
autoinstall:
version: 1
locale: en_US
keyboard:
layout: en
variant: us
network:
network:
version: 2
ethernets:
ens192:
dhcp4: true
dhcp-identifier: mac
storage:
layout:
name: direct
identity:
hostname: packer-template
username: ubuntu
# password: $6$ZThRyfmSMh9499ar$KSZus58U/l58Efci0tiJEqDKFCpoy.rv25JjGRv5.iL33AQLTY2aljumkGiDAiX6LsjzVsGTgH85Tx4S.aTfx0
password: $6$rounds=4096$ZKfzRoaQOtc$M.fhOsI0gbLnJcCONXz/YkPfSoefP4i2/PQgzi2xHEi2x9CUhush.3VmYKL0XVr5JhoYvnLfFwqwR/1YYEqZy/
ssh:
install-server: yes
allow-pw: true
user-data:
disable_root: false
late-commands:
- echo 'ubuntu ALL=(ALL) NOPASSWD:ALL' > /target/etc/sudoers.d/ubuntu

View File

@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<servicing/>
<settings pass="windowsPE">
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<DiskConfiguration>
<Disk wcm:action="add">
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Type>Primary</Type>
<Extend>true</Extend>
</CreatePartition>
</CreatePartitions>
<ModifyPartitions>
<ModifyPartition wcm:action="add">
<Extend>false</Extend>
<Format>NTFS</Format>
<Letter>C</Letter>
<Order>1</Order>
<PartitionID>1</PartitionID>
<Label>Windows 10</Label>
</ModifyPartition>
</ModifyPartitions>
<DiskID>0</DiskID>
<WillWipeDisk>true</WillWipeDisk>
</Disk>
<WillShowUI>OnError</WillShowUI>
</DiskConfiguration>
<UserData>
<AcceptEula>true</AcceptEula>
<!-- <FullName>Spamasaurus Rex</FullName>
<Organization>Spamasaurus Rex</Organization> -->
<ProductKey>
<Key><<img-productkey>></Key>
<WillShowUI>Never</WillShowUI>
</ProductKey>
</UserData>
<ImageInstall>
<OSImage>
<InstallTo>
<DiskID>0</DiskID>
<PartitionID>1</PartitionID>
</InstallTo>
<WillShowUI>OnError</WillShowUI>
<InstallToAvailablePartition>false</InstallToAvailablePartition>
<InstallFrom>
<MetaData wcm:action="add">
<Key>/IMAGE/INDEX</Key>
<Value>3</Value>
</MetaData>
</InstallFrom>
</OSImage>
</ImageInstall>
</component>
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<SetupUILanguage>
<UILanguage>en-US</UILanguage>
</SetupUILanguage>
<InputLocale>en-US</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">
<EnableLUA>false</EnableLUA>
</component>
</settings>
<settings pass="oobeSystem">
<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>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</component>
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<UserAccounts>
<AdministratorPassword>
<Value><<img-password>></Value>
<PlainText>true</PlainText>
</AdministratorPassword>
</UserAccounts>
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Home</NetworkLocation>
<ProtectYourPC>1</ProtectYourPC>
</OOBE>
<AutoLogon>
<Password>
<Value><<img-password>></Value>
<PlainText>true</PlainText>
</Password>
<Username>administrator</Username>
<Enabled>true</Enabled>
</AutoLogon>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine>
<Description>Set execution policy 64bit</Description>
<Order>1</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>C:\Windows\SysWOW64\cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine>
<Description>Set execution policy 32bit</Description>
<Order>2</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c reg add "HKLM\System\CurrentControlSet\Control\Network\NewNetworkWindowOff"</CommandLine>
<Description>Disable new network prompt</Description>
<Order>3</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\Set-NetworkProfile.ps1</CommandLine>
<Description>Set network profile to private</Description>
<Order>4</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\Disable-WinRM.ps1</CommandLine>
<Description>Disable WinRM</Description>
<Order>5</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c a:\Install-VMwareTools.cmd</CommandLine>
<Order>13</Order>
<Description>Install VMware Tools</Description>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\Enable-WinRM.ps1</CommandLine>
<Description>Enable WinRM</Description>
<Order>99</Order>
</SynchronousCommand>
</FirstLogonCommands>
<ShowWindowsLive>false</ShowWindowsLive>
</component>
</settings>
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<OEMInformation>
<HelpCustomized>false</HelpCustomized>
</OEMInformation>
<!-- Rename computer here. -->
<ComputerName>packer-template</ComputerName>
<TimeZone>W. Europe Standard Time</TimeZone>
<RegisteredOwner/>
</component>
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<SkipAutoActivation>true</SkipAutoActivation>
</component>
</settings>
</unattend>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<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>
<component name="Microsoft-Windows-PnpSysprep" 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">
<PersistAllDeviceInstalls>true</PersistAllDeviceInstalls>
<DoNotCleanUpNonPresentDevices>true</DoNotCleanUpNonPresentDevices>
</component>
</settings>
<settings pass="oobeSystem">
<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>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</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">
<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>
<TimeZone>UTC</TimeZone>
<UserAccounts>
<AdministratorPassword>
<Value><<img-password>></Value>
<PlainText>true</PlainText>
</AdministratorPassword>
</UserAccounts>
</component>
</settings>
<settings pass="specialize">
</settings>
</unattend>

View File

@@ -1,12 +1,9 @@
variable "vcenter_server" {}
variable "vsphere_username" {}
variable "vsphere_password" {
sensitive = true
}
variable "vsphere_password" {}
variable "vsphere_host" {}
variable "vsphere_datacenter" {}
variable "vsphere_cluster" {}
variable "vsphere_templatefolder" {}
variable "vsphere_folder" {}
@@ -15,17 +12,7 @@ variable "vsphere_network" {}
variable "vm_name" {}
variable "vm_guestos" {}
variable "ssh_password" {
sensitive = true
}
variable "winrm_password" {}
variable "iso_url" {}
variable "iso_checksum" {}
variable "repo_username" {}
variable "repo_password" {
sensitive = true
}
local "iso_authenticatedurl" {
expression = "https://${var.repo_username}:${var.repo_password}@${var.iso_url}"
sensitive = true
}
variable "repo_password" {}

View File

@@ -1,9 +1,8 @@
vcenter_server = "bv11-vc.bessems.lan"
vsphere_username = "administrator@vsphere.local"
vsphere_datacenter = "DeSchakel"
vsphere_cluster = "Cluster.01"
vsphere_host = "bv11-esx02.bessems.lan"
vsphere_datastore = "NAS01.RAID5"
vsphere_host = "bv11-esx.bessems.lan"
vsphere_datastore = "Datastore01.SSD"
vsphere_folder = "/Packer"
vsphere_templatefolder = "/Templates"
vsphere_network = "LAN"

133
packer/windows10.pkr.hcl Normal file
View File

@@ -0,0 +1,133 @@
packer {
required_plugins {
windows-update = {
version = ">= 0.14.0"
source = "github.com/rgl/windows-update"
}
}
}
source "vsphere-iso" "win10" {
vcenter_server = var.vcenter_server
username = var.vsphere_username
password = var.vsphere_password
insecure_connection = "true"
vm_name = "${var.vm_guestos}-${var.vm_name}"
datacenter = var.vsphere_datacenter
host = var.vsphere_host
folder = var.vsphere_folder
datastore = var.vsphere_datastore
guest_os_type = "windows9_64Guest"
boot_order = "disk,cdrom"
boot_command = [""]
boot_wait = "5m"
communicator = "winrm"
winrm_username = "administrator"
winrm_password = var.winrm_password
winrm_timeout = "10m"
CPUs = 2
RAM = 8192
network_adapters {
network = var.vsphere_network
network_card = "vmxnet3"
}
storage {
disk_size = 20480
disk_thin_provisioned = true
}
disk_controller_type = ["lsilogic-sas"]
usb_controller = ["xhci"]
floppy_files = [
"packer/preseed/Windows10/Autounattend.xml",
"packer/preseed/Windows10/Sysprep_Unattend.xml",
"scripts/Set-NetworkProfile.ps1",
"scripts/Disable-WinRM.ps1",
"scripts/Enable-WinRM.ps1",
"scripts/Install-VMwareTools.cmd"
]
iso_checksum = "sha256:8D1663B71280533824CF95C7AB48ADAF5A187C38FCFF5B16A569F903688916D0"
iso_paths = [
"ISO-files/VMware-tools-windows-11.3.5-18557794/VMware-tools-windows-11.3.5-18557794.iso"
]
iso_url = "https://${var.repo_username}:${var.repo_password}@sn.itch.fyi/Repository/iso/Microsoft/Windows%2010/20H2/en_windows_10_enterprise_20H2_x64.iso"
shutdown_command = "C:\\Windows\\System32\\Sysprep\\sysprep.exe /generalize /oobe /unattend:A:\\Sysprep_Unattend.xml"
shutdown_timeout = "1h"
export {
images = false
output_directory = "/scratch/win10"
}
remove_cdrom = true
}
build {
sources = ["source.vsphere-iso.win10"]
provisioner "windows-update" {
filters = [
"exclude:$_.Title -like '*Preview*'",
"include:$true"
]
}
provisioner "powershell" {
inline = [
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12",
"Invoke-Expression ((New-Object Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))"
]
}
provisioner "powershell" {
inline = [
"choco config set --name=limit-output --value=LimitOutput",
"choco install -y 7zip.install",
"choco install -y sysinternals",
"choco install -y firefox"
]
}
provisioner "windows-update" {
filters = [
"exclude:$_.Title -like '*Preview*'",
"include:$true"
]
}
provisioner "powershell" {
inline = [
"New-Item -Path 'C:\\Payload\\Scripts' -ItemType 'Directory' -Force:$True -Confirm:$False"
]
}
provisioner "file" {
destination = "C:\\Payload\\"
source = "scripts/Windows10/payload/"
}
provisioner "powershell" {
scripts = [
"scripts/Windows10/Register-ScheduledTask.ps1"
]
}
post-processor "shell-local" {
inline = [
"pwsh -command \"& scripts/Update-OvfConfiguration.ps1 \\",
" -OVFFile '/scratch/win10/${var.vm_guestos}-${var.vm_name}.ovf' \\",
" -Parameter @{'appliance.name'='${var.vm_guestos}';'appliance.version'='${var.vm_name}'}\"",
"pwsh -file scripts/Update-Manifest.ps1 \\",
" -ManifestFileName '/scratch/win10/${var.vm_guestos}-${var.vm_name}.mf'",
"ovftool --acceptAllEulas --allowExtraConfig --overwrite \\",
" '/scratch/win10/${var.vm_guestos}-${var.vm_name}.ovf' \\",
" /output/Windows10.ova"
]
}
}

View File

@@ -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

18
scripts/Enable-WinRM.ps1 Normal file
View File

@@ -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
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" profile=public new remoteip=any
Set-Service winrm -startuptype "auto"
Restart-Service winrm

View File

@@ -0,0 +1,2 @@
@rem Silent mode, basic UI, no reboot
e:\setup64 /s /v "/qb REBOOT=R"

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8" ?>
<BlockList>
<!-- services to disable -->
<Services>
<Name>MVMCP2VAgent</Name>
<Name>VMTools</Name>
<Name> VMUpgradeHelper </Name>
<Name> vmvss </Name>
<Name>vmdesched</Name>
<Name>Virtual Server</Name>
<!-- Virtual Machine Helper -->
<Name>vmh</Name>
<!-- Xen-specific service -->
<Name>xensvc</Name>
</Services>
<!-- drivers to disable -->
<Drivers>
<Name>vmx_svga</Name>
<Name>vmmouse</Name>
<Name>vmscsi</Name>
<Name>amdpcn</Name>
<Name>PCnet</Name>
<Name>VMMEMCTL</Name>
<Name> pvscsi </Name>
<Name> vmci </Name>
<Name> vmmouse </Name>
<Name> vmaudio </Name>
<Name> vmrawdsk </Name>
<Name> vmxnet </Name>
<Name> vmxnet3ndis6 </Name>
<Name> vm3dmp </Name>
<Name> vmdebug </Name>
<Name> vmxnet3ndis5 </Name>
<Name>cirrus</Name>
<!-- storage drivers -->
<Name>buslogic</Name>
<Name>symc810</Name>
<Name>cpqarray</Name>
<Name>pcntn4m</Name>
<Name>cpqnf3</Name>
<Name>MRaidNT</Name>
<Name>Symc8XX</Name>
<!-- VIA chipset drivers -->
<Name>viaide</Name>
<Name>VIAudio</Name>
<Name>VIAPFD</Name>
<Name>viafilter</Name>
<Name>viaagp</Name>
<Name>viaagp1</Name>
<!-- network drivers: Intel(R) PRO/100 -->
<Name>E100B</Name>
<!-- tape drivers -->
<Name>4mmdat</Name>
<Name>4mmdat-SeSFT</Name>
<Name>SCSIChanger</Name>
<!-- Virtual Machine Monitor -->
<Name>vmm</Name>
<!-- Xen-specific drivers -->
<Name>xenevtchn</Name>
<Name>xenvbd</Name>
<Name>xennet</Name>
</Drivers>
<Programs>
<Name>ProMON</Name>
<Name>s3tray2</Name>
<Name>VMwareTray</Name>
<Name>VMwareUser</Name>
</Programs>
</BlockList>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
scripts/MVMC/MvmcCmdlet.dll Normal file

Binary file not shown.

Binary file not shown.

BIN
scripts/MVMC/Sshlib_x64.dll Normal file

Binary file not shown.

BIN
scripts/MVMC/Sshlib_x86.dll Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,23 @@
# You cannot enable Windows PowerShell Remoting on network connections that are set to Public
# Spin through all the network locations and if they are set to Public, set them to Private
# using the INetwork interface:
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa370750(v=vs.85).aspx
# For more info, see:
# http://blogs.msdn.com/b/powershell/archive/2009/04/03/setting-network-location-to-private.aspx
# Network location feature was only introduced in Windows Vista - no need to bother with this
# if the operating system is older than Vista
if([environment]::OSVersion.version.Major -lt 6) { return }
# You cannot change the network location if you are joined to a domain, so abort
if(1,3,4,5 -contains (Get-WmiObject win32_computersystem).DomainRole) { return }
# Get network connections
$networkListManager = [Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]"{DCB00C01-570F-4A9B-8D69-199FDBA5723B}"))
$connections = $networkListManager.GetNetworkConnections()
$connections |foreach {
Write-Host $_.GetNetwork().GetName()"category was previously set to"$_.GetNetwork().GetCategory()
$_.GetNetwork().SetCategory(1)
Write-Host $_.GetNetwork().GetName()"changed to category"$_.GetNetwork().GetCategory()
}

View File

@@ -47,77 +47,7 @@ $GetContentSplat = @{
}
$XML = [xml](Get-Content @GetContentSplat)
$NS = [System.Xml.XmlNamespaceManager]$XML.NameTable
[void]$NS.AddNamespace('ns', $XML.DocumentElement.xmlns)
[void]$NS.AddNamespace('ovf', $XML.DocumentElement.ovf)
[void]$NS.AddNamespace('rasd', $XML.DocumentElement.rasd)
[void]$NS.AddNamespace('vmw', $XML.DocumentElement.vmw)
# Create copy of existing 'Item/ResourceType'=17 (=Hard disk) node
$XMLDiskTemplate = $XML.SelectSingleNode("//ns:VirtualHardwareSection/ns:Item/rasd:ResourceType[.='17']", $NS).ParentNode.CloneNode($True)
ForEach ($Disk in $OVFConfig.DynamicDisks) {
# Determine next free available 'diskId'
$XMLDisks = $XML.SelectNodes("//ns:DiskSection/ns:Disk[contains(@ovf:diskId,'vmdisk')]", $NS)
$DiskId = 1
While ($XMLDisks.DiskId -contains "vmdisk$($DiskId)") {
$DiskId++
}
# Add new 'Disk' node (under 'DiskSection')
$XMLDisk = $XML.CreateElement('Disk', $XML.DocumentElement.xmlns)
$PowersMap = @{
KB = 10
MB = 20
GB = 30
TB = 40
PB = 50
}
If ($PowersMap.Keys -notcontains $Disk.UnitSize) {
# Invalid UnitSize; skipping adding new disk
Continue
}
[void]$XMLDisk.SetAttribute('capacityAllocationUnits', $NS.LookupNamespace('ovf'), "byte * 2^$($PowersMap[$Disk.UnitSize])")
[void]$XMLDisk.SetAttribute('format', $NS.LookupNamespace('ovf'), 'http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized')
[void]$XMLDisk.SetAttribute('diskId', $NS.LookupNamespace('ovf'), "vmdisk$($DiskId)")
[void]$XMLDisk.SetAttribute('capacity', $NS.LookupNamespace('ovf'), '${{vmconfig.disksize.{0}}}' -f $DiskId)
[void]$XMLDisk.SetAttribute('populatedSize', $NS.LookupNamespace('ovf'), 0)
[void]$XML.SelectSingleNode('//ns:DiskSection', $NS).AppendChild($XMLDisk)
# Add new 'Item/ResourceType' node (under 'VirtualHardwareSection')
$XMLDiskItem = $XMLDiskTemplate.CloneNode($True)
$XMLDiskItem.SelectSingleNode('rasd:AddressOnParent', $NS).InnerText = ($DiskId - 1)
$XMLDiskItem.SelectSingleNode('rasd:ElementName', $NS).InnerText = "Hard Disk $($DiskId)"
$XMLDiskItem.SelectSingleNode('rasd:HostResource', $NS).InnerText = "ovf:/disk/vmdisk$($DiskId)"
# Determine next free available and highest 'InstanceID'
$InstanceIDs = $XML.SelectNodes('//ns:VirtualHardwareSection/ns:Item/rasd:InstanceID', $NS).InnerText
$InstanceID = 1
While ($InstanceIDs -contains $InstanceID) {
$InstanceID++
}
$HighestInstanceID = ($InstanceIDs | Measure-Object -Maximum).Maximum
$XMLDiskItem.SelectSingleNode('rasd:InstanceID', $NS).InnerText = $InstanceID
[void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).InsertAfter(
$XMLDiskItem,
$XML.SelectSingleNode("//ns:VirtualHardwareSection/ns:Item/rasd:InstanceID[.='$($HighestInstanceID)']", $NS).ParentNode
)
$OVFConfig.PropertyCategories[0].ProductProperties += @{
Key = "vmconfig.disksize.$($DiskId)"
Type = If ([boolean]$Disk.Constraints.Minimum -or [boolean]$Disk.Constraints.Maximum) {
"Int($($Disk.Constraints.Minimum)..$($Disk.Constraints.Maximum))"
}
Else {
'Int'
}
Label = "Disk $($DiskId) size*"
Description = "$($Disk.Description) (in $($Disk.UnitSize))".Trim()
DefaultValue = "$($Disk.Constraints.Minimum)"
Configurations = '*'
UserConfigurable = 'true'
}
}
Write-Host "Inserted $($OVFConfig.DynamicDisks.Count) new node(s) into 'DiskSection' and 'VirtualHardwareSection' respectively"
[void]$NS.AddNamespace('Any', $XML.DocumentElement.xmlns)
If ($OVFConfig.DeploymentConfigurations.Count -gt 0) {
$XMLSection = $XML.CreateElement('DeploymentOptionSection', $XML.DocumentElement.xmlns)
@@ -128,72 +58,52 @@ If ($OVFConfig.DeploymentConfigurations.Count -gt 0) {
ForEach ($Configuration in $OVFConfig.DeploymentConfigurations) {
$XMLConfig = $XML.CreateElement('Configuration', $XML.DocumentElement.xmlns)
[void]$XMLConfig.SetAttribute('id', $NS.LookupNamespace('ovf'), $Configuration.Id)
$XMLConfigAttrId = $XML.CreateAttribute('id', $XML.DocumentElement.ovf)
$XMLConfigAttrId.Value = $Configuration.Id
$XMLConfigLabel = $XML.CreateElement('Label', $XML.DocumentElement.xmlns)
$XMLConfigLabel.InnerText = $Configuration.Label
$XMLConfigDescription = $XML.CreateElement('Description', $XML.DocumentElement.xmlns)
$XMLConfigDescription.InnerText = $Configuration.Description
[void]$XMLConfig.Attributes.Append($XMLConfigAttrId)
[void]$XMLConfig.AppendChild($XMLConfigLabel)
[void]$XMLConfig.AppendChild($XMLConfigDescription)
[void]$XMLSection.AppendChild($XMLConfig)
}
[void]$XML.SelectSingleNode('//ns:Envelope', $NS).InsertAfter($XMLSection, $XML.SelectSingleNode('//ns:NetworkSection', $NS))
[void]$XML.SelectSingleNode('//Any:Envelope', $NS).InsertAfter($XMLSection, $XML.SelectSingleNode('//Any:NetworkSection', $NS))
Write-Host "Inserted 'DeploymentOptionSection' with $($Configuration.Count) nodes"
If ($OVFConfig.DeploymentConfigurations.Count -eq $OVFConfig.DeploymentConfigurations.Size.Count) {
# Create copies of existing 'Item/ResourceType' nodes
$XMLCPUTemplate = $XML.SelectSingleNode("//ns:VirtualHardwareSection/ns:Item/rasd:ResourceType[.='3']", $NS).ParentNode.CloneNode($True)
$XMLMemoryTemplate = $XML.SelectSingleNode("//ns:VirtualHardwareSection/ns:Item/rasd:ResourceType[.='4']", $NS).ParentNode.CloneNode($True)
# Delete existing nodes
ForEach ($Node in $XML.SelectNodes("//ns:VirtualHardwareSection/ns:Item/rasd:ResourceType[.='3' or .='4']", $NS).ParentNode) {
[void]$Node.ParentNode.RemoveChild($Node)
}
# Add adjusted 'Item/ResourceType' nodes
ForEach ($Configuration in $OVFConfig.DeploymentConfigurations) {
$XMLCPU = $XMLCPUTemplate.CloneNode($True)
[void]$XMLCPU.SetAttribute('configuration', $NS.LookupNamespace('ovf'), $Configuration.Id)
$XMLCPU.SelectSingleNode('rasd:ElementName', $NS).InnerText = '{0} virtual CPU(s)' -f $Configuration.Size.CPU
$XMLCPU.SelectSingleNode('rasd:VirtualQuantity', $NS).InnerText = $Configuration.Size.CPU
$XMLMemory = $XMLMemoryTemplate.CloneNode($True)
[void]$XMLMemory.SetAttribute('configuration', $NS.LookupNamespace('ovf'), $Configuration.Id)
$XMLMemory.SelectSingleNode('rasd:ElementName', $NS).InnerText = '{0}MB of memory' -f $Configuration.Size.Memory
$XMLMemory.SelectSingleNode('rasd:VirtualQuantity', $NS).InnerText = $Configuration.Size.Memory
[void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).InsertAfter(
$XMLCPU,
$XML.SelectSingleNode('//ns:VirtualHardwareSection/ns:System', $NS)
)
[void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).InsertAfter(
$XMLMemory,
$XML.SelectSingleNode('//ns:VirtualHardwareSection/ns:System', $NS)
)
}
}
}
[void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).SetAttribute('transport', $NS.LookupNamespace('ovf'), 'com.vmware.guestInfo')
$XMLAttrTransport = $XML.CreateAttribute('transport', $XML.DocumentElement.ovf)
$XMLAttrTransport.Value = 'com.vmware.guestInfo'
[void]$XML.SelectSingleNode('//Any:VirtualHardwareSection', $NS).Attributes.Append($XMLAttrTransport)
ForEach ($ExtraConfig in $OVFConfig.AdvancedOptions) {
$XMLExtraConfig = $XML.CreateElement('vmw:ExtraConfig', $XML.DocumentElement.vmw)
[void]$XMLExtraConfig.SetAttribute('required', $NS.LookupNamespace('ovf'), "$([boolean]$ExtraConfig.Required)".ToLower())
[void]$XMLExtraConfig.SetAttribute('key', $NS.LookupNamespace('vmw'), $ExtraConfig.Key)
[void]$XMLExtraConfig.SetAttribute('value', $NS.LookupNamespace('vmw'), $ExtraConfig.Value)
$XMLExtraConfigAttrRequired = $XML.CreateAttribute('required', $XML.DocumentElement.ovf)
$XMLExtraConfigAttrRequired.Value = "$([boolean]$ExtraConfig.Required)".ToLower()
$XMLExtraConfigAttrKey = $XML.CreateAttribute('key', $XML.DocumentElement.vmw)
$XMLExtraConfigAttrKey.Value = $ExtraConfig.Key
$XMLExtraConfigAttrValue = $XML.CreateAttribute('value', $XML.DocumentElement.vmw)
$XMLExtraConfigAttrValue.Value = $ExtraConfig.Value
[void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).AppendChild($XMLExtraConfig)
[void]$XMLExtraConfig.Attributes.Append($XMLExtraConfigAttrRequired)
[void]$XMLExtraConfig.Attributes.Append($XMLExtraConfigAttrKey)
[void]$XMLExtraConfig.Attributes.Append($XMLExtraConfigAttrValue)
[void]$XML.SelectSingleNode('//Any:VirtualHardwareSection', $NS).AppendChild($XMLExtraConfig)
}
Write-Host "Added $($OVFConfig.AdvancedOptions.Count) 'vmw:ExtraConfig' node(s)"
Write-Host "Added $($OVFConfig.AdvancedOptions.Count) 'vmw:ExtraConfig' nodes"
$XMLProductSection = $XML.SelectSingleNode('//ns:ProductSection', $NS)
$XMLProductSection = $XML.SelectSingleNode('//Any:ProductSection', $NS)
If ($XMLProductSection -eq $Null) {
$XMLProductSection = $XML.CreateElement('ProductSection', $XML.DocumentElement.xmlns)
[void]$XML.SelectSingleNode('//ns:VirtualSystem', $NS).AppendChild($XMLProductSection)
[void]$XML.SelectSingleNode('//Any:VirtualSystem', $NS).AppendChild($XMLProductSection)
Write-Host "Inserted 'ProductSection'"
} Else {
ForEach ($Child in $XMLProductSection.SelectNodes('//ns:ProductSection/child::*', $NS)) {
ForEach ($Child in $XMLProductSection.SelectNodes('//Any:ProductSection/child::*', $NS)) {
[void]$Child.ParentNode.RemoveChild($Child)
}
Write-Host "Destroyed pre-existing children in 'ProductSection'"
@@ -214,13 +124,15 @@ ForEach ($Category in $OVFConfig.PropertyCategories) {
ForEach ($Property in $Category.ProductProperties) {
$XMLProperty = $XML.CreateElement('Property', $XML.DocumentElement.xmlns)
[void]$XMLProperty.SetAttribute('key', $NS.LookupNamespace('ovf'), $Property.Key)
$XMLPropertyAttrKey = $XML.CreateAttribute('key', $XML.DocumentElement.ovf)
$XMLPropertyAttrKey.Value = $Property.Key
$XMLPropertyAttrType = $XML.CreateAttribute('type', $XML.DocumentElement.ovf)
Switch -regex ($Property.Type) {
'^boolean' {
[void]$XMLProperty.SetAttribute('type', $NS.LookupNamespace('ovf'), 'boolean')
$XMLPropertyAttrType.Value = 'boolean'
}
'^int' {
[void]$XMLProperty.SetAttribute('type', $NS.LookupNamespace('ovf'), 'uint16')
$XMLPropertyAttrType.Value = 'uint8'
$Qualifiers = @()
If ($Property.Type -match '^int\((\d*)\.\.(\d*)\)') {
If ($Matches[1]) {
@@ -229,16 +141,23 @@ ForEach ($Category in $OVFConfig.PropertyCategories) {
If ($Matches[2]) {
$Qualifiers += "MaxValue($($Matches[2]))"
}
[void]$XMLProperty.SetAttribute('qualifiers', $NS.LookupNamespace('ovf'), $Qualifiers -join ' ')
$XMLPropertyAttrQualifiers = $XML.CreateAttribute('qualifiers', $XML.DocumentElement.ovf)
$XMLPropertyAttrQualifiers.Value = $Qualifiers -join ' '
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrQualifiers)
}
}
'^ip' {
[void]$XMLProperty.SetAttribute('type', $NS.LookupNamespace('ovf'), 'string')
[void]$XMLProperty.SetAttribute('qualifiers', $NS.LookupNamespace('vmw'), 'Ip')
$XMLPropertyAttrType.Value = 'string'
$XMLPropertyAttrQualifiers = $XML.CreateAttribute('qualifiers', $XML.DocumentElement.vmw)
$XMLPropertyAttrQualifiers.Value = 'Ip'
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrQualifiers)
}
'^password' {
[void]$XMLProperty.SetAttribute('type', $NS.LookupNamespace('ovf'), 'string')
[void]$XMLProperty.SetAttribute('password', $NS.LookupNamespace('ovf'), 'true')
$XMLPropertyAttrType.Value = 'string'
$XMLPropertyAttrPassword = $XML.CreateAttribute('password', $XML.DocumentElement.ovf)
$XMLPropertyAttrPassword.Value = 'true'
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrPassword)
$Qualifiers = @()
If ($Property.Type -match '^password\((\d*)\.\.(\d*)\)') {
If ($Matches[1]) {
@@ -247,11 +166,13 @@ ForEach ($Category in $OVFConfig.PropertyCategories) {
If ($Matches[2]) {
$Qualifiers += "MaxLen($($Matches[2]))"
}
[void]$XMLProperty.SetAttribute('qualifiers', $NS.LookupNamespace('ovf'), $Qualifiers -join ' ')
$XMLPropertyAttrQualifiers = $XML.CreateAttribute('qualifiers', $XML.DocumentElement.ovf)
$XMLPropertyAttrQualifiers.Value = $Qualifiers -join ' '
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrQualifiers)
}
}
'^string' {
[void]$XMLProperty.SetAttribute('type', $NS.LookupNamespace('ovf'), 'string')
$XMLPropertyAttrType.Value = 'string'
$Qualifiers = @()
If ($Property.Type -match '^string\((\d*)\.\.(\d*)\)') {
If ($Matches[1]) {
@@ -260,19 +181,28 @@ ForEach ($Category in $OVFConfig.PropertyCategories) {
If ($Matches[2]) {
$Qualifiers += "MaxLen($($Matches[2]))"
}
[void]$XMLProperty.SetAttribute('qualifiers', $NS.LookupNamespace('ovf'), $Qualifiers -join ' ')
$XMLPropertyAttrQualifiers = $XML.CreateAttribute('qualifiers', $XML.DocumentElement.ovf)
$XMLPropertyAttrQualifiers.Value = $Qualifiers -join ' '
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrQualifiers)
} ElseIf ($Property.Type -match '^string\[(.*)\]') {
[void]$XMLProperty.SetAttribute('qualifiers', $NS.LookupNamespace('ovf'), "ValueMap{$($Matches[1] -replace '","', '", "')}")
$XMLPropertyAttrQualifiers = $XML.CreateAttribute('qualifiers', $XML.DocumentElement.ovf)
$XMLPropertyAttrQualifiers.Value = "ValueMap{$($Matches[1] -replace '","', '", "')}"
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrQualifiers)
}
}
}
[void]$XMLProperty.SetAttribute('userConfigurable', $NS.LookupNamespace('ovf'), "$([boolean]$Property.UserConfigurable)".ToLower())
$XMLPropertyAttrUserConfigurable = $XML.CreateAttribute('userConfigurable', $XML.DocumentElement.ovf)
$XMLPropertyAttrUserConfigurable.Value = "$([boolean]$Property.UserConfigurable)".ToLower()
$XMLPropertyAttrValue = $XML.CreateAttribute('value', $XML.DocumentElement.ovf)
If ($Property.Type -eq 'boolean') {
[void]$XMLProperty.SetAttribute('value', $NS.LookupNamespace('ovf'), "$([boolean]$Property.DefaultValue)".ToLower())
$XMLPropertyAttrValue.Value = "$([boolean]$Property.DefaultValue)".ToLower()
} Else {
[void]$XMLProperty.SetAttribute('value', $NS.LookupNamespace('ovf'), $Property.DefaultValue)
$XMLPropertyAttrValue.Value = $Property.DefaultValue
}
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrKey)
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrType)
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrUserConfigurable)
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrValue)
If ($Property.Label) {
$XMLPropertyLabel = $XML.CreateElement('Label', $XML.DocumentElement.xmlns)
@@ -286,19 +216,30 @@ ForEach ($Category in $OVFConfig.PropertyCategories) {
}
If (($Property.Configurations.Count -eq 1) -and ($Property.Configurations -eq '*')) {
[void]$XMLProperty.SetAttribute('configuration', $NS.LookupNamespace('ovf'), $OVFConfig.DeploymentConfigurations.Id -join ' ')
$XMLPropertyAttrConfiguration = $XML.CreateAttribute('configuration', $XML.DocumentElement.ovf)
$XMLPropertyAttrConfiguration.Value = $OVFConfig.DeploymentConfigurations.Id -join ' '
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrConfiguration)
} ElseIf ($Property.Configurations.Count -gt 0) {
[void]$XMLProperty.SetAttribute('configuration', $NS.LookupNamespace('ovf'), $Property.Configurations -join ' ')
$XMLPropertyAttrConfiguration = $XML.CreateAttribute('configuration', $XML.DocumentElement.ovf)
$XMLPropertyAttrConfiguration.Value = $Property.Configurations -join ' '
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrConfiguration)
}
If ($Property.Value.Count -eq 1) {
[void]$XMLProperty.SetAttribute('value', $NS.LookupNamespace('ovf'), $Property.Value)
$XMLPropertyAttrValue = $XML.CreateAttribute('value', $XML.DocumentElement.ovf)
$XMLPropertyAttrValue.Value = $Property.Value
[void]$XMLProperty.Attributes.Append($XMLPropertyAttrValue)
} ElseIf ($Property.Value.Count -gt 1) {
ForEach ($Value in $Property.Value) {
$XMLValue = $XML.CreateElement('Value', $XML.DocumentElement.xmlns)
[void]$XMLValue.SetAttribute('value', $NS.LookupNamespace('ovf'), $Value)
[void]$XMLValue.SetAttribute('configuration', $NS.LookupNamespace('ovf'), $Value)
$XMLValueAttrValue = $XML.CreateAttribute('value', $XML.DocumentElement.ovf)
$XMLValueAttrValue.Value = $Value
$XMLValueAttrConfiguration = $XML.CreateAttribute('configuration', $XML.DocumentElement.ovf)
$XMLValueAttrConfiguration.Value = $Value
[void]$XMLValue.Attributes.Append($XMLValueAttrValue)
[void]$XMLValue.Attributes.Append($XMLValueAttrConfiguration)
[void]$XMLProperty.AppendChild($XMLValue)
}

View File

@@ -1,79 +1,46 @@
DeploymentConfigurations:
- Id: small
Label: 'Ubuntu Server 22.04 [SMALL: 2 vCPU/4GB RAM]'
Description: Ubuntu Server 22.04.x
Size:
CPU: 2
Memory: 4096
- Id: medium
Label: 'Ubuntu Server 22.04 [MEDIUM: 4 vCPU/8GB RAM]'
Description: Ubuntu Server 22.04.x
Size:
CPU: 4
Memory: 8192
DynamicDisks:
- Description: Longhorn persistent storage
UnitSize: GB
Constraints:
Minimum: 100
Maximum: ''
- Id: domainmember
Label: Domain member
Description: Windows 10 client joined to an Active Directory domain
- Id: standalone
Label: Stand-alone
Description: Stand-alone Windows 10 client
PropertyCategories:
# - Name: 0) Deployment information
# ProductProperties:
# - Key: deployment.type
# Type: string
# Value:
# - small
# - medium
# UserConfigurable: false
- Name: 1) Kubernetes
- Name: 0) Deployment information
ProductProperties:
- Key: metacluster.fqdn
Type: string(1..)
Label: Appliance FQDN*
Description: 'Respective subdomains will be available for each component (e.g. storage.example.org); this address should already be configured as a wildcard record within your DNS zone.'
DefaultValue: 'meta.k8s.cluster'
Configurations: '*'
UserConfigurable: true
- Name: 2) Operating System
- Key: deployment.type
Type: string
Value:
- domainmember
- standalone
UserConfigurable: false
- Name: 1) Operating System
ProductProperties:
- Key: guestinfo.hostname
Type: string(1..15)
Label: Hostname*
Description: ''
DefaultValue: 'META-NODE-{{ hostname.suffix }}'
Description: '(max length: 15 characters)'
DefaultValue: ''
Configurations: '*'
UserConfigurable: true
- Key: guestinfo.rootpw
- Key: guestinfo.administratorpw
Type: password(7..)
Label: Local root password*
Description: ''
DefaultValue: ''
Configurations: '*'
Label: Local administrator password*
Description: Must meet password complexity rules
DefaultValue: password
Configurations:
- standalone
UserConfigurable: true
- Key: guestinfo.rootsshkey
Type: password(1..)
Label: Local root SSH public key*
Description: This line should start with 'ssh-rsa AAAAB3N'
DefaultValue: ''
Configurations: '*'
- Key: guestinfo.ntpserver
Type: string(1..)
Label: Time server*
Description: A comma-separated list of timeservers
DefaultValue: 0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org
Configurations:
- standalone
UserConfigurable: true
- Name: 3) Networking
- Name: 2) Networking
ProductProperties:
- Key: guestinfo.ipaddress
Type: ip
Label: IP Address*
@@ -81,7 +48,6 @@ PropertyCategories:
DefaultValue: ''
Configurations: '*'
UserConfigurable: true
- Key: guestinfo.prefixlength
Type: int(8..32)
Label: Subnet prefix length*
@@ -89,7 +55,6 @@ PropertyCategories:
DefaultValue: '24'
Configurations: '*'
UserConfigurable: true
- Key: guestinfo.dnsserver
Type: ip
Label: DNS server*
@@ -97,7 +62,6 @@ PropertyCategories:
DefaultValue: ''
Configurations: '*'
UserConfigurable: true
- Key: guestinfo.gateway
Type: ip
Label: Gateway*
@@ -105,42 +69,32 @@ PropertyCategories:
DefaultValue: ''
Configurations: '*'
UserConfigurable: true
- Key: guestinfo.ntpserver
Type: string(1..)
Label: Time server*
Description: A comma-separated list of timeservers
DefaultValue: 0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org
Configurations: '*'
UserConfigurable: true
- Name: 4) Hypervisor
- Name: 3) Active Directory membership
ProductProperties:
- Key: hv.fqdn
- Key: addsconfig.domainname
Type: string(1..)
Label: vCenter FQDN/IP-address*
Description: The address of the vCenter instance which this bootstrap appliance will interact with for provisioning new VM's.
DefaultValue: ''
Configurations: '*'
Label: Domain name*
Description: Must be able to be resolved through provided DNS server
DefaultValue: example.org
Configurations:
- domainmember
UserConfigurable: true
- Key: hv.username
- Key: addsconfig.username
Type: string(1..)
Label: vCenter username*
Description: The username which this bootstrap appliance will authenticate with to the vCenter instance.
DefaultValue: ''
Configurations: '*'
Label: Domain account username*
Description: ''
DefaultValue: username
Configurations:
- domainmember
UserConfigurable: true
- Key: hv.password
- Key: addsconfig.password
Type: password(1..)
Label: vCenter password ...*
Description: The password which this bootstrap appliance will authenticate with to the vCenter instance.
DefaultValue: ''
Configurations: '*'
Label: Domain account password*
Description: ''
DefaultValue: password
Configurations:
- domainmember
UserConfigurable: true
AdvancedOptions:
- Key: appliance.name
Value: "{{ appliance.name }}"
@@ -151,9 +105,6 @@ AdvancedOptions:
---
Variables:
- Name: hostname.suffix
Expression: |
(-join ((48..57) + (97..122) | Get-Random -Count 5 | % {[char]$_})).ToUpper()
- Name: appliance.name
Expression: |
$Parameter['appliance.name']

View File

@@ -0,0 +1,7 @@
[CmdletBinding()]
Param(
# No parameters
)
# Create scheduled task
& schtasks.exe /Create /TN 'FirstBoot' /SC ONSTART /RU SYSTEM /TR "powershell.exe -file C:\Payload\Apply-FirstBootConfig.ps1"

View File

@@ -0,0 +1,244 @@
[CmdletBinding()]
Param(
# No parameters
)
$SetLocationSplat = @{
Path = $PSScriptRoot
}
Set-Location @SetLocationSplat
$NewEventLogSplat = @{
LogName = 'Application'
Source = 'FirstBoot'
ErrorAction = 'SilentlyContinue'
}
New-EventLog @NewEventLogSplat
$WriteEventLogSplat = @{
LogName = 'Application'
Source = 'FirstBoot'
EntryType = 'Information'
EventID = 1
Message = "FirstBoot sequence initiated [working directory: '$PWD']"
}
Write-EventLog @WriteEventLogSplat
$VMwareToolsExecutable = "C:\Program Files\VMware\VMware Tools\vmtoolsd.exe"
[xml]$ovfEnv = & $VMwareToolsExecutable --cmd "info-get guestinfo.ovfEnv" | Out-String
$ovfProperties = $ovfEnv.ChildNodes.NextSibling.PropertySection.Property
$ovfPropertyValues = @{}
foreach ($ovfProperty in $ovfProperties) {
$ovfPropertyValues[$ovfProperty.key] = $ovfProperty.Value
}
# Check for mandatory values
Switch ($ovfPropertyValues['deployment.type']) {
'domainmember' {
$MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.gateway', 'addsconfig.domainname', 'addsconfig.username', 'addsconfig.password'), @()
}
'standalone' {
$MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.gateway', 'guestinfo.administratorpw', 'guestinfo.ntpserver'), @()
}
default {
# Mandatory values missing, cannot provision.
$WriteEventLogSplat = @{
LogName = 'Application'
Source = 'FirstBoot'
EntryType = 'Error'
EventID = 66
Message = "Unexpected or no value set for property 'deployment.type', cannot provision."
}
Write-EventLog @WriteEventLogSplat
& schtasks.exe /Change /TN 'FirstBoot' /DISABLE
Stop-Computer -Force
Exit
}
}
ForEach ($Property in $MandatoryProperties) {
If (!$ovfPropertyValues[$Property]) {
$MissingProperties += $Property
}
}
If ($MissingProperties.Length -gt 0) {
# Mandatory values missing, cannot provision.
$WriteEventLogSplat = @{
LogName = 'Application'
Source = 'FirstBoot'
EntryType = 'Error'
EventID = 66
Message = "Missing values for mandatory properties $(($MissingProperties | ForEach-Object {"'{0}'" -f $_}) -join ', '), cannot provision."
}
Write-EventLog @WriteEventLogSplat
& schtasks.exe /Change /TN 'FirstBoot' /DISABLE
Stop-Computer -Force
Exit
}
# Set hostname and description
If ($Env:ComputerName -ne $ovfPropertyValues['guestinfo.hostname']) {
$RenameComputerSplat = @{
NewName = $ovfPropertyValues['guestinfo.hostname']
Force = $True
Confirm = $False
}
Rename-Computer @RenameComputerSplat
$SetCimInstanceSplat = @{
InputObject = (Get-CimInstance -ClassName 'Win32_OperatingSystem')
Property = @{
Description = $ovfPropertyValues['guestinfo.hostname']
}
}
Set-CimInstance @SetCimInstanceSplat
# Restart the computer to apply changes
Restart-Computer -Force
Exit
}
# Configure network interface
If ((Get-WmiObject -Class 'Win32_NetworkAdapterConfiguration').IPAddress -NotContains $ovfPropertyValues['guestinfo.ipaddress']) {
$NewNetIPAddressSplat = @{
InterfaceAlias = (Get-NetAdapter).Name
AddressFamily = 'IPv4'
IPAddress = $ovfPropertyValues['guestinfo.ipaddress']
PrefixLength = $ovfPropertyValues['guestinfo.prefixlength']
DefaultGateway = $ovfPropertyValues['guestinfo.gateway']
}
$IPAddress = New-NetIPAddress @NewNetIPAddressSplat
# Wait for network connection to become available
$Timestamp, $TimeoutMinutes = (Get-Date), 5
Do {
If ($Timestamp.AddMinutes($TimeoutMinutes) -lt (Get-Date)) {
$WriteEventLogSplat = @{
LogName = 'Application'
Source = 'FirstBoot'
EntryType = 'Warning'
EventID = 13
Message = "Timeout after $($TimeoutMinutes) minutes waiting for network connection to become available."
}
Write-EventLog @WriteEventLogSplat
Break
}
Start-Sleep -Milliseconds 250
$GetNetIPAddressSplat = @{
IPAddress = $ovfPropertyValues['guestinfo.ipaddress']
InterfaceIndex = $IPAddress.InterfaceIndex
AddressFamily = 'IPv4'
ErrorAction = 'SilentlyContinue'
}
} Until ((Get-NetIPAddress @GetNetIPAddressSplat).AddressState -eq 'Preferred')
$OldErrorActionPreference, $ErrorActionPreference = $ErrorActionPreference, 'SilentlyContinue'
$TestNetConnectionSplat = @{
ComputerName = ([IPAddress]$ovfPropertyValues['guestinfo.dnsserver']).IPAddressToString
InformationLevel = 'Quiet'
}
$SetDnsClientServerAddressSplat = @{
InterfaceAlias = (Get-NetAdapter).Name
ServerAddresses = If (
[boolean]($ovfPropertyValues['guestinfo.dnsserver'] -as [IPaddress]) -and (Test-NetConnection @TestNetConnectionSplat)) {
($ovfPropertyValues['guestinfo.dnsserver'])
} else {
('127.0.0.1')
}
Validate = $False
}
Set-DnsClientServerAddress @SetDnsClientServerAddressSplat
$ErrorActionPreference, $OldErrorActionPreference = $OldErrorActionPreference, $NULL
}
Switch ($ovfPropertyValues['deployment.type']) {
'domainmember' {
# Join Active Directory domain as member
If (!(Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain) {
$AddComputerSplat = @{
DomainName = $ovfPropertyValues['addsconfig.domainname']
Credential = New-Object System.Management.Automation.PSCredential(
$ovfPropertyValues['addsconfig.username'],
(ConvertTo-SecureString $ovfPropertyValues['addsconfig.password'] -AsPlainText -Force)
)
# OUPath = $ovfPropertyValues['addsconfig.organizationalunit']
Restart = $True
Force = $True
Confirm = $False
}
Add-Computer @AddComputerSplat
# Previous cmdlet performs a reboot on completion; so these are commented out
# Restart-Computer -Force
# Exit
}
}
'standalone' {
# Change password of built-in Administrator
$BuiltinAdministrator = (Get-LocalUser | Where-Object {$_.SID -match '-500'})
$ConvertToSecureStringSplat = @{
String = $ovfPropertyValues['guestinfo.administratorpw']
AsPlainText = $True
Force = $True
}
$SetLocalUserSplat = @{
InputObject = $BuiltinAdministrator
Password = ConvertTo-SecureString @ConvertToSecureStringSplat
PasswordNeverExpires = $True
AccountNeverExpires = $True
### This setting is not allowed on the last administrator
# UserMayChangePassword = $False
Confirm = $False
}
Set-LocalUser @SetLocalUserSplat
$EnableLocalUserSplat = @{
InputObject = $BuiltinAdministrator
Confirm = $False
}
Enable-LocalUser @EnableLocalUserSplat
}
}
# Iterate through and invoke all payload scripts
#! TODO: add registry values to determine which scripts have already been invoked (in case of intermediate reboots)
$GetItemSplat = @{
Path = "$($PSScriptRoot)\Scripts\*.ps1"
}
ForEach ($Script in (Get-Item @GetItemSplat)) {
Try {
$WriteEventLogSplat = @{
LogName = 'Application'
Source = 'FirstBoot'
EntryType = 'Information'
EventID = 4
Message = "Running script: '$($Script.FullName)'"
}
Write-EventLog @WriteEventLogSplat
& $Script.FullName -Parameter $ovfPropertyValues
}
Catch {
$WriteEventLogSplat = @{
LogName = 'Application'
Source = 'FirstBoot'
EntryType = 'Error'
EventID = 66
Message = @"
Error occurred while executing script '$($Script.Name)':
$($_.Exception.Message)
"@
}
Write-EventLog @WriteEventLogSplat
}
}
$WriteEventLogSplat = @{
LogName = 'Application'
Source = 'FirstBoot'
EntryType = 'Information'
EventID = 42
Message = 'FirstBoot sequence applied and finished'
}
Write-EventLog @WriteEventLogSplat
& schtasks.exe /Change /TN 'FirstBoot' /DISABLE