diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7ecfe0b..314fae8 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -25,4 +25,24 @@ jobs: -u 'github:${{ secrets.NEXUS_PASSWORD }}' -H "Content-Type: multipart/form-data" --data-binary "@./$(find target/debian/ -name *.deb)" - "https://repo.stackable.tech/repository/deb-nightly/" \ No newline at end of file + "https://repo.stackable.tech/repository/deb-nightly/" + + centos: + runs-on: centos${{ matrix.node }} + strategy: + matrix: + node: [ 7, 8 ] + steps: + - uses: actions/checkout@v2 + - name: Build + run: ~/.cargo/bin/cargo +nightly build --verbose --release + - name: Build RPM package + run: packaging/buildrpm.sh stackable-agent + - name: Publish RPM package + run: >- + /usr/bin/curl + -vvvv + --fail + -u 'github:${{ secrets.NEXUS_PASSWORD }}' + --upload-file "./$(find target/rpm/RPMS/x86_64/ -name *.rpm)" + "https://repo.stackable.tech/repository/rpm-nightly/el${{ matrix.node }}/" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 4f9982f..b5a7c8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,6 @@ codegen-units = 1 maintainer-scripts = "packaging/debian/" systemd-units = { enable = false } assets = [ - ["packaging/config/agent.conf", "etc/stackable-agent/", "644"], - ["target/release/agent", "opt/stackable-agent/stackable-agent", "755"], -] + ["packaging/config/agent.conf", "etc/stackable/stackable-agent/", "644"], + ["target/release/stackable-agent", "opt/stackable-agent/stackable-agent", "755"], +] \ No newline at end of file diff --git a/documentation/commandline_args.adoc b/documentation/commandline_args.adoc index 7e64ec3..54c7c07 100644 --- a/documentation/commandline_args.adoc +++ b/documentation/commandline_args.adoc @@ -1,7 +1,7 @@ -=== server-cert-file +=== no-config *Default value*: `No default value` @@ -10,60 +10,78 @@ *Multiple values:* false -The certificate file for the local webserver which the Krustlet starts. +If this option is specified, any file referenced in AGENT_CONF environment variable will be ignored. -=== server-port +=== pod-cidr -*Default value*: `3000` +*Default value*: `` *Required*: false *Multiple values:* false -Port to listen on for callbacks. +This setting controls the pod address range that the agent reports to Kubernetes. +The effect of this setting is that Kubernetes will reserve address blocks from withhin this range for every node. +Depending on the setting for maximum pods per node, these will be larger or smaller ranges, and influence the maximum number of nodes for the cluster. +The agent does not require any pod address ranges, and by default doesn't specify anything for this setting. -=== package-directory +WARNING: There should almost never be a reason to use this setting, this is mostly here for very special circumstances. Do not touch it unless you really know what you're doing. -*Default value*: `/opt/stackable/packages` + +=== bootstrap-file + +*Default value*: `/etc/stackable/stackable-agent/bootstrap-kubelet.conf` *Required*: false *Multiple values:* false -This directory will serve as starting point for packages that are needed by pods assigned to this node.\n Packages will be downloaded into the "_download" folder at the top level of this folder as archives and remain there for potential future use. +The bootstrap file to use in case Kubernetes bootstraping is used to add the agent. -Archives will the be extracted directly into this folder in subdirectories following the naming -scheme of "productname-productversion". -The agent will need full access to this directory and tries to create it if it does not exist. +=== server-bind-ip +*Default value*: `No default value` -=== data-directory +*Required*: false + +*Multiple values:* false + + +The local IP to register as the node's ip with the apiserver. Will be automatically set to the first address of the first non-loopback interface if not specified. + + +=== server-key-file -*Default value*: `/var/stackable/agent/data` +*Default value*: `/etc/stackable/stackable-agent/secret/agent.key` *Required*: false *Multiple values:* false -The directory where the stackable agent should keep its working data. +Private key file (in PKCS8 format) to use for the local webserver the Krustlet starts. -=== no-config +=== package-directory -*Default value*: `No default value` +*Default value*: `/opt/stackable/packages` *Required*: false *Multiple values:* false -If this option is specified, any file referenced in AGENT_CONF environment variable will be ignored. +This directory will serve as starting point for packages that are needed by pods assigned to this node.\n Packages will be downloaded into the "_download" folder at the top level of this folder as archives and remain there for potential future use. + +Archives will the be extracted directly into this folder in subdirectories following the naming +scheme of "productname-productversion". + +The agent will need full access to this directory and tries to create it if it does not exist. === hostname @@ -78,49 +96,45 @@ If this option is specified, any file referenced in AGENT_CONF environment varia The hostname to register the node under in Kubernetes - defaults to system hostname. -=== pod-cidr +=== data-directory -*Default value*: `` +*Default value*: `/var/lib/stackable/agent` *Required*: false *Multiple values:* false -This setting controls the pod address range that the agent reports to Kubernetes. -The effect of this setting is that Kubernetes will reserve address blocks from withhin this range for every node. -Depending on the setting for maximum pods per node, these will be larger or smaller ranges, and influence the maximum number of nodes for the cluster. - -The agent does not require any pod address ranges, and by default doesn't specify anything for this setting. +The directory where the stackable agent should keep its working data. -=== server-key-file +=== server-cert-file -*Default value*: `No default value` +*Default value*: `/etc/stackable/stackable-agent/secret/agent.crt` *Required*: false *Multiple values:* false -Private key file (in PKCS8 format) to use for the local webserver the Krustlet starts. +The certificate file for the local webserver which the Krustlet starts. -=== server-bind-ip +=== server-port -*Default value*: `No default value` +*Default value*: `3000` *Required*: false *Multiple values:* false -The local IP to register as the node's ip with the apiserver. Will be automatically set to the first address of the first non-loopback interface if not specified. +Port to listen on for callbacks. === config-directory -*Default value*: `/opt/stackable/config` +*Default value*: `/etc/stackable/serviceconfig` *Required*: false @@ -140,43 +154,54 @@ WARNING: This allows anybody who can specify pods more or less full access to th The agent will need full access to this directory and tries to create it if it does not exist. -=== tag +=== log-directory -*Default value*: `No default value` +*Default value*: `/var/log/stackable/servicelogs` *Required*: false -*Multiple values:* true +*Multiple values:* false -A "key=value" pair that should be assigned to this agent as tag. This can be specified multiple times to assign additional tags. +This directory will serve as starting point for all log files which this service creates. +Every service will get its own subdirectory created within this directory. +Anything that is then specified in the log4j config or similar files will be resolved relatively to this directory. -Tags are the main way of identifying nodes to assign services to later on. +The agent will need full access to this directory and tries to create it if it does not exist. -=== log-directory +=== session -*Default value*: `/opt/stackable/logs` +*Default value*: `No default value` *Required*: false *Multiple values:* false -This directory will serve as starting point for all log files which this service creates. -Every service will get its own subdirectory created within this directory. -Anything that is then specified in the log4j config or similar files will be resolved relatively to this directory. +This parameter specifies whether to use a session or the system DBus connection when talking to systemd. +For our purposps the difference between the two can be explained as the session bus being restricted to the current user, whereas the system bus rolls out services that are available for every user. +In reality is is a bit more involved than that, please refer to the https://dbus.freedesktop.org/doc/dbus-specification.html[official docs] for more information. -The agent will need full access to this directory and tries to create it if it does not exist. +When this flag is specified it causes symlinks for loaded services to be created in the currently active users systemd directory `~/.config/systemd/user` instead of one of the globally valid locations: +- `/lib/systemd/system` +- `/etc/systemd/system` -=== bootstrap-file +The default is to use the system bus, for which it is necessary that the agent either run as root or have passwordless sudo rights. + +Using the session bus will mainly be useful for scenarios without root access and for testing on developer machines. -*Default value*: `/etc/kubernetes/bootstrap-kubelet.conf` + +=== tag + +*Default value*: `No default value` *Required*: false -*Multiple values:* false +*Multiple values:* true -The bootstrap file to use in case Kubernetes bootstraping is used to add the agent. \ No newline at end of file +A "key=value" pair that should be assigned to this agent as tag. This can be specified multiple times to assign additional tags. + +Tags are the main way of identifying nodes to assign services to later on. \ No newline at end of file diff --git a/packaging/buildrpm.sh b/packaging/buildrpm.sh new file mode 100755 index 0000000..00d427a --- /dev/null +++ b/packaging/buildrpm.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# This script creates an RPM package containing the binary created by this Cargo project. +# The script is not universally applicable, since it makes a few assumptions about the project structure: +# 1. The RPM scaffolding needs to be provided in packaging/rpm +# 2. The binary to be packaged needs to be created in target/release + +# The script takes one argument, which is the name of the binary that has been created by the build process. +# This argument will be reused for naming the final RPM file. + +# Check if one parameter was specified - we'll use this as the name parameter for all files +# This allows us to reuse the script across all operators +if [ -z $1 ]; then + echo "This script requires the project name to be specified as the first parameter!" + exit 1 +fi + +export PACKAGE_NAME=$1 +BINARY_FILE=target/release/$PACKAGE_NAME + +# The package description is parsed from the output of `cargo metadata` by using jq. +# We need to look up the package with a select statement to match the name from an array of packages +# The name is passed into jq as a jq variable, as no substitution would take place within the single +# quotes of the jq expression. +export PACKAGE_DESCRIPTION=$(~/.cargo/bin/cargo metadata --format-version 1| jq --arg NAME "$PACKAGE_NAME" '.packages[] | select(.name == $NAME) | .description') +if [ -z $PACKAGE_DESCRIPTION ]; then + echo "Unable to parse package description from output of `cargo metadata`, cannot build RPM without this field!" + exit 2 +fi +echo + +# Check that we are being called from the main directory and the release build process has been run +if [ ! -f $BINARY_FILE ]; then + echo "Binary file not found at [$BINARY_FILE] - this script should be called from the root directory of the repository and 'cargo build --release' needs to have run before calling this script!" + exit 3 +fi + +echo Cleaning up prior build attempts +rm -rf target/rpm + +# Parse the version and release strings from the PKGID reported by Cargo +# This is in the form Path#Projectname:version, which we parse by repeated calls to awk with different separators +# This could most definitely be improved, but works for now +export VERSION_STRING=$(~/.cargo/bin/cargo pkgid | awk -F'#' '{print $2}' | awk -F':' '{print $2}') +echo version: ${VERSION_STRING} + +export PACKAGE_VERSION=$(echo ${VERSION_STRING} | awk -F '-' '{print $1}') + +# Any suffix like '-nightly' is split out into the release here, as - is not an allowed character in rpm versions +# The final release will look like 0.suffix or 0 if no suffix is specified. +export PACKAGE_RELEASE="0$(echo ${VERSION_STRING} | awk -F '-' '{ if ($2 != "") print "."$2;}')" + +echo Defined package version: [${PACKAGE_VERSION}] +echo Defined package release: [${PACKAGE_RELEASE}] +echo Defined package description: [${PACKAGE_DESCRIPTION}] + +echo Creating directory scaffolding for RPM +cp -r packaging/rpm target/ +# Create empty directory for the binary to be placed into +mkdir -p target/rpm/SOURCES/${PACKAGE_NAME}-VERSION/opt/stackable/${PACKAGE_NAME} + +# Create config directory and copy config file template over +mkdir -p target/rpm/SOURCES/${PACKAGE_NAME}-VERSION/etc/stackable/${PACKAGE_NAME} +cp packaging/config/agent.conf target/rpm/SOURCES/${PACKAGE_NAME}-VERSION/etc/stackable/${PACKAGE_NAME} + +# The packaging source directory does not contain the version yet, as this will need to be replaced for every +# execution. Instead the directory name contains the marker "VERSION" which we now replace with the actual version. +rename VERSION ${PACKAGE_VERSION} target/rpm/SOURCES/${PACKAGE_NAME}-VERSION + +cp target/release/${PACKAGE_NAME} target/rpm/SOURCES/${PACKAGE_NAME}-${PACKAGE_VERSION}/opt/stackable/${PACKAGE_NAME}/ + +pushd target/rpm/SOURCES +tar czvf ${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz ${PACKAGE_NAME}-${PACKAGE_VERSION} +popd + +rpmbuild --define "_topdir `pwd`/target/rpm" -v -ba target/rpm/SPECS/${PACKAGE_NAME}.spec diff --git a/packaging/config/agent.conf b/packaging/config/agent.conf index 19ae909..e69de29 100644 --- a/packaging/config/agent.conf +++ b/packaging/config/agent.conf @@ -1,5 +0,0 @@ ---package-directory=/var/lib/stackable/packages ---config-directory=/var/lib/stackable/config ---data-directory=/var/lib/stackable/agent ---server-key-file=/var/lib/stackable/agent/config/agent.key ---server-cert-file=/var/lib/stackable/agent/config/agent.crt \ No newline at end of file diff --git a/packaging/debian/postinst b/packaging/debian/postinst index 320bab6..1a9628c 100644 --- a/packaging/debian/postinst +++ b/packaging/debian/postinst @@ -1,7 +1,9 @@ #!/usr/bin/env bash -mkdir -p /var/lib/stackable/packages -mkdir -p /var/lib/stackable/config -mkdir -p /var/lib/stackable/agent/config +mkdir -p /opt/stackable/packages +mkdir -p /var/lib/stackable/stackable-agent +mkdir -p /var/log/stackable/servicelogs +mkdir -p /etc/stackable/stackable-agent +mkdir -m 700 /etc/stackable/stackable-agent/secret #DEBHELPER# \ No newline at end of file diff --git a/packaging/debian/service b/packaging/debian/service index 1825efa..d80fa43 100644 --- a/packaging/debian/service +++ b/packaging/debian/service @@ -8,7 +8,7 @@ ExecStart=/opt/stackable-agent/stackable-agent Restart=on-abort StandardOutput=journal StandardError=journal -Environment="CONFIG_FILE=/etc/stackable-agent/agent.conf" +Environment="CONFIG_FILE=/etc/stackable/stackable-agent/agent.conf" Environment="RUST_LOG=info" [Install] WantedBy=multi-user.target diff --git a/packaging/rpm/SOURCES/stackable-agent-VERSION/usr/lib/systemd/system/stackable-agent.service b/packaging/rpm/SOURCES/stackable-agent-VERSION/usr/lib/systemd/system/stackable-agent.service new file mode 100644 index 0000000..0549d4b --- /dev/null +++ b/packaging/rpm/SOURCES/stackable-agent-VERSION/usr/lib/systemd/system/stackable-agent.service @@ -0,0 +1,14 @@ +[Unit] +Description=Stackable Agent +Before= +After=network.target +[Service] +User=root +ExecStart=/opt/stackable/stackable-agent/stackable-agent +Restart=on-abort +StandardOutput=journal +StandardError=journal +Environment="CONFIG_FILE=/etc/stackable/stackable-agent/agent.conf" +Environment="RUST_LOG=info" +[Install] +WantedBy=multi-user.target diff --git a/packaging/rpm/SPECS/stackable-agent.spec b/packaging/rpm/SPECS/stackable-agent.spec new file mode 100644 index 0000000..6c27791 --- /dev/null +++ b/packaging/rpm/SPECS/stackable-agent.spec @@ -0,0 +1,62 @@ +%define __spec_install_post %{nil} +%define __os_install_post %{_dbpath}/brp-compress +%define debug_package %{nil} +%define _servicedir /usr/lib/systemd/system +%define _version %{getenv:PACKAGE_VERSION} +%define _release %{getenv:PACKAGE_RELEASE} +%define _name %{getenv:PACKAGE_NAME} +%define _bindir /opt/stackable/%{_name} +%define _confdir /etc/stackable/%{_name} +%define _vardir /var/lib/stackable/%{_name} +%define _description %{getenv:PACKAGE_DESCRIPTION} + +Name: %{_name} +Summary: %{_description} +Version: %{_version} +Release: %{_release}%{?dist} +License: ASL 2.0 +Group: Applications/System +Source0: %{name}-%{version}.tar.gz + +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root + +%description +%{summary} + +%prep +%setup -q + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot} +cp -a * %{buildroot} + +%post +systemctl daemon-reload +mkdir -p /opt/stackable/packages +mkdir -p %{_vardir} +mkdir -p /var/log/stackable/servicelogs +mkdir -p %{_confdir} +mkdir -m 700 %{_confdir}/secret + +%preun +if [ $1 == 0 ]; then #uninstall + systemctl unmask %{name}.service + systemctl stop %{name}.service + systemctl disable %{name}.service +fi + +%postun +if [ $1 == 0 ]; then #uninstall + systemctl daemon-reload + systemctl reset-failed +fi + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%{_bindir}/* +%{_servicedir}/%{name}.service +%{_confdir}/agent.conf \ No newline at end of file diff --git a/src/bin/agent.rs b/src/bin/stackable-agent.rs similarity index 100% rename from src/bin/agent.rs rename to src/bin/stackable-agent.rs diff --git a/src/config/mod.rs b/src/config/mod.rs index b3e684e..38d69b8 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -53,7 +53,7 @@ impl AgentConfig { pub const DATA_DIR: ConfigOption = ConfigOption { name: "data-directory", - default: Some("/var/stackable/agent/data"), + default: Some("/var/lib/stackable/agent"), required: false, takes_argument: true, help: "The directory where the stackable agent should keep its working data.", @@ -63,7 +63,7 @@ impl AgentConfig { pub const BOOTSTRAP_FILE: ConfigOption = ConfigOption { name: "bootstrap-file", - default: Some("/etc/kubernetes/bootstrap-kubelet.conf"), + default: Some("/etc/stackable/stackable-agent/bootstrap-kubelet.conf"), required: false, takes_argument: true, help: "The bootstrap file to use in case Kubernetes bootstraping is used to add the agent.", @@ -83,7 +83,7 @@ impl AgentConfig { pub const SERVER_CERT_FILE: ConfigOption = ConfigOption { name: "server-cert-file", - default: None, + default: Some("/etc/stackable/stackable-agent/secret/agent.crt"), required: false, takes_argument: true, help: "The certificate file for the local webserver which the Krustlet starts.", @@ -93,7 +93,7 @@ impl AgentConfig { pub const SERVER_KEY_FILE: ConfigOption = ConfigOption { name: "server-key-file", - default: None, + default: Some("/etc/stackable/stackable-agent/secret/agent.key"), required: false, takes_argument: true, help: @@ -124,7 +124,7 @@ impl AgentConfig { pub const CONFIG_DIR: ConfigOption = ConfigOption { name: "config-directory", - default: Some("/opt/stackable/config"), + default: Some("/etc/stackable/serviceconfig"), required: false, takes_argument: true, help: "The base directory under which configuration will be generated for all executed services.", @@ -134,7 +134,7 @@ impl AgentConfig { pub const LOG_DIR: ConfigOption = ConfigOption { name: "log-directory", - default: Some("/opt/stackable/logs"), + default: Some("/var/log/stackable/servicelogs"), required: false, takes_argument: true, help: "The base directory under which log files will be placed for all services.",