contrib: Set up continuous integration through Jenkins
This commit is contained in:
parent
8553254867
commit
174dd3146f
27
contrib/ci/Jenkinsfile
vendored
Normal file
27
contrib/ci/Jenkinsfile
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
pipeline {
|
||||
agent { label 'gluon-docker' }
|
||||
environment {
|
||||
GLUON_SITEDIR = "contrib/ci/minimal-site"
|
||||
GLUON_TARGET = "x86-64"
|
||||
BUILD_LOG = "1"
|
||||
}
|
||||
stages {
|
||||
stage('lint') {
|
||||
steps {
|
||||
sh 'luacheck package scripts targets'
|
||||
}
|
||||
}
|
||||
stage('docs') {
|
||||
steps {
|
||||
sh 'make -C docs html'
|
||||
}
|
||||
}
|
||||
stage('build') {
|
||||
steps {
|
||||
sh 'make update'
|
||||
sh 'test -d /dl_cache && ln -s /dl_cache openwrt/dl || true'
|
||||
sh 'make -j$(nproc) V=s'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
contrib/ci/jenkins-community-slave/Dockerfile
Normal file
33
contrib/ci/jenkins-community-slave/Dockerfile
Normal file
@ -0,0 +1,33 @@
|
||||
FROM gluon
|
||||
|
||||
USER root
|
||||
|
||||
# this is needed to install default-jre-headless in debian slim images
|
||||
RUN mkdir -p /usr/share/man/man1
|
||||
|
||||
RUN apt-get update && apt-get install -y default-jre-headless curl python3 python3-pip python3-sphinx git
|
||||
RUN pip3 install jenkins-webapi sphinx_rtd_theme
|
||||
|
||||
# Get docker-compose in the agent container
|
||||
RUN mkdir -p /home/jenkins
|
||||
RUN mkdir -p /var/lib/jenkins
|
||||
RUN mkdir -p /remoting
|
||||
RUN chown gluon /home/jenkins
|
||||
RUN chown gluon /var/lib/jenkins
|
||||
RUN chown gluon /remoting
|
||||
|
||||
# Start-up script to attach the slave to the master
|
||||
ADD slave.py /var/lib/jenkins/slave.py
|
||||
|
||||
USER gluon
|
||||
|
||||
WORKDIR /home/jenkins
|
||||
|
||||
ENV JENKINS_URL "https://build.ffh.zone/"
|
||||
ENV JENKINS_SLAVE_ADDRESS ""
|
||||
ENV SLAVE_EXECUTORS "1"
|
||||
ENV SLAVE_LABELS "docker"
|
||||
ENV SLAVE_WORING_DIR ""
|
||||
ENV CLEAN_WORKING_DIR "true"
|
||||
|
||||
CMD [ "python3", "-u", "/var/lib/jenkins/slave.py" ]
|
32
contrib/ci/jenkins-community-slave/README.md
Normal file
32
contrib/ci/jenkins-community-slave/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Gluon CI using Jenkins
|
||||
|
||||
## Requirements
|
||||
- Only a host with docker.
|
||||
|
||||
## Architecture
|
||||
|
||||
![Screenshot from 2019-09-24 00-20-32](https://user-images.githubusercontent.com/601153/65468827-9edf2c80-de65-11e9-9fe0-56c3487719c3.png)
|
||||
|
||||
## Installation
|
||||
You can support the gluon CI with your infrastructure:
|
||||
1. You need to query @lemoer (freifunk@irrelefant.net) for credentials.
|
||||
2. He will give you a `SLAVE_NAME` and a `SLAVE_SECRET` for your host.
|
||||
3. Then go to your docker host and substitute the values for `SLAVE_NAME` and a `SLAVE_SECRET` in the following statements:
|
||||
``` shell
|
||||
git clone https://github.com/freifunk-gluon/gluon/
|
||||
cd gluon/contrib/ci/jenkins-community-slave/
|
||||
docker build -t gluon-jenkins .
|
||||
mkdir /var/cache/openwrt_dl_cache/
|
||||
docker run --detach --restart always \
|
||||
-e "SLAVE_NAME=whoareyou" \
|
||||
-e "SLAVE_SECRET=changeme" \
|
||||
-v /var/cache/openwrt_dl_cache/:/dl_cache
|
||||
```
|
||||
4. Check whether the instance is running correctly:
|
||||
- Your node should appear [here](https://build.ffh.zone/label/gluon-docker/).
|
||||
- When clicking on it, Jenkins should state "Agent is connected." like here:
|
||||
![Screenshot from 2019-09-24 01-00-52](https://user-images.githubusercontent.com/601153/65469209-dac6c180-de66-11e9-9d62-0d1c3b6b940b.png)
|
||||
5. **Your docker container needs to be rebuilt, when the build dependencies of gluon change. So please be aware of that and update your docker container in that case.**
|
||||
|
||||
## Backoff
|
||||
- If @lemoer is not reachable, please be patient at first if possible. Otherwise contact info@hannover.freifunk.net or join the channel `#freifunkh` on hackint.
|
103
contrib/ci/jenkins-community-slave/slave.py
Normal file
103
contrib/ci/jenkins-community-slave/slave.py
Normal file
@ -0,0 +1,103 @@
|
||||
from jenkins import Jenkins, JenkinsError, NodeLaunchMethod
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import urllib.request
|
||||
import subprocess
|
||||
import shutil
|
||||
import requests
|
||||
import time
|
||||
|
||||
slave_jar = '/var/lib/jenkins/slave.jar'
|
||||
slave_name = os.environ['SLAVE_NAME'] if os.environ['SLAVE_NAME'] != '' else 'docker-slave-' + os.environ['HOSTNAME']
|
||||
jnlp_url = os.environ['JENKINS_URL'] + '/computer/' + slave_name + '/slave-agent.jnlp'
|
||||
slave_jar_url = os.environ['JENKINS_URL'] + '/jnlpJars/slave.jar'
|
||||
print(slave_jar_url)
|
||||
process = None
|
||||
|
||||
def clean_dir(dir):
|
||||
for root, dirs, files in os.walk(dir):
|
||||
for f in files:
|
||||
os.unlink(os.path.join(root, f))
|
||||
for d in dirs:
|
||||
shutil.rmtree(os.path.join(root, d))
|
||||
|
||||
def slave_create(node_name, working_dir, executors, labels):
|
||||
j = Jenkins(os.environ['JENKINS_URL'], os.environ['JENKINS_USER'], os.environ['JENKINS_PASS'])
|
||||
j.node_create(node_name, working_dir, num_executors = int(executors), labels = labels, launcher = NodeLaunchMethod.JNLP)
|
||||
|
||||
def slave_delete(node_name):
|
||||
j = Jenkins(os.environ['JENKINS_URL'], os.environ['JENKINS_USER'], os.environ['JENKINS_PASS'])
|
||||
j.node_delete(node_name)
|
||||
|
||||
def slave_download(target):
|
||||
if os.path.isfile(slave_jar):
|
||||
os.remove(slave_jar)
|
||||
|
||||
loader = urllib.request.URLopener()
|
||||
loader.retrieve(os.environ['JENKINS_URL'] + '/jnlpJars/slave.jar', '/var/lib/jenkins/slave.jar')
|
||||
|
||||
def slave_run(slave_jar, jnlp_url):
|
||||
params = [ 'java', '-jar', slave_jar, '-jnlpUrl', jnlp_url ]
|
||||
if os.environ['JENKINS_SLAVE_ADDRESS'] != '':
|
||||
params.extend([ '-connectTo', os.environ['JENKINS_SLAVE_ADDRESS' ] ])
|
||||
|
||||
if os.environ['SLAVE_SECRET'] == '':
|
||||
params.extend([ '-jnlpCredentials', os.environ['JENKINS_USER'] + ':' + os.environ['JENKINS_PASS'] ])
|
||||
else:
|
||||
params.extend([ '-secret', os.environ['SLAVE_SECRET'] ])
|
||||
return subprocess.Popen(params, stdout=subprocess.PIPE)
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
if process != None:
|
||||
process.send_signal(signal.SIGINT)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
def h():
|
||||
print("ERROR!: please specify environment variables")
|
||||
print("")
|
||||
print('docker run -e "SLAVE_NAME=test" -e "SLAVE_SECRET=..." jenkins')
|
||||
|
||||
if os.environ.get('SLAVE_NAME') is None:
|
||||
h()
|
||||
sys.exit(1)
|
||||
|
||||
if os.environ.get('SLAVE_SECRET') is None:
|
||||
h()
|
||||
sys.exit(1)
|
||||
|
||||
def master_ready(url):
|
||||
try:
|
||||
r = requests.head(url, verify=False, timeout=None)
|
||||
return r.status_code == requests.codes.ok
|
||||
except:
|
||||
return False
|
||||
|
||||
while not master_ready(slave_jar_url):
|
||||
print("Master not ready yet, sleeping for 10sec!")
|
||||
time.sleep(10)
|
||||
|
||||
slave_download(slave_jar)
|
||||
print('Downloaded Jenkins slave jar.')
|
||||
|
||||
if os.environ['SLAVE_WORING_DIR']:
|
||||
os.setcwd(os.environ['SLAVE_WORING_DIR'])
|
||||
|
||||
if os.environ['CLEAN_WORKING_DIR'] == 'true':
|
||||
clean_dir(os.getcwd())
|
||||
print("Cleaned up working directory.")
|
||||
|
||||
if os.environ['SLAVE_NAME'] == '':
|
||||
slave_create(slave_name, os.getcwd(), os.environ['SLAVE_EXECUTORS'], os.environ['SLAVE_LABELS'])
|
||||
print('Created temporary Jenkins slave.')
|
||||
|
||||
process = slave_run(slave_jar, jnlp_url)
|
||||
print('Started Jenkins slave with name "' + slave_name + '" and labels [' + os.environ['SLAVE_LABELS'] + '].')
|
||||
process.wait()
|
||||
|
||||
print('Jenkins slave stopped.')
|
||||
if os.environ['SLAVE_NAME'] == '':
|
||||
slave_delete(slave_name)
|
||||
print('Removed temporary Jenkins slave.')
|
1
contrib/ci/minimal-site/i18n
Symbolic link
1
contrib/ci/minimal-site/i18n
Symbolic link
@ -0,0 +1 @@
|
||||
../../../docs/site-example/i18n/
|
1
contrib/ci/minimal-site/modules
Symbolic link
1
contrib/ci/minimal-site/modules
Symbolic link
@ -0,0 +1 @@
|
||||
../../../docs/site-example/modules
|
154
contrib/ci/minimal-site/site.conf
Normal file
154
contrib/ci/minimal-site/site.conf
Normal file
@ -0,0 +1,154 @@
|
||||
-- This is an example site configuration for Gluon v2018.2+
|
||||
--
|
||||
-- Take a look at the documentation located at
|
||||
-- https://gluon.readthedocs.io/ for details.
|
||||
--
|
||||
-- This configuration will not work as is. You're required to make
|
||||
-- community specific changes to it!
|
||||
{
|
||||
-- Used for generated hostnames, e.g. freifunk-abcdef123456. (optional)
|
||||
-- hostname_prefix = 'freifunk-',
|
||||
|
||||
-- Name of the community.
|
||||
site_name = 'Continious Integration',
|
||||
|
||||
-- Shorthand of the community.
|
||||
site_code = 'ci',
|
||||
|
||||
-- 32 bytes of random data, encoded in hexadecimal
|
||||
-- This data must be unique among all sites and domains!
|
||||
-- Can be generated using: echo $(hexdump -v -n 32 -e '1/1 "%02x"' </dev/urandom)
|
||||
domain_seed = 'e9608c4ff338b920992d629190e9ff11049de1dfc3f299eac07792dfbcda341c',
|
||||
|
||||
-- Prefixes used within the mesh.
|
||||
-- prefix6 is required, prefix4 can be omitted if next_node.ip4
|
||||
-- is not set.
|
||||
prefix4 = '10.0.0.0/20',
|
||||
prefix6 = 'fd::/64',
|
||||
|
||||
-- Timezone of your community.
|
||||
-- See https://openwrt.org/docs/guide-user/base-system/system_configuration#time_zones
|
||||
timezone = 'CET-1CEST,M3.5.0,M10.5.0/3',
|
||||
|
||||
-- List of NTP servers in your community.
|
||||
-- Must be reachable using IPv6!
|
||||
-- ntp_servers = {'1.ntp.services.ffxx'},
|
||||
|
||||
-- Wireless regulatory domain of your community.
|
||||
regdom = 'DE',
|
||||
|
||||
-- Wireless configuration for 2.4 GHz interfaces.
|
||||
wifi24 = {
|
||||
-- Wireless channel.
|
||||
channel = 1,
|
||||
|
||||
-- ESSID used for client network.
|
||||
ap = {
|
||||
ssid = 'gluon-ci-ssid',
|
||||
-- disabled = true, -- (optional)
|
||||
},
|
||||
|
||||
mesh = {
|
||||
-- Adjust these values!
|
||||
id = 'ueH3uXjdp', -- usually you don't want users to connect to this mesh-SSID, so use a cryptic id that no one will accidentally mistake for the client WiFi
|
||||
mcast_rate = 12000,
|
||||
-- disabled = true, -- (optional)
|
||||
},
|
||||
},
|
||||
|
||||
-- Wireless configuration for 5 GHz interfaces.
|
||||
-- This should be equal to the 2.4 GHz variant, except
|
||||
-- for channel.
|
||||
wifi5 = {
|
||||
channel = 44,
|
||||
outdoor_chanlist = '100-140',
|
||||
ap = {
|
||||
ssid = 'gluon-ci-ssid',
|
||||
},
|
||||
mesh = {
|
||||
-- Adjust these values!
|
||||
id = 'ueH3uXjdp',
|
||||
mcast_rate = 12000,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
-- The next node feature allows clients to always reach the node it is
|
||||
-- connected to using a known IP address.
|
||||
next_node = {
|
||||
-- anycast IPs of all nodes
|
||||
-- name = { 'nextnode.location.community.example.org', 'nextnode', 'nn' },
|
||||
ip4 = '10.0.0.1',
|
||||
ip6 = 'fd::1',
|
||||
},
|
||||
|
||||
mesh = {
|
||||
vxlan = true,
|
||||
batman_adv = {
|
||||
routing_algo = 'BATMAN_IV'
|
||||
}
|
||||
},
|
||||
|
||||
mesh_vpn = {
|
||||
-- enabled = true,
|
||||
mtu = 1312,
|
||||
|
||||
fastd = {
|
||||
-- Refer to https://fastd.readthedocs.io/en/latest/ to better understand
|
||||
-- what these options do.
|
||||
|
||||
-- List of crypto-methods to use.
|
||||
methods = {'salsa2012+umac'},
|
||||
-- configurable = true,
|
||||
-- syslog_level = 'warn',
|
||||
|
||||
groups = {
|
||||
backbone = {
|
||||
-- Limit number of connected peers to reduce bandwidth.
|
||||
limit = 1,
|
||||
|
||||
-- List of peers.
|
||||
peers = {
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
bandwidth_limit = {
|
||||
-- The bandwidth limit can be enabled by default here.
|
||||
enabled = false,
|
||||
|
||||
-- Default upload limit (kbit/s).
|
||||
egress = 200,
|
||||
|
||||
-- Default download limit (kbit/s).
|
||||
ingress = 3000,
|
||||
},
|
||||
},
|
||||
|
||||
autoupdater = {
|
||||
-- Default branch. Don't forget to set GLUON_BRANCH when building!
|
||||
branch = 'stable',
|
||||
|
||||
-- List of branches. You may define multiple branches.
|
||||
branches = {
|
||||
stable = {
|
||||
name = 'stable',
|
||||
|
||||
-- List of mirrors to fetch images from. IPv6 required!
|
||||
mirrors = {'http://1.updates.services.ffhl/stable/sysupgrade'},
|
||||
|
||||
-- Number of good signatures required.
|
||||
-- Have multiple maintainers sign your build and only
|
||||
-- accept it when a sufficient number of them have
|
||||
-- signed it.
|
||||
good_signatures = 2,
|
||||
|
||||
-- List of public keys of maintainers.
|
||||
pubkeys = {
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
1
contrib/ci/minimal-site/site.mk
Symbolic link
1
contrib/ci/minimal-site/site.mk
Symbolic link
@ -0,0 +1 @@
|
||||
../../../docs/site-example/site.mk
|
Loading…
Reference in New Issue
Block a user