Compare commits

...
23 Commits
Author SHA1 Message Date
Mattias Walström cc0edaf59c confd: Add support to upload images directly
This add support for upgrading images with curl only, without FTP or
similiar. The image needs to be base64 encoded.

Example:

TEMP_JSON=/tmp/restconf.txt
echo -n '{"infix-system:input":{"image":"' > "$TEMP_JSON"
base64 -w 0 "$BUNDLE_FILE" >> "$TEMP_JSON"
echo '"}}' >> "$TEMP_JSON"

curl -k \
  -X POST \
  -u "admin:admin" \
  -H "Content-Type: application/yang-data+json" \
  -H "Accept: application/yang-data+json" \
  -d @"$TEMP_JSON" \
  "https://192.168.30.1/restconf/operations/infix-system:install-bundle"
2025-07-11 16:33:02 +02:00
Mattias Walström 60b3a97f0e confd: Coding style 2025-07-11 16:33:01 +02:00
Mattias Walström e534c0bd2d nginx: Add support for large files in restconf app. 2025-07-11 16:26:33 +02:00
Joachim WibergandGitHub 692fa166d1 Merge pull request #1049 from kernelkit/document-kernel-buildroot-update
Document process about upgrade buildroot and kernel

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-07-11 13:40:58 +02:00
Joachim WibergandGitHub c5d9a7c8b3 Merge pull request #1078 from kernelkit/ci-workflow-redesign
Redesign CI to use workflow calls

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-07-09 11:52:54 +02:00
Richard Alpe 45c588567d .github: fix parallel typo in dispatch
Fixes: 874c0a54 Use massive parallel in workflow for PRs

Signed-off-by: Richard Alpe <richard@bit42.se>
2025-07-09 10:42:40 +02:00
Richard Alpe 2a8580edcc .github: allow workflow caller to pass test dir
Signed-off-by: Richard Alpe <richard@bit42.se>
2025-07-07 09:53:06 +02:00
Richard Alpe ffd0c05f41 .github: allow workflow caller to pass 9pm config 2025-07-07 09:53:05 +02:00
Richard Alpe 9271c2439f .github: add name when calling test from self trigger 2025-07-07 09:53:04 +02:00
Richard Alpe 7295077f27 .github: update test.yml to support workflow call 2025-07-07 09:53:03 +02:00
Richard Alpe b3d76de8f3 .github: quote workflow call variables in trigger 2025-07-07 09:53:02 +02:00
Richard Alpe 56f14ff520 .github: build _minimal on self-trigger
Signed-off-by: Richard Alpe <richard@bit42.se>
2025-07-07 09:53:01 +02:00
Richard Alpe 9845e3b0c7 .github: make flavor selection into a env option 2025-07-07 09:53:00 +02:00
Richard Alpe 08e6833266 .github: allow repo checkout override
Needed when called by spins inside different organizations.
2025-07-07 09:52:59 +02:00
Richard Alpe df54982978 .github: simplify flavor selection 2025-07-07 09:52:58 +02:00
Richard Alpe efd6cce842 .github: pass (Infix) name as workflow_call input
Signed-off-by: Richard Alpe <richard@bit42.se>
2025-07-07 09:52:57 +02:00
Richard Alpe 1dc84af095 .github: use workflow calls to build and test
Redesign how GH actions are triggered. This new design uses a
self-trigger to start jobs which are only started via workflow_calls.

The main benefit of this is to be able to start workflows from various
Infix Spins without needing to duplicate the workflow files in them.

Upcoming patches are intended to parameterize to allow Spins to pass
different architectures, brand names and such.

