committed by
GitHub
19 changed files with 1108 additions and 25 deletions
@ -0,0 +1 @@ |
|||||
|
.vscode |
||||
@ -0,0 +1,2 @@ |
|||||
|
inventory.yaml |
||||
|
vault.yaml |
||||
@ -0,0 +1,55 @@ |
|||||
|
# Installation on RHEL 9 Automatisation |
||||
|
|
||||
|
Ansible Playbook allowing to create ostree images for edge using Ansible. |
||||
|
|
||||
|
## Pre-requisites |
||||
|
|
||||
|
RHEL 9 pre-requisites : |
||||
|
|
||||
|
- RHEL 9 is installed |
||||
|
- The Red Hat repositories **baseos** and **appstream** are reachable |
||||
|
|
||||
|
Microshift pre-requisites : |
||||
|
|
||||
|
- RHEL 9.2 or 9.3 |
||||
|
- LVM volume group (VG) with unused space |
||||
|
|
||||
|
## Pre-requisites on the target machine |
||||
|
|
||||
|
```sh |
||||
|
sudo subscription-manager register --username $RHN_LOGIN --auto-attach |
||||
|
sudo subscription-manager attach --pool=$RHN_POOL_ID |
||||
|
``` |
||||
|
|
||||
|
## Ansible Config |
||||
|
|
||||
|
Create a `inventory.yaml` file inside the ansible folder or define the inventory path inside the `ansible.cfg` file |
||||
|
|
||||
|
Update `config.yaml` in `ansible/group_vars/all/` to match your environment. |
||||
|
|
||||
|
Create an ansible vault named `vault.yaml` in `ansible/group_vars/all/` with the following content. |
||||
|
|
||||
|
```yaml |
||||
|
blueprint_admin_password_hash: # Generate one with "mkpasswd -m bcrypt" |
||||
|
blueprint_kiosk_password_hash: # Generate one with "mkpasswd -m bcrypt" |
||||
|
kickstart_microshift_pull_secret: # Generate one on https://console.redhat.com/openshift/install/pull-secret |
||||
|
``` |
||||
|
|
||||
|
Install the required collections. |
||||
|
|
||||
|
```sh |
||||
|
ansible-galaxy collection install -r requirements.yaml |
||||
|
``` |
||||
|
|
||||
|
## Prepare the target machine |
||||
|
|
||||
|
```sh |
||||
|
ansible-playbook prerequisites.yaml |
||||
|
ansible-playbook bootstrap-ostree.yaml |
||||
|
``` |
||||
|
|
||||
|
## Regular builds |
||||
|
|
||||
|
```sh |
||||
|
ansible-playbook build.yaml |
||||
|
``` |
||||
@ -0,0 +1,6 @@ |
|||||
|
[defaults] |
||||
|
# Use the provided inventory |
||||
|
inventory = inventory.yaml |
||||
|
|
||||
|
# Use a forked copy of the infra.osbuild plugins |
||||
|
library = plugins/modules |
||||
@ -0,0 +1,77 @@ |
|||||
|
- name: Create the initial ostree repo |
||||
|
hosts: all |
||||
|
become: false |
||||
|
tasks: |
||||
|
- name: Read blueprint |
||||
|
register: results |
||||
|
args: |
||||
|
executable: /usr/bin/python3 |
||||
|
stdin: "{{ lookup('ansible.builtin.file', playbook_dir ~ '/files/minimal.toml') }}" |
||||
|
shell: | |
||||
|
import toml |
||||
|
import json |
||||
|
import sys |
||||
|
str=sys.stdin.read() |
||||
|
obj=toml.loads(str) |
||||
|
print(json.dumps(obj)) |
||||
|
delegate_to: localhost |
||||
|
become: false |
||||
|
changed_when: false |
||||
|
|
||||
|
- set_fact: |
||||
|
blueprint_name: '{{ blueprint_object.name }}' |
||||
|
vars: |
||||
|
blueprint_object: '{{ results.stdout | from_json }}' |
||||
|
|
||||
|
- name: Push blueprint |
||||
|
infra.osbuild.push_blueprint: |
||||
|
blueprint: "{{ lookup('ansible.builtin.file', playbook_dir ~ '/files/minimal.toml') }}" |
||||
|
|
||||
|
- name: Start ostree compose |
||||
|
infra.osbuild.start_compose: |
||||
|
blueprint: "{{ blueprint_name }}" |
||||
|
allow_duplicate: true |
||||
|
compose_type: edge-commit |
||||
|
timeout: "{{ compose_timeout }}" |
||||
|
register: builder_compose_start_out |
||||
|
|
||||
|
- ansible.builtin.set_fact: |
||||
|
compose_id: "{{ builder_compose_start_out['result']['body']['build_id'] }}" |
||||
|
|
||||
|
- name: Wait for compose to finish |
||||
|
infra.osbuild.wait_compose: |
||||
|
compose_id: "{{ compose_id }}" |
||||
|
timeout: 3600 |
||||
|
|
||||
|
- ansible.builtin.tempfile: |
||||
|
state: directory |
||||
|
suffix: build |
||||
|
register: tmp |
||||
|
|
||||
|
- name: Export the compose artifact |
||||
|
infra.osbuild.export_compose: # noqa only-builtins |
||||
|
compose_id: "{{ compose_id }}" |
||||
|
dest: "{{ tmp.path }}/{{ compose_id }}.tar" |
||||
|
|
||||
|
- name: Clear directory /var/www/repo |
||||
|
ansible.builtin.file: |
||||
|
path: "{{ www_location }}/repo" |
||||
|
state: absent |
||||
|
|
||||
|
- name: Extract compose artifact into /var/www/repo |
||||
|
ansible.builtin.unarchive: |
||||
|
src: "{{ tmp.path }}/{{ compose_id }}.tar" |
||||
|
dest: "{{ www_location }}" |
||||
|
remote_src: true |
||||
|
become: true |
||||
|
|
||||
|
- name: Create an empty tree |
||||
|
ansible.builtin.file: |
||||
|
path: "{{ tmp.path }}/empty-tree" |
||||
|
mode: '0755' |
||||
|
state: directory |
||||
|
become: true |
||||
|
|
||||
|
- name: Create an empty commit |
||||
|
ansible.builtin.shell: "ostree --repo={{ www_location }}/repo commit -b 'empty' --tree=dir={{ tmp.path }}/empty-tree" |
||||
|
become: true |
||||
@ -0,0 +1,270 @@ |
|||||
|
- name: Build the Kiosk images |
||||
|
hosts: all |
||||
|
become: false |
||||
|
tasks: |
||||
|
- name: Checkout the git repo |
||||
|
ansible.builtin.git: |
||||
|
repo: 'https://github.com/nmasse-itix/red-hat-kiosk.git' |
||||
|
dest: "{{ ansible_user_dir }}/red-hat-kiosk" |
||||
|
update: yes |
||||
|
clone: yes |
||||
|
|
||||
|
- ansible.builtin.tempfile: |
||||
|
state: directory |
||||
|
suffix: -build |
||||
|
register: tmp |
||||
|
|
||||
|
## |
||||
|
## Cleanup |
||||
|
## |
||||
|
|
||||
|
- name: Get all images for removal |
||||
|
ansible.builtin.command: /usr/bin/composer-cli compose list |
||||
|
register: builder_output |
||||
|
changed_when: false |
||||
|
|
||||
|
- name: Remove each image by UUID |
||||
|
ansible.builtin.command: "/usr/bin/composer-cli compose delete {{ (item | split)[0] }}" |
||||
|
loop: "{{ builder_output.stdout_lines }}" |
||||
|
loop_control: |
||||
|
label: "{{ (item | split)[0] }}" |
||||
|
changed_when: true |
||||
|
when: (item | split)[0] != "ID" |
||||
|
|
||||
|
## |
||||
|
## RPM construction |
||||
|
## |
||||
|
|
||||
|
- debug: |
||||
|
msg: "Starting RPM build..." |
||||
|
|
||||
|
- name: Ensure ~/rpmbuild is a symbolic link |
||||
|
ansible.builtin.file: |
||||
|
src: "{{ ansible_user_dir }}/red-hat-kiosk/rpms" |
||||
|
dest: "{{ ansible_user_dir }}/rpmbuild" |
||||
|
state: link |
||||
|
|
||||
|
- name: Build the kiosk-config RPMS |
||||
|
ansible.builtin.shell: | |
||||
|
spectool -g -R {{ ansible_user_dir }}/rpmbuild/SPECS/kiosk-config.spec |
||||
|
rpmbuild -ba {{ ansible_user_dir }}/rpmbuild/SPECS/kiosk-config.spec |
||||
|
|
||||
|
- name: Build the microshift-manifests RPM |
||||
|
ansible.builtin.shell: | |
||||
|
spectool -g -R {{ ansible_user_dir }}/rpmbuild/SPECS/microshift-manifests.spec |
||||
|
rpmbuild -ba {{ ansible_user_dir }}/rpmbuild/SPECS/microshift-manifests.spec |
||||
|
|
||||
|
- name: Ensure the VENDOR directory exists |
||||
|
ansible.builtin.file: |
||||
|
path: "{{ ansible_user_dir }}/rpmbuild/VENDOR" |
||||
|
state: directory |
||||
|
mode: '0755' |
||||
|
|
||||
|
- name: Download Google Chrome RPM |
||||
|
ansible.builtin.get_url: |
||||
|
url: https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm |
||||
|
dest: "{{ ansible_user_dir }}/rpmbuild/VENDOR/google-chrome-stable_current_x86_64.rpm" |
||||
|
|
||||
|
- name: Rebuild the Google Chrome RPM |
||||
|
ansible.builtin.shell: | |
||||
|
set -Eeuo pipefail |
||||
|
rpmrebuild -s {{ ansible_user_dir }}/rpmbuild/SPECS/google-chrome-stable.spec -p {{ ansible_user_dir }}/rpmbuild/VENDOR/google-chrome-stable_current_x86_64.rpm |
||||
|
RPM=$(rpm -q {{ ansible_user_dir }}/rpmbuild/VENDOR/google-chrome-stable_current_x86_64.rpm) |
||||
|
mkdir -p {{ ansible_user_dir }}/rpmbuild/BUILDROOT/$RPM/ |
||||
|
rpm2cpio {{ ansible_user_dir }}/rpmbuild/VENDOR/google-chrome-stable_current_x86_64.rpm | cpio -idmv -D {{ ansible_user_dir }}/rpmbuild/BUILDROOT/$RPM/ |
||||
|
mv {{ ansible_user_dir }}/rpmbuild/BUILDROOT/$RPM/opt/google/ {{ ansible_user_dir }}/rpmbuild/BUILDROOT/$RPM/usr/bin/ |
||||
|
cd {{ ansible_user_dir }}/rpmbuild/BUILDROOT/$RPM/usr/bin/ |
||||
|
rm -f google-chrome-stable |
||||
|
ln -s google/chrome/google-chrome google-chrome-stable |
||||
|
ln -s google/chrome/google-chrome chrome |
||||
|
sed -i.${EPOCHREALTIME:-bak} 's|/opt/google|/usr/bin/google|g' {{ ansible_user_dir }}/rpmbuild/SPECS/google-chrome-stable.spec |
||||
|
rpmbuild -bb {{ ansible_user_dir }}/rpmbuild/SPECS/google-chrome-stable.spec |
||||
|
args: |
||||
|
executable: /bin/bash |
||||
|
register: rebuild_result |
||||
|
failed_when: rebuild_result.rc != 0 |
||||
|
|
||||
|
- name: Get built RPMS |
||||
|
ansible.builtin.find: |
||||
|
path: "{{ ansible_user_dir }}/rpmbuild/RPMS/x86_64/" |
||||
|
patterns: "*.rpm" |
||||
|
register: build_rpms |
||||
|
|
||||
|
- name: Extract filenames from paths of built RPMs |
||||
|
ansible.builtin.set_fact: |
||||
|
rpm_filenames: "{{ build_rpms.files | map(attribute='path') | list }}" |
||||
|
|
||||
|
- name: Copy RPMs to the repository location |
||||
|
ansible.builtin.copy: |
||||
|
src: '{{ item }}' |
||||
|
dest: "{{ repo_location }}" |
||||
|
owner: root |
||||
|
group: root |
||||
|
mode: '0644' |
||||
|
remote_src: yes |
||||
|
loop: '{{ rpm_filenames }}' |
||||
|
loop_control: |
||||
|
label: "{{ item | basename }}" |
||||
|
become: true |
||||
|
|
||||
|
- name: Update the repository with createrepo |
||||
|
become: true |
||||
|
ansible.builtin.command: |
||||
|
cmd: "createrepo {{ repo_location }}" |
||||
|
|
||||
|
- name: Clean dnf cache |
||||
|
become: true |
||||
|
ansible.builtin.command: |
||||
|
cmd: dnf clean all |
||||
|
|
||||
|
## |
||||
|
## Ostree construction |
||||
|
## |
||||
|
|
||||
|
- debug: |
||||
|
msg: "Starting ostree build..." |
||||
|
|
||||
|
- name: Parse blueprint |
||||
|
register: results |
||||
|
args: |
||||
|
executable: /usr/bin/python3 |
||||
|
stdin: "{{ lookup('ansible.builtin.template', 'kiosk.toml.j2') }}" |
||||
|
shell: | |
||||
|
import toml |
||||
|
import json |
||||
|
import sys |
||||
|
str=sys.stdin.read() |
||||
|
obj=toml.loads(str) |
||||
|
print(json.dumps(obj)) |
||||
|
become: false |
||||
|
changed_when: false |
||||
|
|
||||
|
- set_fact: |
||||
|
blueprint_name: '{{ blueprint_object.name }}' |
||||
|
vars: |
||||
|
blueprint_object: '{{ results.stdout | from_json }}' |
||||
|
|
||||
|
- name: Push Blueprint |
||||
|
infra.osbuild.push_blueprint: |
||||
|
blueprint: "{{ lookup('ansible.builtin.template', 'kiosk.toml.j2') }}" |
||||
|
|
||||
|
- name: Start ostree compose |
||||
|
start_compose2: |
||||
|
blueprint: "{{ blueprint_name }}" |
||||
|
allow_duplicate: true |
||||
|
compose_type: edge-commit |
||||
|
ostree_ref: "rhel/9/{{ ansible_facts['userspace_architecture'] }}/edge-kiosk" |
||||
|
ostree_parent: "rhel/9/{{ ansible_facts['userspace_architecture'] }}/edge" |
||||
|
ostree_url: http://{{ ansible_default_ipv4.address }}/repo |
||||
|
timeout: "{{ compose_timeout }}" |
||||
|
register: builder_compose_start_out |
||||
|
|
||||
|
- ansible.builtin.set_fact: |
||||
|
compose_id: "{{ builder_compose_start_out['result']['body']['build_id'] }}" |
||||
|
|
||||
|
- name: Wait for compose to finish |
||||
|
infra.osbuild.wait_compose: |
||||
|
compose_id: "{{ compose_id }}" |
||||
|
timeout: 3600 |
||||
|
|
||||
|
- name: Export the compose artifact |
||||
|
infra.osbuild.export_compose: # noqa only-builtins |
||||
|
compose_id: "{{ compose_id }}" |
||||
|
dest: "{{ tmp.path }}/{{ compose_id }}.tar" |
||||
|
|
||||
|
- name: Create commit directory |
||||
|
ansible.builtin.file: |
||||
|
path: "{{ tmp.path }}/{{ compose_id }}" |
||||
|
mode: '0755' |
||||
|
state: directory |
||||
|
|
||||
|
- name: Extract compose artifact |
||||
|
ansible.builtin.unarchive: |
||||
|
src: "{{ tmp.path }}/{{ compose_id }}.tar" |
||||
|
dest: "{{ tmp.path }}/{{ compose_id }}" |
||||
|
remote_src: true |
||||
|
|
||||
|
- name: Pull local ostree repository |
||||
|
ansible.builtin.shell: ostree --repo={{ www_location }}/repo pull-local "{{ tmp.path }}/{{ compose_id }}/repo" |
||||
|
become: true |
||||
|
|
||||
|
## |
||||
|
## ISO Construction |
||||
|
## |
||||
|
|
||||
|
- debug: |
||||
|
msg: "Starting ISO build..." |
||||
|
|
||||
|
- name: Read blueprint |
||||
|
register: results |
||||
|
args: |
||||
|
executable: /usr/bin/python3 |
||||
|
stdin: "{{ lookup('ansible.builtin.file', playbook_dir ~ '/files/edge-installer.toml') }}" |
||||
|
shell: | |
||||
|
import toml |
||||
|
import json |
||||
|
import sys |
||||
|
str=sys.stdin.read() |
||||
|
obj=toml.loads(str) |
||||
|
print(json.dumps(obj)) |
||||
|
delegate_to: localhost |
||||
|
become: false |
||||
|
changed_when: false |
||||
|
|
||||
|
- set_fact: |
||||
|
blueprint_name: '{{ blueprint_object.name }}' |
||||
|
vars: |
||||
|
blueprint_object: '{{ results.stdout | from_json }}' |
||||
|
|
||||
|
- name: Push blueprint |
||||
|
infra.osbuild.push_blueprint: |
||||
|
blueprint: "{{ lookup('ansible.builtin.file', playbook_dir ~ '/files/edge-installer.toml') }}" |
||||
|
|
||||
|
- name: Start ostree compose |
||||
|
start_compose2: |
||||
|
blueprint: "{{ blueprint_name }}" |
||||
|
allow_duplicate: true |
||||
|
compose_type: edge-installer |
||||
|
ostree_ref: empty |
||||
|
ostree_url: http://{{ ansible_default_ipv4.address }}/repo |
||||
|
timeout: "{{ compose_timeout }}" |
||||
|
register: builder_compose_start_out |
||||
|
|
||||
|
- ansible.builtin.set_fact: |
||||
|
compose_id: "{{ builder_compose_start_out['result']['body']['build_id'] }}" |
||||
|
|
||||
|
- name: Wait for compose to finish |
||||
|
infra.osbuild.wait_compose: |
||||
|
compose_id: "{{ compose_id }}" |
||||
|
timeout: 3600 |
||||
|
|
||||
|
- name: Export the compose artifact |
||||
|
infra.osbuild.export_compose: # noqa only-builtins |
||||
|
compose_id: "{{ compose_id }}" |
||||
|
dest: "{{ tmp.path }}/{{ compose_id }}.iso" |
||||
|
|
||||
|
- name: Create kiosk.ks from template |
||||
|
ansible.builtin.template: |
||||
|
src: "kiosk.ks.j2" |
||||
|
dest: "{{ tmp.path }}/kiosk.ks" |
||||
|
|
||||
|
- name: Validate kiosk.ks using ksvalidator |
||||
|
ansible.builtin.command: |
||||
|
cmd: "ksvalidator {{ tmp.path }}/kiosk.ks" |
||||
|
|
||||
|
- name: Create new kiosk.iso file |
||||
|
ansible.builtin.command: |
||||
|
cmd: "mkksiso -r 'inst.ks' --ks {{ tmp.path }}/kiosk.ks {{ tmp.path }}/{{ compose_id }}.iso {{ tmp.path }}/kiosk.iso" |
||||
|
|
||||
|
- name: Copy new ISO to /var/www |
||||
|
copy: |
||||
|
src: "{{ tmp.path }}/kiosk.iso" |
||||
|
dest: "{{ www_location }}/kiosk.iso" |
||||
|
remote_src: true |
||||
|
become: true |
||||
|
|
||||
|
post_tasks: |
||||
|
- ansible.builtin.file: |
||||
|
path: "{{ tmp.path }}.iso" |
||||
|
state: absent |
||||
|
when: tmp is defined |
||||
@ -0,0 +1,6 @@ |
|||||
|
name = "edge-installer" |
||||
|
description = "" |
||||
|
version = "0.0.0" |
||||
|
modules = [] |
||||
|
groups = [] |
||||
|
packages = [] |
||||
@ -0,0 +1,6 @@ |
|||||
|
name = "minimal-rhel9" |
||||
|
description = "minimal blueprint for ostree commit" |
||||
|
version = "1.1.0" |
||||
|
modules = [] |
||||
|
groups = [] |
||||
|
distro = "rhel-93" |
||||
@ -0,0 +1,5 @@ |
|||||
|
repo_location: /opt/custom-rpms |
||||
|
blueprint_admin_ssh_public_key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFW62WJXI1ZCMfNA4w0dMpL0fsldhbEfULNGIUB0nQui nmasse@localhost.localdomain |
||||
|
www_location: /var/www |
||||
|
compose_timeout: 300 |
||||
|
kickstart_root_disk: /dev/disk/by-path/pci-0000:00:12.0-ata-1 |
||||
@ -0,0 +1,5 @@ |
|||||
|
# README |
||||
|
|
||||
|
This is a modified version of the start_compose module from the repo https://github.com/redhat-cop/infra.osbuild, commit 6e3416233c84623b2edd503a4b50d15c61d6c155. |
||||
|
|
||||
|
The module has been patched to specify the ostree ref when starting a compose of type "ostree-commit". |
||||
@ -0,0 +1,339 @@ |
|||||
|
#!/usr/bin/python |
||||
|
# Copyright: Red Hat Inc. |
||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) |
||||
|
|
||||
|
ANSIBLE_METADATA = { |
||||
|
"metadata_version": "1.1", |
||||
|
"status": ["preview"], |
||||
|
"supported_by": "community", |
||||
|
} |
||||
|
|
||||
|
DOCUMENTATION = """ |
||||
|
--- |
||||
|
module: start_compose2 |
||||
|
short_description: Start an ostree compose |
||||
|
description: |
||||
|
- Start an ostree compose |
||||
|
author: |
||||
|
- Adam Miller (@maxamillion) |
||||
|
- Chris Santiago (@resoluteCoder) |
||||
|
options: |
||||
|
blueprint: |
||||
|
description: |
||||
|
- Name of blueprint to iniate a build for |
||||
|
type: str |
||||
|
required: true |
||||
|
size: |
||||
|
description: |
||||
|
- Image size expressed in MiB |
||||
|
type: int |
||||
|
default: 0 |
||||
|
required: false |
||||
|
profile: |
||||
|
description: |
||||
|
- Path to profile toml file |
||||
|
type: str |
||||
|
default: "" |
||||
|
required: false |
||||
|
image_name: |
||||
|
description: |
||||
|
- Image name |
||||
|
type: str |
||||
|
default: "" |
||||
|
required: false |
||||
|
allow_duplicate: |
||||
|
description: |
||||
|
- Allow a duplicate version'd compose. |
||||
|
- (Default osbuild composer functionality is to allow duplicate composes) |
||||
|
type: bool |
||||
|
default: True |
||||
|
required: false |
||||
|
compose_type: |
||||
|
description: |
||||
|
- type of compose |
||||
|
type: str |
||||
|
default: "edge-commit" |
||||
|
required: false |
||||
|
choices: |
||||
|
- ami |
||||
|
- edge-commit |
||||
|
- edge-container |
||||
|
- edge-installer |
||||
|
- edge-raw-image |
||||
|
- edge-simplified-installer |
||||
|
- image-installer |
||||
|
- oci |
||||
|
- openstack |
||||
|
- qcow2 |
||||
|
- tar |
||||
|
- vhd |
||||
|
- vmdk |
||||
|
- iot-commit |
||||
|
- iot-container |
||||
|
- iot-installer |
||||
|
- iot-raw-image |
||||
|
- container |
||||
|
ostree_ref: |
||||
|
description: |
||||
|
- ostree ref |
||||
|
type: str |
||||
|
default: "" |
||||
|
required: false |
||||
|
ostree_parent: |
||||
|
description: |
||||
|
- ostree parent |
||||
|
type: str |
||||
|
default: "" |
||||
|
required: false |
||||
|
ostree_url: |
||||
|
description: |
||||
|
- ostree URL |
||||
|
type: str |
||||
|
default: "" |
||||
|
required: false |
||||
|
timeout: |
||||
|
description: |
||||
|
- timeout for osbuild-compose requests, in seconds |
||||
|
type: int |
||||
|
default: 120 |
||||
|
required: false |
||||
|
notes: |
||||
|
- THIS MODULE IS NOT IDEMPOTENT UNLESS C(allow_duplicate) is set to C(false) |
||||
|
- The params C(profile) and C(image_name) are required together. |
||||
|
- The C(profile) option is not fully implemented at this time. |
||||
|
""" |
||||
|
|
||||
|
EXAMPLES = """ |
||||
|
- name: Start ostree compose size 4096 |
||||
|
start_compose2: |
||||
|
blueprint: rhel-for-edge-demo |
||||
|
image_name: testimage |
||||
|
size: 4096 |
||||
|
|
||||
|
- name: Start ostree compose with idempotent transaction |
||||
|
start_compose2: |
||||
|
blueprint: rhel-for-edge-demo |
||||
|
allow_duplicate: false |
||||
|
""" |
||||
|
import json # noqa E402 |
||||
|
import socket |
||||
|
from typing import Any # noqa E402 |
||||
|
|
||||
|
from ansible.module_utils.basic import AnsibleModule # noqa E402 |
||||
|
from ansible_collections.infra.osbuild.plugins.module_utils.weldr import Weldr # noqa E402 |
||||
|
|
||||
|
argument_spec = dict( |
||||
|
blueprint=dict(type="str", required=True), |
||||
|
size=dict(type="int", required=False, default=0), |
||||
|
profile=dict(type="str", required=False, default=""), |
||||
|
image_name=dict(type="str", required=False, default=""), |
||||
|
allow_duplicate=dict(type="bool", required=False, default=True), |
||||
|
compose_type=dict( |
||||
|
type="str", |
||||
|
required=False, |
||||
|
default="edge-commit", |
||||
|
choices=[ |
||||
|
"ami", |
||||
|
"edge-commit", |
||||
|
"edge-container", |
||||
|
"edge-installer", |
||||
|
"edge-raw-image", |
||||
|
"edge-simplified-installer", |
||||
|
"image-installer", |
||||
|
"oci", |
||||
|
"openstack", |
||||
|
"qcow2", |
||||
|
"tar", |
||||
|
"vhd", |
||||
|
"vmdk", |
||||
|
"iot-commit", |
||||
|
"iot-container", |
||||
|
"iot-installer", |
||||
|
"iot-raw-image", |
||||
|
"container", |
||||
|
], |
||||
|
), |
||||
|
ostree_ref=dict(type="str", required=False, default=""), |
||||
|
ostree_parent=dict(type="str", required=False, default=""), |
||||
|
ostree_url=dict(type="str", required=False, default=""), |
||||
|
timeout=dict(type="int", required=False, default=120), |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def start_compose(module, weldr): |
||||
|
changed: bool = False |
||||
|
dupe_compose: list = [] |
||||
|
blueprint_info: dict = weldr.api.get_blueprints_info(module.params["blueprint"]) |
||||
|
blueprint_version: int = blueprint_info["blueprints"][0]["version"] |
||||
|
|
||||
|
# Add check if compose_type is supported |
||||
|
supported_compose_type: dict = weldr.api.get_compose_types() |
||||
|
|
||||
|
is_supported: dict = next((item for item in supported_compose_type["types"] if item["name"] == module.params["compose_type"]), {}) |
||||
|
|
||||
|
if not is_supported: |
||||
|
module.fail_json( |
||||
|
msg="%s is not a valid image type, valid types are: %s" |
||||
|
% (module.params["compose_type"], [[v for k, v in t.items() if k == "name"] for t in supported_compose_type["types"]]), |
||||
|
changed=changed |
||||
|
) |
||||
|
else: |
||||
|
if not is_supported["enabled"]: |
||||
|
module.fail_json( |
||||
|
msg="%s is not a supported image type, supported image types are: %s" |
||||
|
% (module.params["compose_type"], [[v for k, v in t.items() if k == "enabled" and v is True] for t in supported_compose_type["types"]]), |
||||
|
changed=changed |
||||
|
) |
||||
|
|
||||
|
if not module.params["allow_duplicate"]: |
||||
|
# only do all this query and filtering if needed |
||||
|
|
||||
|
compose_queue: dict = weldr.api.get_compose_queue() |
||||
|
# {"new":[],"run":[{"id":"930a1584-8737-4b61-ba77-582780f0ff2d","blueprint":"base-image-with-tmux","version":"0.0.5","compose_type":"edge-commit","image_size":0,"queue_status":"RUNNING","job_created":1654620015.4107578,"job_started":1654620015.415151}]} |
||||
|
|
||||
|
compose_queue_run_dupe: list = [ |
||||
|
compose for compose in compose_queue["run"] if (compose["blueprint"] == module.params["blueprint"]) and (compose["version"] == blueprint_version) |
||||
|
] |
||||
|
compose_queue_new_dupe: list = [ |
||||
|
compose for compose in compose_queue["new"] if (compose["blueprint"] == module.params["blueprint"]) and (compose["version"] == blueprint_version) |
||||
|
] |
||||
|
|
||||
|
compose_finished: dict = weldr.api.get_compose_finished() |
||||
|
# {"finished":[{"id":"930a1584-8737-4b61-ba77-582780f0ff2d","blueprint":"base-image-with-tmux","version":"0.0.5","compose_type":"edge-commit","image_size":8192,"queue_status":"FINISHED","job_created":1654620015.4107578,"job_started":1654620015.415151,"job_finished":1654620302.9069786}]} |
||||
|
compose_finished_dupe: list = [ |
||||
|
compose |
||||
|
for compose in compose_finished["finished"] |
||||
|
if (compose["blueprint"] == module.params["blueprint"]) and (compose["version"] == blueprint_version) |
||||
|
] |
||||
|
|
||||
|
compose_failed: dict = weldr.api.get_compose_failed() |
||||
|
# {"failed":[]} |
||||
|
compose_failed_dupe: list = [ |
||||
|
compose |
||||
|
for compose in compose_failed["failed"] |
||||
|
if (compose["blueprint"] == module.params["blueprint"]) and (compose["version"] == blueprint_version) |
||||
|
] |
||||
|
|
||||
|
dupe_compose: list = compose_queue_run_dupe + compose_queue_new_dupe + compose_failed_dupe + compose_finished_dupe |
||||
|
|
||||
|
if module.params["allow_duplicate"] or (len(dupe_compose) == 0): |
||||
|
# FIXME - build to POST payload and POST that ish |
||||
|
compose_settings: dict[str, Any] = { |
||||
|
"blueprint_name": module.params["blueprint"], |
||||
|
"compose_type": module.params["compose_type"], |
||||
|
"branch": "master", |
||||
|
"size": module.params["size"], |
||||
|
} |
||||
|
|
||||
|
if "edge-commit" in module.params["compose_type"] or "installer" in module.params["compose_type"] or "raw" in module.params["compose_type"]: |
||||
|
compose_settings["ostree"] = { |
||||
|
"ref": module.params["ostree_ref"], |
||||
|
"parent": module.params["ostree_parent"], |
||||
|
"url": module.params["ostree_url"], |
||||
|
} |
||||
|
|
||||
|
try: |
||||
|
result: dict = weldr.api.post_compose(json.dumps(compose_settings), timeout=module.params["timeout"]) |
||||
|
except socket.timeout: |
||||
|
# it's possible we don't get a response back from weldr because on the |
||||
|
# very first run including a new content source composer will build a repo cache |
||||
|
# and when that happens we get an empty JSON response |
||||
|
|
||||
|
compose_queue: dict = weldr.api.get_compose_queue() |
||||
|
# {"new":[],"run":[{"id":"930a1584-8737-4b61-ba77-582780f0ff2d","blueprint":"base-image-with-tmux","version":"0.0.5","compose_type":"edge-commit","image_size":0,"queue_status":"RUNNING","job_created":1654620015.4107578,"job_started":1654620015.415151}]} |
||||
|
|
||||
|
submitted_compose_uuid: str = "" |
||||
|
|
||||
|
submitted_compose_found_run: list[dict[str, str]] = [ |
||||
|
compose |
||||
|
for compose in compose_queue["run"] |
||||
|
if (compose["blueprint"] == module.params["blueprint"]) and (compose["version"] == blueprint_version) |
||||
|
] |
||||
|
if submitted_compose_found_run: |
||||
|
# we expect it to be RUNNING, so check that first |
||||
|
submitted_compose_uuid: str = submitted_compose_found_run[0]["id"] |
||||
|
else: |
||||
|
# didn't find it running, check for NEW queue status |
||||
|
submitted_compose_found_new: list = [ |
||||
|
compose |
||||
|
for compose in compose_queue["new"] |
||||
|
if (compose["blueprint"] == module.params["blueprint"]) and (compose["version"] == blueprint_version) |
||||
|
] |
||||
|
|
||||
|
if submitted_compose_found_new: |
||||
|
submitted_compose_uuid: str = submitted_compose_found_new[0]["id"] |
||||
|
|
||||
|
else: |
||||
|
# it's not RUNNING and not NEW, so check for FAILURE state |
||||
|
compose_failed: dict = weldr.api.get_compose_failed() |
||||
|
# {"failed":[]} |
||||
|
submitted_compose_found_failed: list = [ |
||||
|
compose |
||||
|
for compose in compose_failed["failed"] |
||||
|
if (compose["blueprint"] == module.params["blueprint"]) and (compose["version"] == blueprint_version) |
||||
|
] |
||||
|
if submitted_compose_found_failed: |
||||
|
submitted_compose_uuid: str = submitted_compose_found_failed[0]["id"] |
||||
|
else: |
||||
|
module.fail_json( |
||||
|
msg="Unable to determine state of build, check osbuild-composer system logs. Also, consider increasing the request timeout", |
||||
|
changed=changed |
||||
|
) |
||||
|
|
||||
|
if submitted_compose_uuid: |
||||
|
result: dict = weldr.api.get_compose_status(submitted_compose_uuid) |
||||
|
result['body'] = { |
||||
|
'build_id': submitted_compose_uuid |
||||
|
} |
||||
|
|
||||
|
if "status_code" in result.keys(): |
||||
|
if result["status_code"] >= 400: |
||||
|
module.fail_json( |
||||
|
msg="Compose returned body: {0}, msg {1}, and status_code {2}".format(result["body"], result["error_msg"], result["status_code"]), |
||||
|
changed=changed |
||||
|
) |
||||
|
|
||||
|
# Having received a non-400+ response, we know a compose has started |
||||
|
changed: bool = True |
||||
|
|
||||
|
compose_output_types: dict[str, list[str]] = { |
||||
|
"tar": ["tar", "edge-commit", "iot-commit", "edge-container", "iot-container", "container"], |
||||
|
"iso": ["edge-installer", "edge-simplified-installer", "iot-installer", "image-installer"], |
||||
|
"qcow2": ["qcow2", "openstack", "oci"], |
||||
|
"vmdk": ["vmdk"], |
||||
|
"vhd": ["vhd"], |
||||
|
"raw.xz": ["edge-raw-image", "iot-raw-image"], |
||||
|
"ami": ["ami"], |
||||
|
} |
||||
|
|
||||
|
output_type: str = "" |
||||
|
for compose_type, compose_type_list in compose_output_types.items(): |
||||
|
if module.params["compose_type"] in compose_type_list: |
||||
|
output_type: str = compose_type |
||||
|
result["output_type"] = output_type |
||||
|
|
||||
|
module.exit_json(msg="Compose submitted to queue", result=result, changed=changed) |
||||
|
|
||||
|
else: |
||||
|
changed: bool = False |
||||
|
module.exit_json( |
||||
|
msg="Not queuing a duplicate versioned compose without allow_duplicate set to true", |
||||
|
changed=changed, |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def main() -> None: |
||||
|
module: AnsibleModule = AnsibleModule( |
||||
|
argument_spec=argument_spec, |
||||
|
required_together=[["image_name", "profile"]], |
||||
|
required_if=[ |
||||
|
["compose_type", "edge-installer", ["ostree_url"]], |
||||
|
["compose_type", "iot-installer", ["ostree_url"]], |
||||
|
], |
||||
|
) |
||||
|
weldr: Weldr = Weldr(module) |
||||
|
start_compose(module, weldr) |
||||
|
|
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
main() |
||||
@ -0,0 +1,148 @@ |
|||||
|
- name: Install prerequisites |
||||
|
hosts: all |
||||
|
become: true |
||||
|
tasks: |
||||
|
- community.general.rhsm_repository: |
||||
|
name: |
||||
|
- rhocp-4.14-for-rhel-9-{{ ansible_facts['userspace_architecture'] }}-rpms |
||||
|
- fast-datapath-for-rhel-9-{{ ansible_facts['userspace_architecture'] }}-rpms |
||||
|
state: enabled |
||||
|
|
||||
|
- name: Install EPEL release package |
||||
|
become: true |
||||
|
ansible.builtin.dnf: |
||||
|
name: https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm |
||||
|
state: present |
||||
|
disable_gpg_check: true |
||||
|
|
||||
|
- name: Install packages |
||||
|
ansible.builtin.dnf: |
||||
|
name: |
||||
|
- python3-toml |
||||
|
- createrepo |
||||
|
- git |
||||
|
- rpm-build |
||||
|
- rpmdevtools |
||||
|
- rpmrebuild |
||||
|
- mkpasswd |
||||
|
- podman |
||||
|
- buildah |
||||
|
- nginx |
||||
|
- lorax |
||||
|
- pykickstart |
||||
|
- osbuild-composer |
||||
|
- composer-cli |
||||
|
- cockpit-composer |
||||
|
- git |
||||
|
- firewalld |
||||
|
state: installed |
||||
|
|
||||
|
- name: Start services |
||||
|
ansible.builtin.systemd: |
||||
|
name: "{{ item }}" |
||||
|
enabled: yes |
||||
|
state: started |
||||
|
loop: |
||||
|
- osbuild-composer.socket |
||||
|
- firewalld.service |
||||
|
- cockpit.socket |
||||
|
- nginx.service |
||||
|
|
||||
|
- name: Adding ansible_user to the weldr group |
||||
|
ansible.builtin.user: |
||||
|
name: '{{ ansible_user | default(ansible_env.SUDO_USER) }}' |
||||
|
groups: weldr |
||||
|
append: yes |
||||
|
|
||||
|
- name: Allow HTTP and HTTPS |
||||
|
ansible.posix.firewalld: |
||||
|
service: '{{ item }}' |
||||
|
permanent: true |
||||
|
immediate: true |
||||
|
state: enabled |
||||
|
loop: |
||||
|
- http |
||||
|
- https |
||||
|
|
||||
|
- name: Ensure the ostree directory exists |
||||
|
become: true |
||||
|
ansible.builtin.file: |
||||
|
path: "{{ www_location }}" |
||||
|
state: directory |
||||
|
mode: '0755' |
||||
|
serole: object_r |
||||
|
setype: httpd_sys_content_t |
||||
|
seuser: system_u |
||||
|
|
||||
|
- name: Configure nginx |
||||
|
lineinfile: |
||||
|
path: /etc/nginx/nginx.conf |
||||
|
line: "root {{ www_location }};" |
||||
|
regexp: "^\\s*root\\s+.*;" |
||||
|
|
||||
|
- name: Restart nginx |
||||
|
ansible.builtin.systemd: |
||||
|
name: nginx.service |
||||
|
state: restarted |
||||
|
|
||||
|
- name: Ensure the repository directory exists |
||||
|
become: true |
||||
|
ansible.builtin.file: |
||||
|
path: "{{ repo_location }}" |
||||
|
state: directory |
||||
|
mode: '0755' |
||||
|
|
||||
|
- name: Update the repository with createrepo |
||||
|
become: true |
||||
|
ansible.builtin.command: |
||||
|
cmd: "createrepo {{ repo_location }}" |
||||
|
|
||||
|
- name: Add custom repository |
||||
|
ansible.builtin.yum_repository: |
||||
|
name: custom |
||||
|
file: custom |
||||
|
description: Custom RPMS |
||||
|
baseurl: file://{{ repo_location }} |
||||
|
enabled: true |
||||
|
gpgcheck: false |
||||
|
|
||||
|
- name: Add sources |
||||
|
infra.osbuild.repository: '{{ item }}' |
||||
|
loop: |
||||
|
- repo_name: custom packages for RHEL |
||||
|
type: yum-baseurl |
||||
|
base_url: file://{{ repo_location }} |
||||
|
check_gpg: false |
||||
|
check_ssl: false |
||||
|
rhsm: false |
||||
|
state: present |
||||
|
- repo_name: Red Hat OpenShift Container Platform 4.14 for RHEL 9 |
||||
|
type: yum-baseurl |
||||
|
base_url: https://cdn.redhat.com/content/dist/layered/rhel9/{{ ansible_facts['userspace_architecture'] }}/rhocp/4.14/os |
||||
|
check_gpg: true |
||||
|
check_ssl: true |
||||
|
rhsm: true |
||||
|
state: present |
||||
|
- repo_name: Fast Datapath for RHEL 9 |
||||
|
type: yum-baseurl |
||||
|
base_url: https://cdn.redhat.com/content/dist/layered/rhel9/{{ ansible_facts['userspace_architecture'] }}/fast-datapath/os |
||||
|
check_gpg: true |
||||
|
check_ssl: true |
||||
|
rhsm: true |
||||
|
state: present |
||||
|
- repo_name: Extra Packages for Enterprise Linux |
||||
|
type: yum-baseurl |
||||
|
base_url: http://mirror.in2p3.fr/pub/epel/9/Everything/{{ ansible_facts['userspace_architecture'] }}/ |
||||
|
check_gpg: false |
||||
|
check_ssl: false |
||||
|
rhsm: false |
||||
|
state: present |
||||
|
loop_control: |
||||
|
label: '{{ item.repo_name }}' |
||||
|
|
||||
|
- name: Install packages on the ansible controller |
||||
|
dnf: |
||||
|
name: |
||||
|
- python3-toml |
||||
|
state: installed |
||||
|
delegate_to: localhost |
||||
@ -0,0 +1,4 @@ |
|||||
|
collections: |
||||
|
- infra.osbuild |
||||
|
- community.general |
||||
|
- ansible.posix |
||||
@ -0,0 +1,68 @@ |
|||||
|
## |
||||
|
## Environment setup |
||||
|
## |
||||
|
|
||||
|
# French I18n |
||||
|
lang fr_FR.UTF-8 |
||||
|
|
||||
|
# French keyboard layout |
||||
|
keyboard fr |
||||
|
|
||||
|
# Timezone is UTC to avoid issue with DST |
||||
|
timezone UTC --utc |
||||
|
|
||||
|
# Configure NTP |
||||
|
timesource --ntp-server=rhel.pool.ntp.org |
||||
|
|
||||
|
# Which action to perform after install: poweroff or reboot |
||||
|
reboot |
||||
|
|
||||
|
# Install mode: text (interactive installs) or cmdline (unattended installs) |
||||
|
text |
||||
|
|
||||
|
## |
||||
|
## Storage configuration for only one disk |
||||
|
## /dev/disk/by-path/pci-0000:00:12.0-ata-1 instead of sda when sda is taken by the usb stick |
||||
|
## |
||||
|
zerombr |
||||
|
clearpart --all --initlabel |
||||
|
reqpart --add-boot |
||||
|
part pv.01 --size=1024 --grow --ondisk={{ kickstart_root_disk }} |
||||
|
volgroup rhel pv.01 |
||||
|
logvol / --fstype="xfs" --size=10240 --name=root --vgname=rhel |
||||
|
|
||||
|
## |
||||
|
## Network configuration |
||||
|
## |
||||
|
|
||||
|
# Configure the first network device |
||||
|
network --bootproto=dhcp --device=enp1s0 --noipv6 --activate |
||||
|
|
||||
|
# Configure hostname |
||||
|
network --hostname=kiosk.localdomain |
||||
|
|
||||
|
## |
||||
|
## Ostree installation |
||||
|
## |
||||
|
|
||||
|
# Use this to fetch from a remote URL |
||||
|
ostreesetup --nogpg --osname=rhel --remote=edge --url=http://{{ ansible_default_ipv4.address }}/repo --ref=rhel/9/x86_64/edge-kiosk |
||||
|
|
||||
|
## |
||||
|
## Post install scripts |
||||
|
## |
||||
|
%post --log=/var/log/anaconda/post-install.log --erroronfail |
||||
|
# Add the pull secret to CRI-O and set root user-only read/write permissions |
||||
|
cat > /etc/crio/openshift-pull-secret << 'EOF' |
||||
|
{{ kickstart_microshift_pull_secret }} |
||||
|
EOF |
||||
|
chmod 600 /etc/crio/openshift-pull-secret |
||||
|
|
||||
|
# Configure the firewall with the mandatory rules for MicroShift |
||||
|
firewall-offline-cmd --zone=trusted --add-source=10.42.0.0/16 |
||||
|
firewall-offline-cmd --zone=trusted --add-source=169.254.169.1 |
||||
|
|
||||
|
# Do not ask password for sudo |
||||
|
sed -i.post-install -e "s/^%wheel\tALL=(ALL)\tALL/%wheel ALL=(ALL) NOPASSWD: ALL/" /etc/sudoers |
||||
|
|
||||
|
%end |
||||
@ -0,0 +1,74 @@ |
|||||
|
name = "kiosk" |
||||
|
description = "Example Kiosk" |
||||
|
version = "0.0.8" |
||||
|
modules = [] |
||||
|
groups = [] |
||||
|
|
||||
|
[[packages]] |
||||
|
name = "kiosk-config" |
||||
|
version = "*" |
||||
|
|
||||
|
[[packages]] |
||||
|
name = "cockpit" |
||||
|
|
||||
|
[[packages]] |
||||
|
name = "microshift-manifests" |
||||
|
version = "*" |
||||
|
|
||||
|
[[packages]] |
||||
|
name = "cockpit-system" |
||||
|
|
||||
|
[customizations] |
||||
|
hostname = "kiosk.local" |
||||
|
|
||||
|
[customizations.services] |
||||
|
enabled = ["cockpit.socket", "sshd", "microshift", "rpm-ostreed", "rpm-ostreed-automatic.timer"] |
||||
|
|
||||
|
[customizations.timezone] |
||||
|
timezone = "Europe/Paris" |
||||
|
ntpservers = ["0.fr.pool.ntp.org", "1.fr.pool.ntp.org"] |
||||
|
|
||||
|
[customizations.locale] |
||||
|
languages = ["fr_FR.UTF-8"] |
||||
|
keyboard = "fr" |
||||
|
|
||||
|
#22 ssh / 9090 cockpit / 6443 microshift |
||||
|
[customizations.firewall] |
||||
|
ports = ["22:tcp", "30000:tcp", "9090:tcp", "6443:tcp"] |
||||
|
|
||||
|
## |
||||
|
## Automatic updates |
||||
|
## |
||||
|
## This file is used by the rpm-ostreed service that is triggered by the |
||||
|
## rpm-ostreed-automatic systemd timer: |
||||
|
## |
||||
|
## [Timer] |
||||
|
## OnBootSec=1h # 1 hour after boot |
||||
|
## OnUnitInactiveSec=1d # 1 day after last check |
||||
|
## |
||||
|
## But you can trigger a check manually with: |
||||
|
## |
||||
|
## sudo rpm-ostree upgrade --trigger-automatic-update-policy |
||||
|
## |
||||
|
[[customizations.files]] |
||||
|
path = "/etc/rpm-ostreed.conf" |
||||
|
data = """[Daemon] |
||||
|
AutomaticUpdatePolicy=apply |
||||
|
""" |
||||
|
|
||||
|
[[customizations.user]] |
||||
|
name = "admin" |
||||
|
description = "admin" |
||||
|
password = '{{ blueprint_admin_password_hash }}' |
||||
|
key = "{{ blueprint_admin_ssh_public_key }}" |
||||
|
home = "/home/admin/" |
||||
|
shell = "/usr/bin/bash" |
||||
|
groups = ["users", "wheel"] |
||||
|
|
||||
|
[[customizations.user]] |
||||
|
name = "kiosk" |
||||
|
description = "kiosk" |
||||
|
password = '{{ blueprint_kiosk_password_hash }}' |
||||
|
home = "/home/kiosk/" |
||||
|
shell = "/bin/bash" |
||||
|
|
||||
Loading…
Reference in new issue