Signed-off-by: Richard Alpe <richard@bit42.se>
2025-07-07 09:52:55 +02:00
Tobias WaldekranzandGitHub d6c0d4bea7 Merge pull request #1042 from kernelkit/ixbin
ixbin: Add util script to update image contents
2025-07-04 11:23:33 +02:00
Tobias Waldekranz 85c4b579d3 ixbin: Add util script to update image contents
[skip-ci]
2025-07-04 11:20:46 +02:00
Joachim WibergandGitHub 1f268a7b90 Merge pull request #1079 from kernelkit/gen-test-spec
Minor Test Update
2025-07-03 14:51:04 +02:00
Ahmed Karic 562fd253c4 test: Update description in speed/duplex test 2025-07-03 14:01:27 +02:00
Ahmed Karic be547ba9a8 test: Generate forgotten test-spec
- container_host_commands test is missing .adoc and .svg files
2025-07-03 13:59:55 +02:00
Mattias Walström eabaaac7c2 Document process about upgrade buildroot and kernel 2025-06-19 15:47:54 +02:00
16 changed files with 951 additions and 220 deletions
+55 -151
View File
@@ -1,30 +1,51 @@
name: Bob the Builder
name: Build
on:
pull_request:
types: [opened, synchronize, reopened, labeled]
push:
branches:
- main
workflow_dispatch:
inputs:
minimal:
description: 'Build minimal defconfigs'
flavor:
description: 'Optional build flavor (e.g. _minimal)'
required: false
default: true
type: boolean
parallell:
default: ''
type: string
parallel:
description: 'Massive parallel build of each image'
required: false
default: true
type: boolean
infix_repo:
description: 'Repo to checkout (for spin overrides)'
required: false
default: kernelkit/infix
type: string
workflow_call:
inputs:
target:
required: true
type: string
name:
required: true
type: string
flavor:
required: false
type: string
default: ''
infix_repo:
required: false
type: string
default: kernelkit/infix
env:
FLV: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.flavor || inputs.flavor }}
INFIX_REPO: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.infix_repo || inputs.infix_repo }}
jobs:
build:
name: Build Infix ${{ matrix.target }}
name: Build ${{ inputs.name }} ${{ inputs.target }}
runs-on: [ self-hosted, latest ]
strategy:
matrix:
target: [aarch64, x86_64]
fail-fast: false
outputs:
build_id: ${{ steps.vars.outputs.INFIX_BUILD_ID }}
@@ -36,8 +57,11 @@ jobs:
rm -rf ./.??* || true
ls -la ./
- uses: actions/checkout@v4
- name: Checkout infix repo
uses: actions/checkout@v4
with:
repository: ${{ env.INFIX_REPO }}
ref: ${{ github.ref }}
clean: true
fetch-depth: 0
submodules: recursive
@@ -55,26 +79,13 @@ jobs:
"${{ github.event.number }}" "${{ github.event.pull_request.head.sha }}" \
| tee -a $GITHUB_OUTPUT $GITHUB_ENV
fi
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
if [ "${{ github.event.inputs.minimal }}" == "true" ]; then
flavor="_minimal"
fi
else
# Ensure 'release' job get the proper image when building main
if [ "$GITHUB_REF_NAME" != "main" ]; then
flavor="_minimal"
else
flavor=""
fi
if ${{ contains(github.event.pull_request.labels.*.name, 'ci:main') }}; then
flavor=""
fi
fi
target=${{ matrix.target }}
echo "dir=infix-$target" >> $GITHUB_OUTPUT
echo "tgz=infix-$target.tar.gz" >> $GITHUB_OUTPUT
echo "flv=$flavor" >> $GITHUB_OUTPUT
echo "Building target ${target}${flavor}_defconfig"
target=${{ inputs.target }}
name=${{ inputs.name }}
echo "dir=${name}-${target}" >> $GITHUB_OUTPUT
echo "tgz=${name}-${target}.tar.gz" >> $GITHUB_OUTPUT
echo "flv=$FLV" >> $GITHUB_OUTPUT
echo "Building target ${target}${FLV}_defconfig"
- name: Restore Cache of dl/
uses: actions/cache@v4
@@ -88,16 +99,16 @@ jobs:
uses: actions/cache@v4
with:
path: .ccache/
key: ccache-${{ matrix.target }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
key: ccache-${{ inputs.target }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
restore-keys: |
ccache-${{ matrix.target }}-
ccache-${{ inputs.target }}-
ccache-
- name: Configure ${{ matrix.target }}${{ steps.vars.outputs.flv }}
- name: Configure ${{ inputs.target }}${{ steps.vars.outputs.flv }}
run: |
make ${{ matrix.target }}${{ steps.vars.outputs.flv }}_defconfig
make ${{ inputs.target }}${{ steps.vars.outputs.flv }}_defconfig
- name: Unit Test ${{ matrix.target }}
- name: Unit Test ${{ inputs.target }}
run: |
make test-unit
@@ -115,9 +126,9 @@ jobs:
fi
echo "MAKE=$MAKE" >> $GITHUB_OUTPUT
- name: Build ${{ matrix.target }}${{ steps.vars.outputs.flv }}
- name: Build ${{ inputs.target }}${{ steps.vars.outputs.flv }}
run: |
echo "Building ${{ matrix.target }}${{ steps.vars.outputs.flv }}_defconfig ..."
echo "Building ${{ inputs.target }}${{ steps.vars.outputs.flv }}_defconfig ..."
eval "${{ steps.parallel.outputs.MAKE }}"
- name: Check SBOM from Build
@@ -139,7 +150,7 @@ jobs:
printf "Size of output/images/: "
ls -l output/images/
- name: Prepare ${{ matrix.target }} Artifact
- name: Prepare ${{ inputs.target }} Artifact
run: |
cd output/
mv images ${{ steps.vars.outputs.dir }}
@@ -149,111 +160,4 @@ jobs:
- uses: actions/upload-artifact@v4
with:
path: output/${{ steps.vars.outputs.tgz }}
name: artifact-${{ matrix.target }}
test:
name: Regression Test of Infix x86_64
needs: build
runs-on: [ self-hosted, regression ]
steps:
- uses: actions/checkout@v4
with:
clean: true
submodules: recursive
- name: Set Build Variables
id: vars
run: |
if [ -n "${{ needs.build.outputs.build_id }}" ]; then
echo "INFIX_BUILD_ID=${{ needs.build.outputs.build_id }}" \
>>$GITHUB_ENV
fi
if [ "$GITHUB_REF_NAME" != "main" ]; then
flavor="_minimal"
else
flavor=""
fi
echo "flv=$flavor" >> $GITHUB_OUTPUT
- name: Configure x86_64${{ steps.vars.outputs.flv }}
run: |
make x86_64${{ steps.vars.outputs.flv }}_defconfig
- uses: actions/download-artifact@v4
with:
pattern: "artifact-*"
merge-multiple: true
- name: Restore x86-64${{ steps.vars.outputs.flv }} output/
run: |
ls -l
mkdir -p output
mv infix-x86_64.tar.gz output/
cd output/
tar xf infix-x86_64.tar.gz
ln -s infix-x86_64 images
- name: Regression Test x86_64${{ steps.vars.outputs.flv }}
run: |
make test
- name: Publish Test Result for x86_64${{ steps.vars.outputs.flv }}
# Ensure this runs even if Regression Test fails
if: always()
run: cat test/.log/last/result-gh.md >> $GITHUB_STEP_SUMMARY
- name: Generate Test Report for x86_64${{ steps.vars.outputs.flv }}
# Ensure this runs even if Regression Test fails
if: always()
run: |
asciidoctor-pdf \
--theme test/9pm/report/theme.yml \
-a pdf-fontsdir=test/9pm/report/fonts \
test/.log/last/report.adoc \
-o test/.log/last/report.pdf
- name: Upload Test Report as Artifact
uses: actions/upload-artifact@v4
with:
name: test-report
path: test/.log/last/report.pdf
release:
if: ${{github.repository_owner == 'kernelkit' && github.ref_name == 'main'}}
name: Upload Latest Build
needs: test
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: "artifact-*"
merge-multiple: true
- name: Create checksums ...
run: |
for file in *.tar.gz; do
sha256sum $file > $file.sha256
done
- uses: ncipollo/release-action@v1
with:
allowUpdates: true
omitName: true
omitBody: true
omitBodyDuringUpdate: true
prerelease: true
tag: "latest"
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "*.tar.gz*"
- name: Summary
run: |
cat <<EOF >> $GITHUB_STEP_SUMMARY
# Latest Build Complete! :rocket:
For the public download links of these build artifacts, please see:
<https://github.com/kernelkit/infix/releases/tag/latest>
EOF
name: artifact-${{ inputs.target }}
+43
View File
@@ -0,0 +1,43 @@
name: Publish latest Infix
on:
workflow_dispatch:
workflow_call:
jobs:
publish:
name: Upload Latest Build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: "artifact-*"
merge-multiple: true
- name: Create checksums ...
run: |
for file in *.tar.gz; do
sha256sum $file > $file.sha256
done
- uses: ncipollo/release-action@v1
with:
allowUpdates: true
omitName: true
omitBody: true
omitBodyDuringUpdate: true
prerelease: true
tag: "latest"
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "*.tar.gz*"
- name: Summary
run: |
cat <<EOF >> $GITHUB_STEP_SUMMARY
# Latest Build Complete! :rocket:
For the public download links of these build artifacts, please see:
<https://github.com/kernelkit/infix/releases/tag/latest>
EOF
+116
View File
@@ -0,0 +1,116 @@
name: Test Infix
on:
workflow_dispatch:
inputs:
infix_repo:
description: 'Repo to checkout (for spin overrides)'
required: false
default: kernelkit/infix
type: string
workflow_call:
inputs:
target:
required: true
type: string
name:
required: true
type: string
flavor:
required: false
type: string
default: ''
infix_repo:
required: false
type: string
default: kernelkit/infix
ninepm-conf:
required: false
type: string
default: ''
test-path:
required: false
type: string
default: 'test'
env:
FLV: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.flavor || inputs.flavor }}
INFIX_REPO: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.infix_repo || inputs.infix_repo }}
NINEPM_CONF: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ninepm-conf || inputs.ninepm-conf }}
TEST_PATH: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.test-path || inputs.test-path }}
jobs:
test:
name: Regression Test ${{ inputs.name }} ${{ inputs.target }}
runs-on: [ self-hosted, regression ]
steps:
- name: Checkout infix repo
uses: actions/checkout@v4
with:
repository: ${{ env.INFIX_REPO }}
ref: ${{ github.ref }}
clean: true
fetch-depth: 0
submodules: recursive
- name: Set Build Variables
id: vars
run: |
if [ -n "${{ needs.build.outputs.build_id }}" ]; then
echo "INFIX_BUILD_ID=${{ needs.build.outputs.build_id }}" \
>>$GITHUB_ENV
fi
echo "flv=$FLV" >> $GITHUB_OUTPUT
- name: Configure ${{ inputs.target }}${{ steps.vars.outputs.flv }}
run: |
make ${{ inputs.target }}${{ steps.vars.outputs.flv }}_defconfig
- uses: actions/download-artifact@v4
with:
pattern: "artifact-*"
merge-multiple: true
- name: Restore x86-64${{ steps.vars.outputs.flv }} output/
run: |
target=${{ inputs.target }}
name=${{ inputs.name }}
ls -l
mkdir -p output
mv ${name}-${target}.tar.gz output/
cd output/
tar xf ${name}-${target}.tar.gz
ln -s ${name}-${target} images
- name: Regression Test x86_64${{ steps.vars.outputs.flv }}
run: |
if [ -n "$NINEPM_CONF" ]; then
export NINEPM_PROJ_CONFIG="${GITHUB_WORKSPACE}/$NINEPM_CONF"
echo "DEBUG: NINEPM_PROJ_CONFIG is '$NINEPM_PROJ_CONFIG'"
fi
make test
- name: Publish Test Result for x86_64${{ steps.vars.outputs.flv }}
# Ensure this runs even if Regression Test fails
if: always()
run: cat $TEST_PATH/.log/last/result-gh.md >> $GITHUB_STEP_SUMMARY
- name: Generate Test Report for x86_64${{ steps.vars.outputs.flv }}
# Ensure this runs even if Regression Test fails
if: always()
run: |
asciidoctor-pdf \
--theme $TEST_PATH/9pm/report/theme.yml \
-a pdf-fontsdir=$TEST_PATH/9pm/report/fonts \
$TEST_PATH/.log/last/report.adoc \
-o $TEST_PATH/.log/last/report.pdf
- name: Upload Test Report as Artifact
uses: actions/upload-artifact@v4
with:
name: test-report
path: ${{ env.TEST_PATH }}/.log/last/report.pdf
+37
View File
@@ -0,0 +1,37 @@
name: Self Trigger
on:
pull_request:
types: [opened, synchronize, reopened, labeled]
push:
branches:
- main
- ci-workflow-redesign
workflow_dispatch:
jobs:
build-x86_64:
uses: ./.github/workflows/build.yml
with:
target: "x86_64"
name: "infix"
flavor: "_minimal"
build-aarch64:
uses: ./.github/workflows/build.yml
with:
target: "aarch64"
name: "infix"
flavor: "_minimal"
test-run-x86_64:
needs: build-x86_64
uses: ./.github/workflows/test.yml
with:
target: "x86_64"
name: "infix"
test-publish-x86_64:
needs: test-run-x86_64
uses: ./.github/workflows/publish.yml
@@ -1,5 +1,11 @@
# /telemetry/optics is for streaming (not used atm)
# Proxy buffer settings for large files
proxy_buffering off; # Disable buffering for streaming
proxy_request_buffering off; # Stream request body immediately
proxy_max_temp_file_size 0; # No temp files
location ~ ^/(restconf|yang|.well-known)/ {
client_max_body_size 200M;
client_body_buffer_size 1M;
grpc_pass grpc://[::1]:10080;
grpc_set_header Host $host;
grpc_set_header X-Real-IP $remote_addr;
+177 -16
View File
@@ -168,36 +168,36 @@ Now you can rebuild `confd`, just as described above, and restart Infix:
### `statd`
The Infix status daemon, `src/statd`, is responsible for populating the
sysrepo `operational` datastore. Like `confd`, it uses XPath subscriptions,
but unlike `confd`, it relies entirely on `yanger`, a Python script that
The Infix status daemon, `src/statd`, is responsible for populating the
sysrepo `operational` datastore. Like `confd`, it uses XPath subscriptions,
but unlike `confd`, it relies entirely on `yanger`, a Python script that
gathers data from local linux services and feeds it into sysrepo.
To apply changes, rebuild the image:
make python-statd-rebuild statd-rebuild all
Rebuilding the image and testing on target for every change during
development process can be tedious. Instead, `yanger` allows remote
execution, running the script directly on the host system (test
Rebuilding the image and testing on target for every change during
development process can be tedious. Instead, `yanger` allows remote
execution, running the script directly on the host system (test
container):
infamy0:test # ../src/statd/python/yanger/yanger -x "../utils/ixll -A ssh d3a" ieee802-dot1ab-lldp
`ixll` is a utility script that lets you run network commands using an
**interface name** instead of a hostname. It makes operations like
**interface name** instead of a hostname. It makes operations like
`ssh`, `scp`, and network discovery easier.
Normally, `yanger` runs commands **locally** to retrieve data
(e.g., `lldpcli` when handling `ieee802-dot1ab-lldp`). However, when
executed with `-x "../utils/ixll -A ssh d3a"` it redirects these
commands to a remote system connected to the local `d3a` interface via
SSH. This setup is used for running `yanger` in an
Normally, `yanger` runs commands **locally** to retrieve data
(e.g., `lldpcli` when handling `ieee802-dot1ab-lldp`). However, when
executed with `-x "../utils/ixll -A ssh d3a"` it redirects these
commands to a remote system connected to the local `d3a` interface via
SSH. This setup is used for running `yanger` in an
[interactive test environment](testing.md#interactive-usage). The yanger
script runs on the `host` system, but key commands are executed on the
script runs on the `host` system, but key commands are executed on the
`target` system.
For debugging or testing, you can capture system command output and
For debugging or testing, you can capture system command output and
replay it later without needing a live system.
To capture:
@@ -211,12 +211,173 @@ To replay:
This is especially useful when working in isolated environments or debugging
issues without direct access to the DUT.
### Upgrading Packages
#### Buildroot
Kernelkit maintains an internal [fork of
Buildroot](https://github.com/kernelkit/buildroot), with branches
following the naming scheme `YYYY.MM.patch-kkit`
e.g. `2025.02.1-kkit`, which means a new branch should be created
whenever Buildroot is updated. These branches should contain **only**
changes to existing packages (but no new patches), modifications to
Buildroot itself or upstream backports.
KernelKit track the latest Buildroot LTS (Long-Term Support) release
and updates. The upgrade of LTS minor releases is expected to have low
impact and should be done as soon there is a patch release of
Buildroot LTS is available.
> **Depending on your setup, follow the appropriate steps below.**
🔁 If you **already have** the Buildroot repo locally
1. Navigate to the Buildroot directory
```bash
$ cd buildroot
```
2. Pull the latest changes from KernelKit
```bash
$ git pull
```
3. Fetch the latest tags from upstream
```bash
$ git fetch upstream --tags
```
🆕 If you don't have the repo locally
1. Clone the Kernelkit Buildroot repository
```bash
$ git clone git@github.com:kernelkit/buildroot.git
```
2. Add the upstream remote
```bash
$ git remote add upstream https://gitlab.com/buildroot.org/buildroot.git
```
3. Checkout old KernelKit branch
```bash
$ git checkout 2025.02.1-kkit
```
🛠 Continue from here (applies to both cases):
4. Create a new branch based on the **previous** KernelKit Buildroot
release (e.g. `2025.02.1-kkit`) and name it according to the naming scheme (e.g. `2025.02.2-kkit`)
```bash
$ git checkout -b 2025.02.2-kkit
```
5. Rebase the new branch onto the corresponding upstream release
```bash
$ git rebase 2025.02.2
```
> [!NOTE] It is **not** allowed to rebase the branch when bumped in Infix.
6. Push the new branch and tags
```bash
$ git push origin 2025.02.2-kkit --tags
```
7. In Infix, checkout new branch of Buildroot
```bash
$ cd buildroot
$ git fetch
$ git checkout 2025.02.2-kkit
```
8. Push changes
Commit and push the changes. Dont forget to update the changelog.
9. Create a pull request.
> [!NOTE] Remember to set the pull request label to `ci:main` to ensure full CI coverage.
#### Linux kernel
KernelKit maintains an internal [fork of Linux
kernel](https://github.com/kernelkit/linux), with branches following
the naming scheme `kkit-linux-[version].y`, e.g. `kkit-6.12.y`, which
means a new branch should be created whenever the major kernel version
is updated. This branch should contain *all* kernel patches used by
Infix.
KernelKit track the latest Linux kernel LTS (Long-Term Support)
release and updates. The upgrade of LTS minor releases is expected to
have low impact and should be done as soon as a patch release of the
LTS Linux kernel is available.
🔁 If you **already have** the Linux kernel repo locally
1. Navigate to the Linux kernel directory
```bash
$ cd linux
```
2. Get latest changes from KernelKit
```bash
$ git pull
```
3. Fetch the latest tags from upstream
```bash
$ git fetch upstream --tags
```
🆕 If you don't have the repo locally
1. Clone the KernelKit Linux kernel repository
```bash
$ git clone git@github.com:kernelkit/linux.git
```
2. Add the upstream remote
```bash
$ git remote add upstream git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
```
3. Checkout correct kernel branch
```bash
$ git checkout kkit-linux-6.12.y
```
🛠 Continue from here (applies to both cases)
4. Rebase on the upstream release
```bash
$ git rebase v6.12.29
```
6. Push changes and the tags
```bash
$ git push -f origin kkit-linux-6.12.y --tags
```
**Move to your infix directory**
7. Generate patches
```bash
$ make x86_64_defconfig
$ cd output
$ ../utils/kernel-refresh.sh -k /path/to/linux -o 6.12.28 -t v6.12.29
```
> [!NOTE] See help of `kernel-refresh.sh` script for more information
8. Push changes
Commit and push the changes. Dont forget to update the s:changelog:doc/ChangeLog.md.
9. Create a pull request.
> [!NOTE] Remember to set the pull request label to `ci:main` to ensure full CI coverage.
### Agree on YANG Model
When making changes to the `confd` and `statd` services, you will often need to update
the YANG models. If you are adding a new YANG module, it's best to follow the
the YANG models. If you are adding a new YANG module, it's best to follow the
structure of an existing one. However, before making any changes, **always discuss
them with the Infix core team**. This helps avoid issues later in development and
them with the Infix core team**. This helps avoid issues later in development and
makes pull request reviews smoother.
+1
View File
@@ -78,6 +78,7 @@ PKG_CHECK_MODULES([jansson], [jansson >= 2.0.0])
PKG_CHECK_MODULES([libite], [libite >= 2.6.1])
PKG_CHECK_MODULES([sysrepo], [sysrepo >= 2.2.36])
PKG_CHECK_MODULES([libsrx], [libsrx >= 1.0.0])
PKG_CHECK_MODULES([libssl], [libssl >= 1.0.0])
# Control build with automake flags
AM_CONDITIONAL(CONTAINERS, [test "x$enable_containers" != "xno"])
+3 -1
View File
@@ -12,6 +12,7 @@ confd_plugin_la_CFLAGS = \
$(libite_CFLAGS) \
$(sysrepo_CFLAGS) \
$(libsrx_CFLAGS) \
$(libssl_CFLAGS) \
$(CFLAGS)
confd_plugin_la_LIBADD = \
@@ -20,7 +21,8 @@ confd_plugin_la_LIBADD = \
$(jansson_LIBS) \
$(libite_LIBS) \
$(sysrepo_LIBS) \
$(libsrx_LIBS)
$(libsrx_LIBS) \
$(libssl_LIBS)
confd_plugin_la_SOURCES = \
base64.c base64.h \
+120 -25
View File
@@ -1,6 +1,9 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <srx/common.h>
#include <srx/lyx.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <sys/statvfs.h>
#include "core.h"
#include "rauc-installer.h"
@@ -21,24 +24,111 @@ static RaucInstaller *infix_system_sw_new_rauc(void)
return rauc;
}
static int base64_decode_inplace(char *input, size_t input_len, size_t *output_len)
{
BIO *bio, *b64;
int decode_len;
bio = BIO_new_mem_buf(input, input_len);
if (!bio) {
return -1;
}
b64 = BIO_new(BIO_f_base64());
if (!b64) {
BIO_free(bio);
return -1;
}
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bio = BIO_push(b64, bio);
/* Decode directly into the same buffer */
decode_len = BIO_read(bio, input, input_len);
BIO_free_all(bio);
if (decode_len < 0) {
return -1;
}
*output_len = decode_len;
return 0;
}
static int infix_system_sw_install(sr_session_ctx_t *session, uint32_t sub_id,
const char *path, const sr_val_t *input,
const size_t input_cnt, sr_event_t event,
unsigned request_id, sr_val_t **output,
size_t *output_cnt, void *priv)
{
char *url = input->data.string_val;
sr_error_t srerr = SR_ERR_OK;
GError *raucerr = NULL;
RaucInstaller *rauc;
RaucInstaller *rauc = NULL;
char *install_source = NULL;
DEBUG("url:%s", url);
const char *url = NULL;
char *binary_data = NULL;
size_t binary_len = 0;
size_t decoded_len = 0;
for (size_t i = 0; i < input_cnt; i++) {
if (strcmp(input[i].xpath, "/infix-system:install-bundle/url") == 0) {
if (input[i].type == SR_STRING_T) {
url = input[i].data.string_val;
}
} else if (strcmp(input[i].xpath, "/infix-system:install-bundle/image") == 0) {
if (input[i].type == SR_BINARY_T) {
binary_data = (char *)input[i].data.binary_val; // Cast away const for in-place decode
binary_len = strlen(binary_data); // Length of base64 string
}
}
}
if (url) {
DEBUG("Installing from URL: %s", url);
install_source = (char *)url;
} else if (binary_data) {
const char *temp_dir = "/tmp";
char path[256];
FILE *fp;
DEBUG("Installing from uploaded binary data (%zu bytes base64)", binary_len);
if (base64_decode_inplace(binary_data, binary_len, &decoded_len) != 0) {
sr_session_set_netconf_error(session, "application", "invalid-value",
NULL, NULL, "Failed to decode base64 image data", 0);
srerr = SR_ERR_INVAL_ARG;
goto cleanup;
}
fmkpath(0775, "%s/images", temp_dir);
snprintf(path, sizeof(path), "%s/images/install_bundle", temp_dir);
fp = fopen(path, "wb");
if (!fp) {
ERROR("Could not open %s", path);
return SR_ERR_NO_MEMORY;
}
fwrite(binary_data, sizeof(char), decoded_len, fp);
fclose(fp);
install_source = path;
} else {
ERROR("Unknown source");
return 0;
}
rauc = infix_system_sw_new_rauc();
if (!rauc)
return SR_ERR_INTERNAL;
if (!rauc) {
sr_session_set_netconf_error(session, "application", "operation-failed",
NULL, NULL, "Failed to initialize RAUC installer", 0);
srerr = SR_ERR_INTERNAL;
goto cleanup;
}
rauc_installer_call_install_sync(rauc, url, NULL, &raucerr);
rauc_installer_call_install_sync(rauc, install_source, NULL, &raucerr);
if (raucerr) {
sr_session_set_netconf_error(session, "application", "operation-failed",
NULL, NULL, raucerr->message, 0);
@@ -46,13 +136,18 @@ static int infix_system_sw_install(sr_session_ctx_t *session, uint32_t sub_id,
srerr = SR_ERR_OPERATION_FAILED;
}
g_object_unref(rauc);
cleanup:
if (rauc) {
g_object_unref(rauc);
}
return srerr;
}
/*
boot order can only be: primary, secondary and net, limited by model
*/
*/
static int infix_system_sw_set_boot_order(sr_session_ctx_t *session, uint32_t sub_id,
const char *path, const sr_val_t *input,
const size_t input_cnt, sr_event_t event,
@@ -63,24 +158,24 @@ static int infix_system_sw_set_boot_order(sr_session_ctx_t *session, uint32_t su
const sr_val_t *val = &input[i];
if (i != 0)
strlcat(boot_order, " ", sizeof(boot_order));
strlcat(boot_order, val->data.string_val, sizeof(boot_order));
}
strlcat(boot_order, " ", sizeof(boot_order));
strlcat(boot_order, val->data.string_val, sizeof(boot_order));
}
if (fexist("/sys/firmware/devicetree/base/chosen/u-boot,version")) {
if (systemf("fw_setenv BOOT_ORDER %s", boot_order)) {
ERROR("Set-boot-order: Failed to set boot order in U-Boot");
return SR_ERR_INTERNAL;
}
} else if (fexist("/mnt/aux/grub/grubenv")) {
if (systemf("grub-editenv /mnt/aux/grub/grubenv set ORDER=\"%s\"", boot_order)) {
ERROR("Set-boot-order: Failed to set boot order in Grub");
return SR_ERR_INTERNAL;
}
} else {
ERROR("No supported boot loader found");
return SR_ERR_UNSUPPORTED;
}
if (fexist("/sys/firmware/devicetree/base/chosen/u-boot,version")) {
if (systemf("fw_setenv BOOT_ORDER %s", boot_order)) {
ERROR("Set-boot-order: Failed to set boot order in U-Boot");
return SR_ERR_INTERNAL;
}
} else if (fexist("/mnt/aux/grub/grubenv")) {
if (systemf("grub-editenv /mnt/aux/grub/grubenv set ORDER=\"%s\"", boot_order)) {
ERROR("Set-boot-order: Failed to set boot order in Grub");
return SR_ERR_INTERNAL;
}
} else {
ERROR("No supported boot loader found");
return SR_ERR_UNSUPPORTED;
}
return SR_ERR_OK;
}
@@ -204,16 +204,42 @@ submodule infix-system-software {
description
"Upgrade the system's software by installing the specified bundle.";
input {
leaf url {
type string;
choice source {
mandatory true;
description
"The location of the software bundle, specified as a Uniform
Resource Locator (URL). Currently supported protocols include
FTP, HTTP(S) and SCP.";
"Source of the software bundle to install.";
case url {
leaf url {
type string;
description
"The location of the software bundle, specified as a Uniform
Resource Locator (URL). Currently supported protocols include
FTP, HTTP(S) and SCP.";
}
}
case upload {
leaf image {
type binary;
description
"The image data to install, provided as base64-encoded binary data.";
}
}
}
}
}
rpc install-upload-bundle {
nacm:default-deny-all;
description "Upgrade the system's software by installing from a supplied blob.";
input {
leaf image {
type binary;
mandatory true;
description "The image to install";
}
}
}
rpc set-boot-order {
nacm:default-deny-all;
description
@@ -1,6 +1,10 @@
=== Interface Speed Duplex (Copper)
==== Description
Verify that auto-negotiation results in expected speed/duplex mode.
Verify that the interface operates at the expected speed/duplex in two scenarios:
1. Fixed configuration host and target are both manually set to a specific speed/duplex
2. Auto-negotiation host advertises selectable modes and the target negotiates
to the highest common speed/duplex.
==== Topology
ifdef::topdoc[]
@@ -17,17 +21,17 @@ endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUT
. Enable target interface
. Set fixed 10/full
. Set fixed 10/half
. Set fixed 100/full
. Set fixed 100/half
. Verify fixed 10/full
. Verify fixed 10/half
. Verify fixed 100/full
. Verify fixed 100/half
. Switch to auto-negotiation mode for target and host
. Configure host to advertise 10/Full only
. Configure host to advertise 10/Half only
. Configure host to advertise 100/Full only
. Configure host to advertise 100/Half only
. Configure host to advertise 10/half + 10/full + 100/half
. Configure host to advertise 10/half + 10/full + 100/half + 100/full + 1000/full
. Verify auto-negotiation to 10/Full only
. Verify auto-negotiation to 10/Half only
. Verify auto-negotiation to 100/Full only
. Verify auto-negotiation to 100/Half only
. Verify auto-negotiation to 10/half + 10/full + 100/half
. Verify auto-negotiation to 10/half + 10/full + 100/half + 100/full + 1000/full
<<<
@@ -2,7 +2,11 @@
"""
Interface Speed Duplex (Copper)
Verify that auto-negotiation results in expected speed/duplex mode.
Verify that the interface operates at the expected speed/duplex in two scenarios:
1. Fixed configuration host and target are both manually set to a specific speed/duplex
2. Auto-negotiation host advertises selectable modes and the target negotiates
to the highest common speed/duplex.
"""
import infamy
@@ -164,22 +168,22 @@ with infamy.Test() as test:
ns.addip("10.0.0.1")
# Fixed mode tests
with test.step("Set fixed 10/full"):
with test.step("Verify fixed 10/full"):
set_host_speed_duplex(hdata, 10, "full")
set_target_speed_duplex(target, tdata, 10, "full")
verify_speed_duplex(target, ns, tdata, 10, "full")
with test.step("Set fixed 10/half"):
with test.step("Verify fixed 10/half"):
set_host_speed_duplex(hdata, 10, "half")
set_target_speed_duplex(target, tdata, 10, "half")
verify_speed_duplex(target, ns, tdata, 10, "half")
with test.step("Set fixed 100/full"):
with test.step("Verify fixed 100/full"):
set_host_speed_duplex(hdata, 100, "full")
set_target_speed_duplex(target, tdata, 100, "full")
verify_speed_duplex(target, ns, tdata, 100, "full")
with test.step("Set fixed 100/half"):
with test.step("Verify fixed 100/half"):
set_host_speed_duplex(hdata, 100, "half")
set_target_speed_duplex(target, tdata, 100, "half")
verify_speed_duplex(target, ns, tdata, 100, "half")
@@ -189,27 +193,27 @@ with infamy.Test() as test:
enable_host_autoneg(hdata)
enable_target_autoneg(target, tdata)
with test.step("Configure host to advertise 10/Full only"):
with test.step("Verify auto-negotiation to 10/Full only"):
advertise_host_modes(hdata, ["10full"])
verify_speed_duplex(target, ns, tdata, 10, "full")
with test.step("Configure host to advertise 10/Half only"):
with test.step("Verify auto-negotiation to 10/Half only"):
advertise_host_modes(hdata, ["10half"])
verify_speed_duplex(target, ns, tdata, 10, "half")
with test.step("Configure host to advertise 100/Full only"):
with test.step("Verify auto-negotiation to 100/Full only"):
advertise_host_modes(hdata, ["100full"])
verify_speed_duplex(target, ns, tdata, 100, "full")
with test.step("Configure host to advertise 100/Half only"):
with test.step("Verify auto-negotiation to 100/Half only"):
advertise_host_modes(hdata, ["100half"])
verify_speed_duplex(target, ns, tdata, 100, "half")
with test.step("Configure host to advertise 10/half + 10/full + 100/half"):
with test.step("Verify auto-negotiation to 10/half + 10/full + 100/half"):
advertise_host_modes(hdata, ["10half", "10full", "100half"])
verify_speed_duplex(target, ns, tdata, 100, "half")
with test.step("Configure host to advertise 10/half + 10/full + 100/half + 100/full + 1000/full"):
with test.step("Verify auto-negotiation to 10/half + 10/full + 100/half + 100/full + 1000/full"):
advertise_host_modes(hdata, ["10half", "10full", "100half", "100full", "1000full"])
verify_speed_duplex(target, ns, tdata, 1000, "full")
@@ -0,0 +1 @@
container_host_commands.adoc
@@ -0,0 +1,29 @@
=== Host Command Execution from Container
==== Description
This test verifies that a container running on Infix can execute commands
that affect the host system. Specifically, it confirms that the container
can change the hostname of the host.
==== Topology
ifdef::topdoc[]
image::{topdoc}../../test/case/infix_containers/container_host_commands/topology.svg[Host Command Execution from Container topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::container_host_commands/topology.svg[Host Command Execution from Container topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[Host Command Execution from Container topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUT
. Set initial hostname
. Verify initial hostname in operational
. Include script in OCI image to modify host hostname
. Verify container has started
. Verify the new hostname set by the container
<<<
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: 1x1 Pages: 1 -->
<svg width="424pt" height="45pt"
viewBox="0.00 0.00 424.03 45.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 41)">
<title>1x1</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-41 420.03,-41 420.03,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 100,-36.5 100,-0.5 0,-0.5"/>
<text text-anchor="middle" x="25" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-0.5 50,-36.5 "/>
<text text-anchor="middle" x="75" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
</g>
<!-- target -->
<g id="node2" class="node">
<title>target</title>
<polygon fill="none" stroke="black" points="300.03,-0.5 300.03,-36.5 416.03,-36.5 416.03,-0.5 300.03,-0.5"/>
<text text-anchor="middle" x="325.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="350.03,-0.5 350.03,-36.5 "/>
<text text-anchor="middle" x="383.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
</g>
<!-- host&#45;&#45;target -->
<g id="edge1" class="edge">
<title>host:mgmt&#45;&#45;target:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M100,-18.5C100,-18.5 300.03,-18.5 300.03,-18.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Executable
+269
View File
@@ -0,0 +1,269 @@
#!/bin/sh
set -e
usage()
{
cat <<EOF
usage: $0 COMMAND [ARGS...]
Update contents of Infix images
Commands:
unpack PKG KEY-DIR DIR
Unpack the RAUC bundle PKG and the root filesystem contained in
it, using the keys stored in KEY-DIR, and store the results in
DIR.
pack DIR [KEY-DIR] PKG
Repackage the (possibly modified) root filesystem in DIR, updating
all signatures as necessary using the keys stored in KEY-DIR (or
the keys used to unpack the bundle, if not specified) and store
the resulting bundle in PKG.
exec DIR COMMAND [ARGS...]
Run COMMAND, possibly with ARGS, from the root of the filesystem
in DIR, inside of a fakeroot context that allows otherwise
privileged operations such as ownership modifications.
help
Show this message and exit.
Key directories:
KEY-DIR arguments must reference a directory that contains the
public/private keypair used to verify/sign an image. Most commonly,
board/common/signing-keys/development from the Infix source tree is
used to validate the input image.
Example:
Include a custom file inside a prebuilt Infix image, assign it to
the UID/GID 1337; and package up the results, signing it with the
same developer key that was used to create it:
ixbin unpack infix-aarch64.pkg ~/infix/board/common/signing-keys/development tmp
cp VBRUN300.DLL tmp/rootfs/lib/
ixbin exec tmp chown 1337:1337 lib/VBRUN300.DLL
ixbin pack tmp infix-vb3-aarch64.pkg
EOF
}
msg()
{
printf "\e[37;44mixbin: %-80s\e[0m\n" "$*"
}
err()
{
printf "\e[37;41mixbin: ERROR: %-73s\e[0m\n" "$*" >&2
}
die()
{
err "$*"
exit 1
}
ensuredeps()
{
local DEPS="fakeroot mksquashfs unsquashfs awk env mkimage truncate dtc fdtget rauc"
local missing=
for dep in $DEPS; do
command -v $dep >/dev/null && continue
missing="$missing $dep"
done
if [ -n "$missing" ]; then
die "Missing dependencies:$missing"
fi
}
squnpack()
{
local sq="$WORKDIR"/pkg/rootfs.img
# Capture the original compression algo and block size for when we
# resquash it later on.
unsquashfs -s "$sq" | awk '
/^Compression/ { printf(" -comp %s", $2); }
/^Block size/ { printf(" -b %s", $3); }
' >"$WORKDIR"/mksquash-opts
$FAKEROOT unsquashfs -d "$WORKDIR"/rootfs "$sq"
}
sqexec()
{
$FAKEROOT env -C "$WORKDIR"/rootfs "$@"
}
sqpack()
{
$FAKEROOT mksquashfs "$WORKDIR"/rootfs "$WORKDIR"/pkg/rootfs.img \
$(cat "$WORKDIR"/mksquash-opts) -noappend "$@"
}
sqsign()
{
local its="$1"
local keys="$2"
local hsize="$3"
local out="$4"
mkimage -E -p "$hsize" -B "$hsize" -k "$keys" -f "$its" "$WORKDIR"/sign.itb
truncate -s $(($hsize)) "$WORKDIR"/sign.itb
mv "$WORKDIR"/sign.itb "$out"
}
sqresign()
{
local keys="$1"
local sq="$WORKDIR"/pkg/rootfs.img
local itbh="$WORKDIR"/pkg/rootfs.itbh
signsize=$(printf '0x%x' $(fdtget "$itbh" /images/rootfs data-position))
dtc -I dtb -O dts "$itbh" | awk -v sq="$sq" '
/timestamp =/ || /data-\w+ =/ || /signer-\w+ =/ || /value =/{
/* Remove old signature */
next;
}
/compression = "none"/ {
/* Splice in reference to the squash */
print;
sub("compression", "data");
sub("\"none\"", sprintf("/incbin/(\"%s\")", sq));
print;
next;
}
{
/* Keep the rest as-is */
print;
};
' >"$WORKDIR"/resign.its
sqsign "$WORKDIR"/resign.its "$keys" "$signsize" "$itbh"
}
unpack()
{
local pkg="$1"
local keys="$2"
WORKDIR=$(readlink -f "$3")
case "$#" in
3)
;;
*)
echo "Usage: ixbin unpack PKG KEYS DIR" >&2
exit 1
esac
local crt=$(ls "$keys"/*.crt | head -n1)
[ -r "$crt" ] || die "No public key found in $keys"
msg "Setting up working directory"
mkdir -p "$WORKDIR"
cp -a "$keys" "$WORKDIR"/in-keys
touch "$WORKDIR"/fakeroot-state
FAKEROOT="fakeroot -i $WORKDIR/fakeroot-state -s $WORKDIR/fakeroot-state"
msg "Unpacking RAUC bundle"
rauc --keyring="$crt" extract "$pkg" "$WORKDIR"/pkg
msg "Unpacking Squash filesystem"
squnpack
msg "OK, $WORKDIR/rootfs can now be modified"
}
run()
{
WORKDIR=$(readlink -f "$1")
shift
[ -r "$WORKDIR"/fakeroot-state ] || \
die "$WORKDIR does not contain an unpacked image"
FAKEROOT="fakeroot -i $WORKDIR/fakeroot-state -s $WORKDIR/fakeroot-state"
sqexec "$@"
}
pack()
{
WORKDIR=$(readlink -f "$1")
local keys="$2"
local pkg="$3"
case "$#" in
3)
;;
2)
keys="$WORKDIR"/in-keys
pkg="$2"
;;
*)
echo "Usage: ixbin pack DIR [KEYS] DIR" >&2
exit 1
esac
[ -r "$WORKDIR"/fakeroot-state ] || \
die "$WORKDIR does not contain an unpacked image"
local crt=$(ls "$keys"/*.crt | head -n1)
[ -r "$crt" ] || die "No public key found in $keys"
local key=$(ls "$keys"/*.key | head -n1)
[ -r "$crt" ] || die "No private key found in $keys"
FAKEROOT="fakeroot -i $WORKDIR/fakeroot-state -s $WORKDIR/fakeroot-state"
msg "Packing up Squash filesystem"
sqpack
[ -f "$WORKDIR"/pkg/rootfs.itbh ] && {
msg "Updating Squash filesystem signature"
sqresign "$keys"
}
msg "Creating RAUC bundle"
rauc --keyring="$crt" --cert="$crt" --key="$key" bundle "$WORKDIR"/pkg "$pkg"
msg "OK, updated bundle available in $pkg"
}
if [ $# -lt 1 ]; then
usage
exit 1
fi
cmd="$1"
shift
case "$cmd" in
unpack)
ensuredeps
unpack "$@"
;;
"exec")
ensuredeps
run "$@"
;;
pack)
ensuredeps
pack "$@"
;;
help)
usage
;;
*)
die "Unknown command \"$cmd\""
;;
esac