Build Windows Templates in RHV
This commit is contained in:
12
build_windows_template.yml
Normal file
12
build_windows_template.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
- name: create an ovirt windows template
|
||||
hosts: windows_template_base
|
||||
gather_facts: False
|
||||
connection: local
|
||||
become: no
|
||||
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ ansible_playbook_python }}"
|
||||
|
||||
|
||||
roles:
|
||||
- oatakan.windows_ovirt_template
|
||||
7
buildvm_artifact.json
Normal file
7
buildvm_artifact.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"plays": [],
|
||||
"stdout": [],
|
||||
"status": "successful",
|
||||
"status_color": 10
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
ack
|
||||
asciidoctor
|
||||
asciinema
|
||||
aspell-dict-ca
|
||||
aspell-dict-en
|
||||
aspell-dict-uk
|
||||
astyle
|
||||
automake
|
||||
avahi
|
||||
cdrtools
|
||||
certbot
|
||||
cracklib
|
||||
curl
|
||||
dash
|
||||
davix
|
||||
db62
|
||||
dbus-python37
|
||||
dupd
|
||||
emacs
|
||||
emacs-mac-app-devel
|
||||
fd
|
||||
ffmpeg
|
||||
gconf
|
||||
gdk-pixbuf2
|
||||
git-delta
|
||||
gmime
|
||||
gnutar
|
||||
go
|
||||
gtk2
|
||||
gtk3
|
||||
html2text
|
||||
icedtea6-plugs
|
||||
ipmitool
|
||||
isync
|
||||
jq
|
||||
kubectl-1.17
|
||||
libglade2
|
||||
libvterm
|
||||
minicom
|
||||
mpvim
|
||||
msmtp
|
||||
mtr
|
||||
mu
|
||||
ncdu
|
||||
nut
|
||||
nvm
|
||||
offlineimap
|
||||
oniguruma6
|
||||
openconnect
|
||||
OpenIPMI
|
||||
openjfx11
|
||||
openssh
|
||||
p5.28-yaml-libyaml
|
||||
pass
|
||||
perl5
|
||||
php-crack
|
||||
plantuml
|
||||
Platypus
|
||||
poppler
|
||||
py-boto3
|
||||
py-libxml2
|
||||
py27-opengl-accelerate
|
||||
py27-pygtk
|
||||
py37-curl
|
||||
py37-msgpack
|
||||
py37-SDL2
|
||||
py38-powerline
|
||||
py38-virtualenvwrapper
|
||||
ranger
|
||||
ripgrep
|
||||
sassc
|
||||
terminal-notifier
|
||||
topgrade
|
||||
virt-viewer
|
||||
xapian-bindings-python27
|
||||
yarn
|
||||
@@ -73,13 +73,27 @@
|
||||
src: "{{ key_files_prefix}}-rootchain.pem"
|
||||
dest: /etc/pki/ca-trust/source/anchors/
|
||||
register: rootchain_result
|
||||
notify: restart httpd
|
||||
notify:
|
||||
- update ca-trust
|
||||
- restart httpd
|
||||
|
||||
- name: Certificate store updated
|
||||
command: /usr/bin/update-ca-trust
|
||||
when: rootchain_result.changed
|
||||
notify: restart httpd
|
||||
|
||||
- name: Apache CA is file, not link
|
||||
file:
|
||||
path: /etc/pki/ovirt-engine/apache-ca.pem
|
||||
state: file
|
||||
register: apache_ca_stat
|
||||
|
||||
- name: Apache CA link is removed
|
||||
file:
|
||||
path: /etc/pki/ovirt-engine/apache-ca.pem
|
||||
state: absent
|
||||
when: apache_ca_stat.state == "file"
|
||||
|
||||
- name: CA Rootchain in Apache config
|
||||
copy:
|
||||
src: "{{ key_files_prefix }}-rootchain.pem"
|
||||
@@ -92,6 +106,9 @@
|
||||
src: "{{ key_files_prefix }}.key"
|
||||
dest: /etc/pki/ovirt-engine/keys/apache.key.nopass
|
||||
backup: yes
|
||||
owner: root
|
||||
group: ovirt
|
||||
mode: 640
|
||||
notify: restart httpd
|
||||
|
||||
- name: Certificate installed
|
||||
@@ -99,6 +116,9 @@
|
||||
src: "{{ key_files_prefix }}.pem"
|
||||
dest: /etc/pki/ovirt-engine/certs/apache.cer
|
||||
backup: yes
|
||||
owner: root
|
||||
group: ovirt
|
||||
mode: 644
|
||||
notify: restart httpd
|
||||
|
||||
- name: Trust Store Configuration
|
||||
@@ -121,8 +141,7 @@
|
||||
- SSL_CERTIFICATE=/etc/pki/ovirt-engine/apache.cer
|
||||
- SSL_KEY=/etc/pki/ovirt-engine/keys/apache.key.nopass
|
||||
notify:
|
||||
- restart ovn
|
||||
- restart ovirt-engine
|
||||
- restart ovirt-websocket-proxy
|
||||
|
||||
handlers:
|
||||
- name: restart httpd
|
||||
@@ -130,6 +149,9 @@
|
||||
name: httpd
|
||||
state: restarted
|
||||
|
||||
- name: update ca-trust
|
||||
command: update-ca-trust
|
||||
|
||||
- name: restart ovn
|
||||
service:
|
||||
name: ovirt-provider-ovn
|
||||
@@ -140,6 +162,11 @@
|
||||
name: ovirt-engine
|
||||
state: restarted
|
||||
|
||||
- name: restart ovirt-websocket-proxy
|
||||
service:
|
||||
name: ovirt-websocket-proxy
|
||||
state: restarted
|
||||
|
||||
|
||||
- name: Create RHV/ovirt VLANs
|
||||
hosts: rhv.mgmt.toal.ca
|
||||
|
||||
1
roles/felixfontein.acme_certificate/.gitignore
vendored
Normal file
1
roles/felixfontein.acme_certificate/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
__pycache__
|
||||
45
roles/felixfontein.acme_certificate/.yamllint
Normal file
45
roles/felixfontein.acme_certificate/.yamllint
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
line-length:
|
||||
max: 140
|
||||
level: warning
|
||||
document-start:
|
||||
present: true
|
||||
document-end:
|
||||
present: false
|
||||
truthy:
|
||||
level: error
|
||||
allowed-values:
|
||||
- 'yes'
|
||||
- 'no'
|
||||
- 'true'
|
||||
- 'false'
|
||||
- 'True'
|
||||
- 'False'
|
||||
indentation:
|
||||
spaces: 2
|
||||
indent-sequences: consistent
|
||||
key-duplicates: enable
|
||||
trailing-spaces: enable
|
||||
new-line-at-end-of-file: disable
|
||||
hyphens:
|
||||
max-spaces-after: 1
|
||||
empty-lines:
|
||||
max: 2
|
||||
max-start: 0
|
||||
max-end: 0
|
||||
commas:
|
||||
max-spaces-before: 0
|
||||
min-spaces-after: 1
|
||||
max-spaces-after: 1
|
||||
colons:
|
||||
max-spaces-before: 0
|
||||
max-spaces-after: 1
|
||||
brackets:
|
||||
min-spaces-inside: 0
|
||||
max-spaces-inside: 0
|
||||
braces:
|
||||
min-spaces-inside: 0
|
||||
max-spaces-inside: 0
|
||||
19
roles/felixfontein.acme_certificate/CHANGELOG.md
Normal file
19
roles/felixfontein.acme_certificate/CHANGELOG.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Changelog for acme_certificate
|
||||
|
||||
## Version 1.1.1 (2020-05-22)
|
||||
|
||||
- Linting, to make Galaxy more happy. (ansible-lint does not like missing modules. This might get better with collections.)
|
||||
|
||||
## Version 1.1.0 (2020-05-22)
|
||||
|
||||
- Added better namespacing for role parameters; all role parameters now start with `acme_certificate_`. The old, shorter names can still be used for now. Support for them will be dropped in version 2.0.0, to be released later this year.
|
||||
- Dropped support for GCDNS (which never worked).
|
||||
- Support for DNS provider NS1 for DNS challenges (thanks to @timelapserduck).
|
||||
- Lint YAML files (thanks to @pgporada).
|
||||
- Allow `key_path` to not have trailing slash (thanks to @nwmcsween).
|
||||
- Fix curve used for P-256.
|
||||
- Require Ansible 2.8.3.
|
||||
|
||||
## Version 1.0 (2019-07-01)
|
||||
|
||||
First version published on Ansible Galaxy.
|
||||
21
roles/felixfontein.acme_certificate/LICENSE
Normal file
21
roles/felixfontein.acme_certificate/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015--2020 Felix Fontein
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
360
roles/felixfontein.acme_certificate/README.md
Normal file
360
roles/felixfontein.acme_certificate/README.md
Normal file
@@ -0,0 +1,360 @@
|
||||
# acme_certificate 1.1.1
|
||||
|
||||
Allows to obtain certificates from Let's Encrypt with minimal interaction with the webserver. Most code is executed on the controller, and the account key is never send to the nodes.
|
||||
|
||||
The role can be installed via [Ansible Galaxy](https://galaxy.ansible.com/felixfontein/acme_certificate):
|
||||
|
||||
ansible-galaxy install felixfontein.acme_certificate
|
||||
|
||||
For changes in this role, see [the changelog](CHANGELOG.md).
|
||||
|
||||
## Description
|
||||
|
||||
This is an [Ansible](https://github.com/ansible/ansible) role which can use any CA supporting the ACME protocol, such as [Let's Encrypt](https://letsencrypt.org/) or [Buypass](https://www.buypass.com/ssl/products/acme), to issue TLS/SSL certificates for your server. This role requires Ansible 2.8.3 or newer and is based on the [acme_certificate module](https://docs.ansible.com/ansible/latest/acme_certificate_module.html) coming with Ansible.
|
||||
|
||||
The main advantage of this approach over others is that *almost no code is executed on your webserver*: only when you use HTTP challenges, files need to be copied onto your webserver, and afterwards deleted from it. Everything else is executed on your local machine!
|
||||
|
||||
(This does not cover installing the certificates, you have to do that yourself in another role.)
|
||||
|
||||
## Requirements
|
||||
|
||||
Requires the Python [cryptography](https://pypi.org/project/cryptography/) library installed on the controller, available to the Python version used to execute the playbook. If `cryptography` is not installed, a recent enough version of [PyOpenSSL](https://pypi.org/project/pyOpenSSL/) is currently supported as a fallback by the Ansible `openssl_privatekey` and `openssl_csr` modules.
|
||||
|
||||
The `openssl` binary must also be available in the executable path on the controller. It is needed by the `acme_certificate` module in case `cryptography` is not installed, and it is used for certificate chain validation.
|
||||
|
||||
If DNS challenges are used, there can be other requirements depending on the DNS provider. For example, for Amazon's Route 53, the Ansible `route53` module requires the Python `boto` package.
|
||||
|
||||
## Account Key Setup
|
||||
|
||||
You can create an account key using the `openssl` binary as follows:
|
||||
|
||||
# RSA 4096 bit key
|
||||
openssl genrsa 4096 -out keys/acme-account.key
|
||||
# ECC 256 bit key (P-256)
|
||||
openssl ecparam -name prime256v1 -genkey -out keys/acme-account.key
|
||||
# ECC 384 bit key (P-384)
|
||||
openssl ecparam -name secp384r1 -genkey -out keys/acme-account.key
|
||||
|
||||
With Ansible, you can use the `openssl_privatekey` module as follows:
|
||||
|
||||
- name: Generate RSA 4096 key
|
||||
openssl_privatekey:
|
||||
path: keys/acme-account.key
|
||||
type: RSA
|
||||
size: 4096
|
||||
- name: Generate ECC 256 bit key (P-256)
|
||||
openssl_privatekey:
|
||||
path: keys/acme-account.key
|
||||
type: ECC
|
||||
curve: secp256r1
|
||||
- name: Generate ECC 384 bit key (P-384)
|
||||
openssl_privatekey:
|
||||
path: keys/acme-account.key
|
||||
type: ECC
|
||||
curve: secp384r1
|
||||
|
||||
Make sure you store the account key safely. As opposed to certificate private keys, there is no need to regenerate it frequently, and it makes recovation of certificates issued with it very simple.
|
||||
|
||||
## Role Variables
|
||||
|
||||
Please note that from May 2020 on, all variables must be prefixed with `acme_certificate_`. For some time, the module will still use the old (short) variable names if the longer ones are not defined. Please upgrade your role usage as soon as possible.
|
||||
|
||||
These are the main variables:
|
||||
|
||||
- `acme_certificate_acme_account`: Path to the private ACME account key. Must always be specified.
|
||||
- `acme_certificate_acme_email`: Your email address which shall be associated to the ACME account. Must always be specified.
|
||||
- `acme_certificate_algorithm`: The algorithm used for creating private keys. The default is `"rsa"`; other choices are `"p-256"`, `"p-384"` or `"p-521"` for the NIST elliptic curves `prime256v1`, `secp384r1` and `secp521r1`, respectively.
|
||||
- `acme_certificate_key_length`: The bitlength to use for RSA private keys. The default is 4096.
|
||||
- `acme_certificate_key_name`: The basename for storing the keys and certificates. The default is the first domain specified, with `*` replaced by `_`.
|
||||
- `acme_certificate_keys_path`: Where the keys and certificates are stored. Default value is `"keys/"`.
|
||||
- `acme_certificate_keys_old_path`: Where old keys and certificates should be copied to; used in case `acme_certificate_keys_old_store` is true. Default value is `"keys/old/"`.
|
||||
- `acme_certificate_keys_old_store`: If set to `true`, will make copies of old keys and certificates. The copies will be stored in the directory specified by `acme_certificate_keys_old_store`. Default value is `false`.
|
||||
- `acme_certificate_keys_old_prepend_timestamp`: Whether copies of old keys and certificates should be prepended by the current date and time. Default value is `false`.
|
||||
- `acme_certificate_ocsp_must_staple`: Whether a certificate with the OCSP Must Staple extension is requested. Default value is `false`.
|
||||
- `acme_certificate_agreement`: The terms of service document the user agrees to. Default value is `https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf`.
|
||||
- `acme_certificate_acme_directory`: The ACME directory to use. Default is `https://acme-v02.api.letsencrypt.org/directory`, which is the current production ACME v2 endpoint of Let's Encrypt.
|
||||
- `acme_certificate_acme_version`: The ACME directory's version. Default is 2. Use 1 for ACME v1.
|
||||
- `acme_certificate_challenge`: The challenge type to use. Should be `http-01` for HTTP challenges (needs access to web server) or `dns-01` for DNS challenges (needs access to DNS provider).
|
||||
- `acme_certificate_root_certificate`: The root certificate for the ACME directory. Default value is `https://letsencrypt.org/certs/isrgrootx1.pem` for the root certificate of Let's Encrypt.
|
||||
- `acme_certificate_deactivate_authzs`: Whether `authz`s (authorizations) should be deactivated afterwards. Default value is `true`. Set to `false` to be able to re-use `authz`s.
|
||||
- `acme_certificate_modify_account`: Whether the ACME account should be created (if it doesn't exist) and the contact data (email address) should be updated. Default value is `true`. Set to `false` if you want to use the `acme_account` module to manage your ACME account (not done by this role).
|
||||
- `acme_certificate_privatekey_mode`: Which file mode to use for the private key file. Default value is `"0600"`, which means read- and writeable by the owner, but not accessible by anyone else (except possibly `root`).
|
||||
|
||||
### HTTP Challenges
|
||||
|
||||
For HTTP challenges, the following variables define how the challenges can be put onto the (remote) webserver:
|
||||
|
||||
- `acme_certificate_server_location`: Location where `.well-known/acme-challenge/` will be served from. Default is `/var/www/challenges`.
|
||||
- `acme_certificate_http_become`: Argument for `become:` for the `file` and `copy` tasks. Default value is `false`.
|
||||
- `acme_certificate_http_challenge_user`: The user the challenge files are owned by. Default value is `root`.
|
||||
- `acme_certificate_http_challenge_group`: The group the challenge files are owned by. Default value is `http`.
|
||||
- `acme_certificate_http_challenge_folder_mode`: The mode to use for the challenge folder. Default value is `0750` (octal).
|
||||
- `acme_certificate_http_challenge_file_mode`: The mode to use for the challenge files. Default value is `0640` (octal).
|
||||
|
||||
The following subsection shows how to configure [nginx](https://nginx.org/) for HTTP challenges. Configuring other webservers can be done in a similar way.
|
||||
|
||||
#### Nginx configuration
|
||||
|
||||
Assume that for one of your TLS/SSL protected domains, you use a HTTP-to-HTTPS redirect. Let's assume it looks like this:
|
||||
|
||||
server {
|
||||
listen example.com:80;
|
||||
server_name example.com *.example.com;
|
||||
return 301 https://www.example.com$request_uri;
|
||||
}
|
||||
|
||||
To allow the `acme_certificate` role to put something at `http://*.example.com/.well-known/acme-challenge/`, you can change this to:
|
||||
|
||||
server {
|
||||
listen example.com:80;
|
||||
server_name example.com *.example.com;
|
||||
location /.well-known/acme-challenge/ {
|
||||
alias /var/www/challenges/;
|
||||
try_files $uri =404;
|
||||
}
|
||||
location / {
|
||||
return 301 https://www.example.com$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
With this nginx config, all other URLs on `*.example.com` and `example.com` are still redirected, while everything in `*.example.com/.well-known/acme-challenge/` is served from `/var/www/challenges`. When adjusting the location of `/var/www/challenges`, you must also change `acme_certificate_server_location`.
|
||||
|
||||
You can even improve on this by redirecting all URLs in `*.example.com/.well-known/acme-challenge/` which do not resolve to a valid file in `/var/www/challenges` to your HTTPS server as well. One way to do this is:
|
||||
|
||||
server {
|
||||
listen example.com:80;
|
||||
server_name example.com *.example.com;
|
||||
location /.well-known/acme-challenge/ {
|
||||
alias /var/www/lechallenges/;
|
||||
try_files $uri @forward_https;
|
||||
}
|
||||
location @forward_https {
|
||||
return 301 https://www.example.com$request_uri;
|
||||
}
|
||||
location / {
|
||||
return 301 https://www.example.com$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
With this config, if `/var/www/challenges/` is empty, your HTTP server will behave as if the `/.well-known/acme-challenge/` location isn't specified.
|
||||
|
||||
### DNS Challenges
|
||||
|
||||
If DNS challenges are used, the following variables define how the challenges can be fulfilled:
|
||||
|
||||
- `acme_certificate_dns_provider`: must be one of `route53`, `hosttech`, and `ns1`. Each needs more information:
|
||||
- For `route53` (Amazon Route 53), the credentials must be passed as `acme_certificate_aws_access_key` and `acme_certificate_aws_secret_key`.
|
||||
- For `hosttech` (hosttech GmbH, requires external [hosttech_dns_record module](https://github.com/felixfontein/ansible-hosttech)).
|
||||
- For `ns1` ([ns1.com](https://ns1.com)) the key for your API account must be passed as `acme_certificate_ns1_secret_key`. Also it depends on external module `ns1_record`. Assuming default directory structure and settings, you may need download 2 files into machine where playbook executed:
|
||||
|
||||
```bash
|
||||
curl --create-dirs -L -o ~/.ansible/plugins/module_utils/ns1.py https://github.com/ns1/ns1-ansible-modules/raw/master/module_utils/ns1.py
|
||||
curl --create-dirs -L -o ~/.ansible/plugins/modules/ns1_record.py https://github.com/ns1/ns1-ansible-modules/raw/master/library/ns1_record.py
|
||||
```
|
||||
|
||||
Please note that the DNS challenge code is not perfect. The Route 53, Hosttech and NS1 functionality has been tested. One thing that is not complete yet is that the code tries to extract the DNS zone from the domain by taking the last two components separated by dots. This will fail for example for `.co.uk` domains or other nested zones.
|
||||
|
||||
Support for more DNS providers can be added by adding `tasks/dns-NAME-create.yml` and `tasks/dns-NAME-cleanup.yml` files with similar content as in the existing files.
|
||||
|
||||
## Account key conversion
|
||||
|
||||
Note that this Ansible role expects the Let's Encrypt account key to be in PEM format and not in JWK format, which is used by the [official Let's Encrypt client certbot](https://github.com/certbot/certbot). If you have created an account key with the official client and now want to use this key with this ansible role, you have to convert it. One tool which can do this is [pem-jwk](https://github.com/dannycoates/pem-jwk).
|
||||
|
||||
## Generated Files
|
||||
|
||||
Let's assume you created TLS keys for `www.example.com`. You have to copy the relevant files to your webserver. The ansible role created the following files:
|
||||
|
||||
* `keys/www.example.com.key`: this is the private key for the certificate. Ensure nobody can access it.
|
||||
* `keys/www.example.com.pem`: this is the certificate itself.
|
||||
* `keys/www.example.com-chain.pem`: this is the intermediate certificate(s) needed for a trust path.
|
||||
* `keys/www.example.com.cnf`: this is an OpenSSL configuration file used to create the Certificate Signing Request. You can safely delete it.
|
||||
* `keys/www.example.com.csr`: this is the Certificate Signing Request used to obtain the certificate. You can safely delete it.
|
||||
* `keys/www.example.com-fullchain.pem`: this is the certificate combined with the intermediate certificate(s).
|
||||
* `keys/www.example.com-rootchain.pem`: this is the intermediate certificate(s) combined with the root certificate. You might need this for OCSP stapling.
|
||||
* `keys/www.example.com-root.pem`: this is the root certificate of Let's Encrypt.
|
||||
|
||||
For configuring your webserver, you need the private key (`keys/www.example.com.key`), and either the certificate with intermediate certificate(s) combined in one file (`keys/www.example.com-fullchain.pem`), or the certificate and the intermediate certificate(s) as two separate files (`keys/www.example.com.pem` and `keys/www.example.com-chain.pem`). If you want to use [OCSP stapling](https://en.wikipedia.org/wiki/OCSP_stapling), you will also need `keys/www.example.com-rootchain.pem`.
|
||||
|
||||
To get these files onto your web server, you could add tasks as follows:
|
||||
|
||||
- name: copy private keys
|
||||
copy:
|
||||
src: keys/{{ item }}
|
||||
dest: /etc/ssl/private/
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0400"
|
||||
with_items:
|
||||
- www.example.com.key
|
||||
notify: reload webserver
|
||||
|
||||
- name: copy certificates
|
||||
copy:
|
||||
src: keys/{{ item }}
|
||||
dest: /etc/ssl/server-certs/
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0444"
|
||||
with_items:
|
||||
- www.example.com-rootchain.pem
|
||||
- www.example.com-fullchain.pem
|
||||
- www.example.com.pem
|
||||
notify: reload webserver
|
||||
|
||||
The webserver configuration could look as follows (for nginx):
|
||||
|
||||
server {
|
||||
listen www.example.com:443 ssl; # IPv4: listen to IP www.example.com points to
|
||||
listen [::]:443 ssl; # IPv6: listen to localhost
|
||||
server_name www.example.com;
|
||||
|
||||
# Allowing only TLS 1.0 and 1.2, with a very selective amount of ciphers.
|
||||
# According to SSL Lab's SSL server test, this will block:
|
||||
# - Android 2.3.7
|
||||
# - IE 6 and 8 under Windows XP
|
||||
# - Java 6, 7 and 8
|
||||
# If that's not acceptable for you, choose other cipher lists. Look for
|
||||
# example at https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
ssl_protocols TLSv1.2 TLSv1;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers "-ALL !ADH !aNULL !EXP !EXPORT40 !EXPORT56 !RC4 !3DES !eNULL !NULL !DES !MD5 !LOW ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES256-SHA256 ECDHE-ECDSA-AES256-SHA ECDHE-RSA-AES256-SHA DHE-RSA-AES256-SHA";
|
||||
|
||||
# The certificate chain sent to the browser, as well as the private key.
|
||||
# Make sure your private key is only accessible by the webserver during
|
||||
# configuration loading (which by default is done with user root).
|
||||
ssl_certificate /etc/ssl/server-certs/www.example.com-fullchain.pem;
|
||||
ssl_certificate_key /etc/ssl/private/www.example.com.key;
|
||||
|
||||
# For OCSP stapling, we need a DNS resolver. Here only public Quad9 and
|
||||
# Google DNS servers are specified; I would prepent them by your hoster's
|
||||
# DNS servers. You can usually find their IPs in /etc/resolv.conf on your
|
||||
# webserver.
|
||||
resolver 9.9.9.9 8.8.8.8 8.8.4.4 valid=300s;
|
||||
resolver_timeout 10s;
|
||||
|
||||
# Enabling OCSP stapling. Nginx will take care of retrieving the OCSP data
|
||||
# automatically. See https://wiki.mozilla.org/Security/Server_Side_TLS#OCSP_Stapling
|
||||
# for details on OCSP stapling.
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
ssl_trusted_certificate /etc/ssl/server-certs/www.example.com-rootchain.pem;
|
||||
|
||||
# Enables a SSL session cache. Adjust the numbers depending on your site's usage.
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_session_timeout 30m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
# You should only use HSTS with proper certificates; the ones from Let's Encrypt
|
||||
# are fine for this, self-signed ones are not. See MozillaWiki for more details:
|
||||
# https://wiki.mozilla.org/Security/Server_Side_TLS#HSTS:_HTTP_Strict_Transport_Security
|
||||
add_header Strict-Transport-Security "max-age=3155760000;";
|
||||
|
||||
charset utf-8;
|
||||
|
||||
access_log /var/log/nginx/www.example.com.log combined;
|
||||
error_log /var/log/nginx/www.example.com.log error;
|
||||
|
||||
location / {
|
||||
root /var/www/www.example.com;
|
||||
index index.html;
|
||||
}
|
||||
}
|
||||
|
||||
## Dependencies
|
||||
|
||||
This role doesn't depend on other roles.
|
||||
|
||||
## Example Playbook
|
||||
|
||||
This role can be used as follows. Note that it obtains several certificates, and defines variables used for all certificates globally:
|
||||
|
||||
---
|
||||
- name: getting certificates for webserver
|
||||
hosts: webserver
|
||||
vars:
|
||||
acme_certificate_acme_account: 'keys/acme-account.key'
|
||||
acme_certificate_acme_email: 'mail@example.com'
|
||||
# For HTTP challenges:
|
||||
acme_certificate_server_location: '/var/www/challenges/'
|
||||
acme_certificate_http_challenge_user: root
|
||||
acme_certificate_http_challenge_group: http
|
||||
acme_certificate_http_challenge_folder_mode: "0750"
|
||||
acme_certificate_http_challenge_file_mode: "0640"
|
||||
# For DNS challenges with route53:
|
||||
acme_certificate_dns_provider: route53
|
||||
acme_certificate_aws_access_key: REPLACE_WITH_YOUR_ACCESS_KEY
|
||||
acme_certificate_aws_secret_key: REPLACE_WITH_YOUR_SECRET_KEY
|
||||
# For DNS challenges with ns1:
|
||||
# acme_certificate_dns_provider: ns1
|
||||
# acme_certificate_ns1_secret_key: REPLACE_WITH_YOUR_SECRET_KEY
|
||||
roles:
|
||||
- role: acme_certificate
|
||||
acme_certificate_domains: ['example.com', 'www.example.com']
|
||||
# Use DNS challenges:
|
||||
acme_certificate_challenge: dns-01
|
||||
# The certificate files will be stored at:
|
||||
# keys/example.com.key (private key)
|
||||
# keys/example.com.csr (certificate signing request)
|
||||
# keys/example.com.pem (certificate)
|
||||
# keys/example.com.cnf (OpenSSL config for CSR creation -- can be safely deleted)
|
||||
# keys/example.com-chain.pem (intermediate certificate)
|
||||
# keys/example.com-fullchain.pem (certificate with intermediate certificate)
|
||||
# keys/example.com-root.pem (root certificate)
|
||||
# keys/example.com-rootchain.pem (intermediate certificate with root certificate)
|
||||
- role: acme_certificate
|
||||
acme_certificate_domains: ['another.example.com']
|
||||
acme_certificate_key_name: 'another.example.com-rsa'
|
||||
acme_certificate_key_length: 4096
|
||||
# Use HTTP challenges:
|
||||
acme_certificate_challenge: http-01
|
||||
# The certificate files will be stored at:
|
||||
# keys/another.example.com-rsa.key (private key)
|
||||
# keys/another.example.com-rsa.csr (certificate signing request)
|
||||
# keys/another.example.com-rsa.pem (certificate)
|
||||
# keys/another.example.com-rsa.cnf (OpenSSL config for CSR creation -- can be safely deleted)
|
||||
# keys/another.example.com-rsa-chain.pem (intermediate certificate)
|
||||
# keys/another.example.com-rsa-fullchain.pem (certificate with intermediate certificate)
|
||||
# keys/another.example.com-rsa-root.pem (root certificate)
|
||||
# keys/another.example.com-rsa-rootchain.pem (intermediate certificate with root certificate)
|
||||
- role: acme_certificate
|
||||
acme_certificate_domains: ['another.example.com']
|
||||
acme_certificate_key_name: 'another.example.com-ecc'
|
||||
acme_certificate_algorithm: 'p-256'
|
||||
# Use HTTP challenges (default for challenge is http-01).
|
||||
# The certificate files will be stored at:
|
||||
# keys/another.example.com-ecc.key (private key)
|
||||
# keys/another.example.com-ecc.csr (certificate signing request)
|
||||
# keys/another.example.com-ecc.pem (certificate)
|
||||
# keys/another.example.com-ecc.cnf (OpenSSL config for CSR creation -- can be safely deleted)
|
||||
# keys/another.example.com-ecc-chain.pem (intermediate certificate)
|
||||
# keys/another.example.com-ecc-fullchain.pem (certificate with intermediate certificate)
|
||||
# keys/another.example.com-ecc-root.pem (root certificate)
|
||||
# keys/another.example.com-ecc-rootchain.pem (intermediate certificate with root certificate)
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2020 Felix Fontein
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## Author Information
|
||||
|
||||
The homepage for this role is https://github.com/felixfontein/acme-certificate/. Please use the issue tracker to report problems.
|
||||
2
roles/felixfontein.acme_certificate/ansible.cfg
Normal file
2
roles/felixfontein.acme_certificate/ansible.cfg
Normal file
@@ -0,0 +1,2 @@
|
||||
[defaults]
|
||||
roles_path = ../
|
||||
47
roles/felixfontein.acme_certificate/defaults/main.yml
Normal file
47
roles/felixfontein.acme_certificate/defaults/main.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
acme_certificate_domains: '{{ domains }}'
|
||||
acme_certificate_dns_provider: '{{ dns_provider }}'
|
||||
acme_certificate_acme_account: '{{ acme_account }}'
|
||||
acme_certificate_acme_email: '{{ acme_email }}'
|
||||
|
||||
acme_certificate_algorithm: '{{ algorithm | default("rsa") }}'
|
||||
acme_certificate_key_length: '{{ key_length | default(4096) }}'
|
||||
acme_certificate_key_name: "{{ key_name | default(acme_certificate_domains[0].replace('*', '_')) }}"
|
||||
acme_certificate_keys_path: '{{ keys_path | default("keys/") }}'
|
||||
acme_certificate_keys_old_path: '{{ keys_old_path | default("keys/old/") }}'
|
||||
acme_certificate_keys_old_store: '{{ keys_old_store | default(false) }}'
|
||||
acme_certificate_keys_old_prepend_timestamp: '{{ keys_old_prepend_timestamp | default(false) }}'
|
||||
acme_certificate_ocsp_must_staple: '{{ ocsp_must_staple | default(false) }}'
|
||||
acme_certificate_terms_agreed: '{{ terms_agreed | default(true) }}'
|
||||
acme_certificate_acme_directory: '{{ acme_directory | default("https://acme-v02.api.letsencrypt.org/directory") }}'
|
||||
acme_certificate_acme_version: '{{ acme_version | default(2) }}'
|
||||
# For ACME v1:
|
||||
# acme_certificate_acme_directory: https://acme-v01.api.letsencrypt.org/directory
|
||||
# acme_certificate_acme_version: 1
|
||||
# For staging, use:
|
||||
# acme_certificate_acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory (ACME v2)
|
||||
# acme_certificate_acme_directory: https://acme-staging.api.letsencrypt.org/directory (ACME v1)
|
||||
acme_certificate_challenge: '{{ challenge | default("http-01") }}'
|
||||
acme_certificate_root_certificate: '{{ root_certificate | default("https://letsencrypt.org/certs/isrgrootx1.pem") }}'
|
||||
# For staging, use:
|
||||
# root_certificate: https://letsencrypt.org/certs/fakelerootx1.pem
|
||||
acme_certificate_deactivate_authzs: '{{ deactivate_authzs | default(true) }}'
|
||||
acme_certificate_modify_account: '{{ modify_account | default(true) }}'
|
||||
acme_certificate_validate_certs: '{{ validate_certs | default(true) }}'
|
||||
acme_certificate_verify_certs: '{{ verify_certs | default(true) }}'
|
||||
acme_certificate_privatekey_mode: '{{ privatekey_mode | default("0600") }}'
|
||||
|
||||
# For HTTP challenges:
|
||||
acme_certificate_server_location: '{{ server_location | default("/var/www/challenges") }}'
|
||||
acme_certificate_http_become: '{{ http_become | default(false) }}'
|
||||
acme_certificate_http_challenge_user: '{{ http_challenge_user | default("root") }}'
|
||||
acme_certificate_http_challenge_group: '{{ http_challenge_group | default("http") }}'
|
||||
acme_certificate_http_challenge_folder_mode: '{{ http_challenge_folder_mode | default("0750") }}'
|
||||
acme_certificate_http_challenge_file_mode: '{{ http_challenge_file_mode | default("0640") }}'
|
||||
|
||||
# DNS challenge credentials
|
||||
acme_certificate_hosttech_username: '{{ hosttech_username | default(omit) }}'
|
||||
acme_certificate_hosttech_password: '{{ hosttech_password | default(omit) }}'
|
||||
acme_certificate_ns1_secret_key: '{{ ns1_secret_key | default(omit) }}'
|
||||
acme_certificate_aws_access_key: '{{ aws_access_key | default(omit) }}'
|
||||
acme_certificate_aws_secret_key: '{{ aws_secret_key | default(omit) }}'
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import os.path
|
||||
|
||||
|
||||
def path_join(list):
|
||||
return os.path.join(*list)
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
''' Ansible core jinja2 filters '''
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'path_join': path_join,
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
install_date: Mon Apr 19 15:24:27 2021
|
||||
version: 1.1.1
|
||||
25
roles/felixfontein.acme_certificate/meta/main.yml
Normal file
25
roles/felixfontein.acme_certificate/meta/main.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
galaxy_info:
|
||||
role_name: acme_certificate
|
||||
author: Felix Fontein
|
||||
description: >
|
||||
Wrapper of Ansible's included acme_certificate module, whose aim is that almost no code
|
||||
is executed on the webserver. Requires the Python cryptography library as well as the
|
||||
OpenSSL binary installed locally and available on executable path.
|
||||
|
||||
license: MIT
|
||||
|
||||
min_ansible_version: 2.8.3
|
||||
|
||||
galaxy_tags:
|
||||
- acme
|
||||
- letsencrypt
|
||||
- buypass
|
||||
- ssl
|
||||
- tls
|
||||
- https
|
||||
- encryption
|
||||
- security
|
||||
- web
|
||||
|
||||
dependencies: []
|
||||
62
roles/felixfontein.acme_certificate/sample-playbook.yml
Normal file
62
roles/felixfontein.acme_certificate/sample-playbook.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
- name: getting certificates for webserver
|
||||
hosts: webserver
|
||||
vars:
|
||||
acme_certificate_acme_account: 'keys/acme-account.key'
|
||||
acme_certificate_acme_email: 'mail@example.com'
|
||||
# For HTTP challenges:
|
||||
acme_certificate_server_location: '/var/www/challenges/'
|
||||
acme_certificate_http_challenge_user: root
|
||||
acme_certificate_http_challenge_group: http
|
||||
acme_certificate_http_challenge_folder_mode: "0750"
|
||||
acme_certificate_http_challenge_file_mode: "0640"
|
||||
# For DNS challenges with route53:
|
||||
acme_certificate_dns_provider: route53
|
||||
acme_certificate_aws_access_key: REPLACE_WITH_YOUR_ACCESS_KEY
|
||||
acme_certificate_aws_secret_key: REPLACE_WITH_YOUR_SECRET_KEY
|
||||
# For DNS challenges with ns1:
|
||||
# acme_certificate_dns_provider: ns1
|
||||
# acme_certificate_ns1_secret_key: REPLACE_WITH_YOUR_SECRET_KEY
|
||||
roles:
|
||||
- role: acme_certificate
|
||||
acme_certificate_domains: ['example.com', 'www.example.com']
|
||||
# Use DNS challenges:
|
||||
acme_certificate_challenge: dns-01
|
||||
# The certificate files will be stored at:
|
||||
# keys/example.com.key (private key)
|
||||
# keys/example.com.csr (certificate signing request)
|
||||
# keys/example.com.pem (certificate)
|
||||
# keys/example.com.cnf (OpenSSL config for CSR creation -- can be safely deleted)
|
||||
# keys/example.com-chain.pem (intermediate certificate)
|
||||
# keys/example.com-fullchain.pem (certificate with intermediate certificate)
|
||||
# keys/example.com-root.pem (root certificate)
|
||||
# keys/example.com-rootchain.pem (intermediate certificate with root certificate)
|
||||
- role: acme_certificate
|
||||
acme_certificate_domains: ['another.example.com']
|
||||
acme_certificate_key_name: 'another.example.com-rsa'
|
||||
acme_certificate_key_length: 4096
|
||||
# Use HTTP challenges:
|
||||
acme_certificate_challenge: http-01
|
||||
# The certificate files will be stored at:
|
||||
# keys/another.example.com-rsa.key (private key)
|
||||
# keys/another.example.com-rsa.csr (certificate signing request)
|
||||
# keys/another.example.com-rsa.pem (certificate)
|
||||
# keys/another.example.com-rsa.cnf (OpenSSL config for CSR creation -- can be safely deleted)
|
||||
# keys/another.example.com-rsa-chain.pem (intermediate certificate)
|
||||
# keys/another.example.com-rsa-fullchain.pem (certificate with intermediate certificate)
|
||||
# keys/another.example.com-rsa-root.pem (root certificate)
|
||||
# keys/another.example.com-rsa-rootchain.pem (intermediate certificate with root certificate)
|
||||
- role: acme_certificate
|
||||
acme_certificate_domains: ['another.example.com']
|
||||
acme_certificate_key_name: 'another.example.com-ecc'
|
||||
acme_certificate_algorithm: 'p-256'
|
||||
# Use HTTP challenges (default for challenge is http-01).
|
||||
# The certificate files will be stored at:
|
||||
# keys/another.example.com-ecc.key (private key)
|
||||
# keys/another.example.com-ecc.csr (certificate signing request)
|
||||
# keys/another.example.com-ecc.pem (certificate)
|
||||
# keys/another.example.com-ecc.cnf (OpenSSL config for CSR creation -- can be safely deleted)
|
||||
# keys/another.example.com-ecc-chain.pem (intermediate certificate)
|
||||
# keys/another.example.com-ecc-fullchain.pem (certificate with intermediate certificate)
|
||||
# keys/another.example.com-ecc-root.pem (root certificate)
|
||||
# keys/another.example.com-ecc-rootchain.pem (intermediate certificate with root certificate)
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
# Clean up DNS challenges for DNS provider DNSMadeEasy
|
||||
- name: Cleaning up challenge DNS entries for domains {{ ', '.join(domains) }} via DNSMadeEasy
|
||||
connection: local
|
||||
community.general.dnsmadeeasy:
|
||||
account_key: "{{ dme_account_key }}"
|
||||
account_secret: "{{ dme_account_secret }}"
|
||||
domain: "{{ item.key |regex_replace('^(?:.*\\.|)([^.]+\\.[^.]+)$', '\\1') }}"
|
||||
record_ttl: 60
|
||||
record_type: TXT
|
||||
record_name: "{{ item.key |regex_replace('^(.*)(\\.[^.]+\\.[^.]+)$', '\\1') }}"
|
||||
record_value: "{{ item.value|first }}"
|
||||
state: absent
|
||||
run_once: True
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.challenge_data_dns }}"
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
32
roles/felixfontein.acme_certificate/tasks/dns-dme-create.yml
Normal file
32
roles/felixfontein.acme_certificate/tasks/dns-dme-create.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# Create DNS challenges for DNS provider Amazon Route53
|
||||
- name: Creating challenge DNS entries for domains {{ ', '.join(domains) }} via DNSMadeEasy
|
||||
connection: local
|
||||
community.general.dnsmadeeasy:
|
||||
account_key: "{{ dme_account_key }}"
|
||||
account_secret: "{{ dme_account_secret }}"
|
||||
# This is fragile, and will only work for 2-level domain (eg: corp.com, NOT corp.co.uk )
|
||||
domain: "{{ item.key | regex_replace('^(?:.*\\.|)([^.]+\\.[^.]+)$', '\\1') }}"
|
||||
record_ttl: 60
|
||||
record_type: TXT
|
||||
record_name: "{{ item.key |regex_replace('^(.*)(\\.[^.]+\\.[^.]+)$', '\\1') }}"
|
||||
record_value: "{{ item.value|first }}"
|
||||
state: present
|
||||
# Need dnsmadeeasy module fixed (https://github.com/ansible/ansible/issues/58305)
|
||||
run_once: True
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.challenge_data_dns }}"
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
|
||||
- name: Wait for DNS entries to become available
|
||||
shell: "dig txt {{ item.key }} +short @8.8.8.8"
|
||||
register: dig_result
|
||||
until: "item.value|first in dig_result.stdout"
|
||||
retries: 60
|
||||
delay: 5
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.challenge_data_dns }}"
|
||||
|
||||
- name: Pause for 60s for more propagation
|
||||
pause:
|
||||
minutes: 1
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
# Clean up DNS challenges for DNS provider HostTech
|
||||
- name: Cleaning up challenge DNS entries for domains {{ ', '.join(acme_certificate_domains) }} via HostTech API
|
||||
hosttech_dns_record:
|
||||
state: absent
|
||||
zone: "{{ item.key | regex_replace('^(?:.*\\.|)([^.]+\\.[^.]+)$', '\\1') }}"
|
||||
record: "{{ item.key }}"
|
||||
type: TXT
|
||||
ttl: 300
|
||||
value: "{{ item.value }}"
|
||||
overwrite: true
|
||||
hosttech_username: "{{ acme_certificate_hosttech_username }}"
|
||||
hosttech_password: "{{ acme_certificate_hosttech_password }}"
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.get('challenge_data_dns', {}) }}"
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# Create DNS challenges for DNS provider HostTech
|
||||
- name: Creating challenge DNS entries for domains {{ ', '.join(acme_certificate_domains) }} via HostTech API
|
||||
hosttech_dns_record:
|
||||
state: present
|
||||
zone: "{{ item.key | regex_replace('^(?:.*\\.|)([^.]+\\.[^.]+)$', '\\1') }}"
|
||||
record: "{{ item.key }}"
|
||||
type: TXT
|
||||
ttl: 300
|
||||
value: "{{ item.value }}"
|
||||
overwrite: true
|
||||
hosttech_username: "{{ acme_certificate_hosttech_username }}"
|
||||
hosttech_password: "{{ acme_certificate_hosttech_password }}"
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.challenge_data_dns }}"
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
|
||||
- name: Wait for DNS entries to propagate
|
||||
pause:
|
||||
seconds: 10
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
- name: Cleaning up challenge DNS entries for domains {{ ', '.join(acme_certificate_domains) }} via NS1 API
|
||||
ns1_record:
|
||||
apiKey: "{{ acme_certificate_ns1_secret_key }}"
|
||||
name: "{{ item.key }}"
|
||||
zone: "{{ item.key | regex_replace('^(?:.*\\.|)([^.]+\\.[^.]+)$', '\\1') }}"
|
||||
state: absent
|
||||
type: TXT
|
||||
answers: []
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
when: "'_acme-challenge' in item.key"
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.get('challenge_data_dns', {}) }}"
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
27
roles/felixfontein.acme_certificate/tasks/dns-ns1-create.yml
Normal file
27
roles/felixfontein.acme_certificate/tasks/dns-ns1-create.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
- name: Creating challenge DNS entries for domains {{ ', '.join(acme_certificate_domains) }} via NS1 DNS
|
||||
ns1_record:
|
||||
apiKey: "{{ acme_certificate_ns1_secret_key }}"
|
||||
name: "{{ item.key }}"
|
||||
zone: "{{ item.key | regex_replace('^(?:.*\\.|)([^.]+\\.[^.]+)$', '\\1') }}"
|
||||
state: present
|
||||
type: TXT
|
||||
answers:
|
||||
- answer:
|
||||
- "{{ item.value[0] }}"
|
||||
meta:
|
||||
up: true
|
||||
delegate_to: localhost
|
||||
when: "'_acme-challenge' in item.key"
|
||||
run_once: true
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.challenge_data_dns }}"
|
||||
|
||||
- name: Check if DNS changes propagated at dns1.p01.nsone.net with 10-seconds intervals
|
||||
command: "dig TXT {{ item.key }} +short @dns1.p01.nsone.net"
|
||||
register: dig
|
||||
until: "item.value[0] in dig.stdout"
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.challenge_data_dns }}"
|
||||
retries: 6
|
||||
delay: 10
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
# Clean up DNS challenges for DNS provider Amazon Route53
|
||||
- name: Cleaning up challenge DNS entries for domains {{ ', '.join(acme_certificate_domains) }} via Route53
|
||||
route53:
|
||||
state: absent
|
||||
zone: "{{ item.key | regex_replace('^(?:.*\\.|)([^.]+\\.[^.]+)$', '\\1') }}"
|
||||
record: "{{ item.key }}"
|
||||
type: TXT
|
||||
ttl: 60
|
||||
value: "{{ item.value | map('regex_replace', '^(.*)$', '\"\\1\"' ) | list }}"
|
||||
overwrite: true
|
||||
aws_access_key: "{{ acme_certificate_aws_access_key }}"
|
||||
aws_secret_key: "{{ acme_certificate_aws_secret_key }}"
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.get('challenge_data_dns', {}) }}"
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
# Create DNS challenges for DNS provider Amazon Route53
|
||||
- name: Creating challenge DNS entries for domains {{ ', '.join(acme_certificate_domains) }} via Route53
|
||||
route53:
|
||||
state: present
|
||||
zone: "{{ item.key | regex_replace('^(?:.*\\.|)([^.]+\\.[^.]+)$', '\\1') }}"
|
||||
record: "{{ item.key }}"
|
||||
type: TXT
|
||||
ttl: 60
|
||||
value: "{{ item.value | map('regex_replace', '^(.*)$', '\"\\1\"' ) | list }}"
|
||||
overwrite: true
|
||||
aws_access_key: "{{ acme_certificate_aws_access_key }}"
|
||||
aws_secret_key: "{{ acme_certificate_aws_secret_key }}"
|
||||
wait: true
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.challenge_data_dns }}"
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
15
roles/felixfontein.acme_certificate/tasks/http-cleanup.yml
Normal file
15
roles/felixfontein.acme_certificate/tasks/http-cleanup.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
# Clean up challenge files on server.
|
||||
- name: "Cleaning up challenge files for domains {{ ', '.join(acme_certificate_domains) }}"
|
||||
file:
|
||||
path: >-
|
||||
{{ [
|
||||
acme_certificate_server_location,
|
||||
item.value[acme_certificate_challenge].resource[('.well-known/acme-challenge/'|length):]
|
||||
] | path_join }}"
|
||||
state: absent
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.get('acme_certificate_challenge_data', {}) }}"
|
||||
become: "{{ acme_certificate_http_become }}"
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
31
roles/felixfontein.acme_certificate/tasks/http-create.yml
Normal file
31
roles/felixfontein.acme_certificate/tasks/http-create.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
# Create up challenge files directory on server.
|
||||
- name: Creating challenge destination directory
|
||||
file:
|
||||
dest: "{{ acme_certificate_server_location }}"
|
||||
state: directory
|
||||
owner: "{{ acme_certificate_http_challenge_user }}"
|
||||
group: "{{ acme_certificate_http_challenge_group }}"
|
||||
mode: "{{ acme_certificate_http_challenge_folder_mode }}"
|
||||
become: "{{ acme_certificate_http_become }}"
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
|
||||
# Create challenge files on server.
|
||||
- name: "Copying challenge files for domains {{ ', '.join(acme_certificate_domains) }}"
|
||||
copy:
|
||||
dest: >-
|
||||
{{ [
|
||||
acme_certificate_server_location,
|
||||
item.value[acme_certificate_challenge].resource[('.well-known/acme-challenge/'|length):]
|
||||
] | path_join }}
|
||||
content: "{{ item.value[acme_certificate_challenge].resource_value }}"
|
||||
owner: "{{ acme_certificate_http_challenge_user }}"
|
||||
group: "{{ acme_certificate_http_challenge_group }}"
|
||||
mode: "{{ acme_certificate_http_challenge_file_mode }}"
|
||||
with_dict: "{{ acme_certificate_INTERNAL_challenge.challenge_data }}"
|
||||
become: "{{ acme_certificate_http_become }}"
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
189
roles/felixfontein.acme_certificate/tasks/main.yml
Normal file
189
roles/felixfontein.acme_certificate/tasks/main.yml
Normal file
@@ -0,0 +1,189 @@
|
||||
---
|
||||
- name: Determine whether to force private key regeneration (1/2)
|
||||
set_fact:
|
||||
acme_certificate_INTERNAL_force_regenerate_private_key: no
|
||||
|
||||
- name: Determine whether to force private key regeneration (2/2)
|
||||
set_fact:
|
||||
acme_certificate_INTERNAL_force_regenerate_private_key: yes
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
|
||||
- block:
|
||||
- name: Ansible version check
|
||||
assert:
|
||||
that: "ansible_version.string is version('2.8.3', '>=')"
|
||||
msg: "This version of the acme-certificate role must be used with Ansible 2.8.3 or later."
|
||||
run_once: yes
|
||||
|
||||
- name: Sanity checks
|
||||
assert:
|
||||
that: "acme_certificate_challenge != 'dns-01' or acme_certificate_dns_provider is not undefined"
|
||||
msg: "acme_certificate_dns_provider must be defined for dns-01 DNS challenge"
|
||||
run_once: yes
|
||||
|
||||
- name: "Test whether old certificate files for domains {{ ', '.join(acme_certificate_domains) }} exist"
|
||||
stat:
|
||||
path: "{{ [acme_certificate_keys_path, acme_certificate_key_name] | path_join }}.pem"
|
||||
delegate_to: localhost
|
||||
register: acme_certificate_INTERNAL_old_certificate_exists
|
||||
when: "acme_certificate_keys_old_store"
|
||||
run_once: yes
|
||||
|
||||
- name: "Copying old certificate files for domains {{ ', '.join(acme_certificate_domains) }}"
|
||||
copy:
|
||||
src: "{{ [acme_certificate_keys_path, acme_certificate_key_name] | path_join }}{{ item }}"
|
||||
dest: >-
|
||||
{{ [
|
||||
acme_certificate_keys_old_path,
|
||||
(
|
||||
(ansible_date_time.date ~ '-' ~ ansible_date_time.hour ~ ansible_date_time.minute ~ ansible_date_time.second ~ '-')
|
||||
if acme_certificate_keys_old_prepend_timestamp else ''
|
||||
) ~ acme_certificate_key_name ~ item
|
||||
] | path_join }}
|
||||
delegate_to: localhost
|
||||
with_items:
|
||||
- "-chain.pem"
|
||||
- "-fullchain.pem"
|
||||
- "-rootchain.pem"
|
||||
- "-root.pem"
|
||||
- ".key"
|
||||
- ".pem"
|
||||
when: "acme_certificate_keys_old_store and acme_certificate_INTERNAL_old_certificate_exists.stat.exists"
|
||||
run_once: yes
|
||||
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
|
||||
- block:
|
||||
- name: "Creating private key for domains {{ ', '.join(acme_certificate_domains) }} (RSA)"
|
||||
openssl_privatekey:
|
||||
path: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '.key'] | path_join }}"
|
||||
mode: "{{ acme_certificate_privatekey_mode }}"
|
||||
type: "{{ 'RSA' if acme_certificate_algorithm == 'rsa' else 'ECC' }}"
|
||||
size: "{{ acme_certificate_key_length if acme_certificate_algorithm == 'rsa' else omit }}"
|
||||
curve: >-
|
||||
{{ omit if acme_certificate_algorithm == 'rsa' else
|
||||
'secp256r1' if acme_certificate_algorithm == 'p-256' else
|
||||
'secp384r1' if acme_certificate_algorithm == 'p-384' else
|
||||
'secp521r1' if acme_certificate_algorithm == 'p-521' else
|
||||
'invalid value for acme_certificate_algorithm!' }}
|
||||
force: "{{ acme_certificate_INTERNAL_force_regenerate_private_key }}"
|
||||
delegate_to: localhost
|
||||
run_once: yes
|
||||
|
||||
- name: "Creating CSR for domains {{ ', '.join(acme_certificate_domains) }}"
|
||||
openssl_csr:
|
||||
path: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '.csr'] | path_join }}"
|
||||
privatekey_path: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '.key'] | path_join }}"
|
||||
subject_alt_name: |
|
||||
{{ acme_certificate_domains | map('regex_replace', '^(.*)$', 'DNS:\1' ) | list }}
|
||||
ocsp_must_staple: "{{ acme_certificate_ocsp_must_staple }}"
|
||||
use_common_name_for_san: no
|
||||
force: yes
|
||||
delegate_to: localhost
|
||||
run_once: yes
|
||||
|
||||
- name: "Get root certificate for domains {{ ', '.join(acme_certificate_domains) }}"
|
||||
get_url:
|
||||
url: "{{ acme_certificate_root_certificate }}"
|
||||
dest: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '-root.pem'] | path_join }}"
|
||||
force: yes
|
||||
validate_certs: "{{ acme_certificate_validate_certs }}"
|
||||
delegate_to: localhost
|
||||
run_once: yes
|
||||
|
||||
- block:
|
||||
- name: "Preparing challenges for domains {{ ', '.join(acme_certificate_domains) }}"
|
||||
acme_certificate:
|
||||
account_key: "{{ acme_certificate_acme_account }}"
|
||||
modify_account: "{{ acme_certificate_modify_account }}"
|
||||
csr: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '.csr'] | path_join }}"
|
||||
dest: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '.pem'] | path_join }}"
|
||||
fullchain_dest: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '-fullchain.pem'] | path_join }}"
|
||||
chain_dest: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '-chain.pem'] | path_join }}"
|
||||
account_email: "{{ acme_certificate_acme_email }}"
|
||||
terms_agreed: "{{ acme_certificate_terms_agreed }}"
|
||||
challenge: "{{ acme_certificate_challenge }}"
|
||||
acme_directory: "{{ acme_certificate_acme_directory }}"
|
||||
acme_version: "{{ acme_certificate_acme_version }}"
|
||||
force: yes
|
||||
validate_certs: "{{ acme_certificate_validate_certs }}"
|
||||
delegate_to: localhost
|
||||
run_once: yes
|
||||
register: acme_certificate_INTERNAL_challenge
|
||||
|
||||
always:
|
||||
- debug:
|
||||
msg: >-
|
||||
account URI: {{ acme_certificate_INTERNAL_challenge.get('account_uri') }};
|
||||
order URI: {{ acme_certificate_INTERNAL_challenge.get('order_uri') }}
|
||||
run_once: yes
|
||||
|
||||
- block:
|
||||
# Set up HTTP challenges
|
||||
- include_tasks: http-create.yml
|
||||
when: "acme_certificate_challenge == 'http-01'"
|
||||
|
||||
# Set up DNS challenges
|
||||
- include_tasks: dns-{{ acme_certificate_dns_provider }}-create.yml
|
||||
when: "acme_certificate_challenge == 'dns-01'"
|
||||
|
||||
- name: "Getting certificates for domains {{ ', '.join(acme_certificate_domains) }}"
|
||||
acme_certificate:
|
||||
account_key: "{{ acme_certificate_acme_account }}"
|
||||
modify_account: "{{ acme_certificate_modify_account }}"
|
||||
csr: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '.csr'] | path_join }}"
|
||||
dest: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '.pem'] | path_join }}"
|
||||
fullchain_dest: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '-fullchain.pem'] | path_join }}"
|
||||
chain_dest: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '-chain.pem'] | path_join }}"
|
||||
account_email: "{{ acme_certificate_acme_email }}"
|
||||
terms_agreed: "{{ acme_certificate_terms_agreed }}"
|
||||
challenge: "{{ acme_certificate_challenge }}"
|
||||
acme_directory: "{{ acme_certificate_acme_directory }}"
|
||||
acme_version: "{{ acme_certificate_acme_version }}"
|
||||
force: yes
|
||||
data: "{{ acme_certificate_INTERNAL_challenge }}"
|
||||
deactivate_authzs: "{{ acme_certificate_deactivate_authzs }}"
|
||||
validate_certs: "{{ acme_certificate_validate_certs }}"
|
||||
delegate_to: localhost
|
||||
run_once: yes
|
||||
|
||||
- name: "Form root chain for domains {{ ', '.join(acme_certificate_domains) }}"
|
||||
copy:
|
||||
dest: "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '-rootchain.pem'] | path_join }}"
|
||||
content: |
|
||||
{{ lookup('file', [acme_certificate_keys_path, acme_certificate_key_name ~ '-root.pem'] | path_join) }}
|
||||
{{ lookup('file', [acme_certificate_keys_path, acme_certificate_key_name ~ '-chain.pem'] | path_join) }}
|
||||
delegate_to: localhost
|
||||
run_once: yes
|
||||
always:
|
||||
# Clean up HTTP challenges
|
||||
- include_tasks: http-cleanup.yml
|
||||
when: "acme_certificate_challenge == 'http-01'"
|
||||
|
||||
# Clean up DNS challenges
|
||||
- include_tasks: dns-{{ acme_certificate_dns_provider }}-cleanup.yml
|
||||
when: "acme_certificate_challenge == 'dns-01'"
|
||||
|
||||
when: acme_certificate_INTERNAL_challenge is changed
|
||||
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
|
||||
- name: "Verifying certificate for domains {{ ', '.join(acme_certificate_domains) }}"
|
||||
command: >-
|
||||
openssl verify
|
||||
-CAfile "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '-root.pem'] | path_join }}"
|
||||
-untrusted "{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '-chain.pem'] | path_join }}"
|
||||
"{{ [acme_certificate_keys_path, acme_certificate_key_name ~ '.pem'] | path_join }}"
|
||||
changed_when: no
|
||||
delegate_to: localhost
|
||||
run_once: yes
|
||||
ignore_errors: "{{ not acme_certificate_verify_certs }}"
|
||||
tags:
|
||||
- issue-tls-certs-newkey
|
||||
- issue-tls-certs
|
||||
- verify-tls-certs
|
||||
4
roles/geerlingguy.gitlab/.ansible-lint
Normal file
4
roles/geerlingguy.gitlab/.ansible-lint
Normal file
@@ -0,0 +1,4 @@
|
||||
skip_list:
|
||||
- 'yaml'
|
||||
- 'role-name'
|
||||
- 'package-latest'
|
||||
75
roles/geerlingguy.gitlab/.github/workflows/ci.yml
vendored
Normal file
75
roles/geerlingguy.gitlab/.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
name: CI
|
||||
'on':
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: "0 7 * * 1"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: 'geerlingguy.gitlab'
|
||||
|
||||
jobs:
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the codebase.
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'geerlingguy.gitlab'
|
||||
|
||||
- name: Set up Python 3.
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install test dependencies.
|
||||
run: pip3 install yamllint
|
||||
|
||||
- name: Lint code.
|
||||
run: |
|
||||
yamllint .
|
||||
|
||||
molecule:
|
||||
name: Molecule
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- distro: centos7
|
||||
playbook: converge.yml
|
||||
- distro: ubuntu1804
|
||||
playbook: converge.yml
|
||||
- distro: debian9
|
||||
playbook: converge.yml
|
||||
- distro: centos7
|
||||
playbook: version.yml
|
||||
- distro: ubuntu1804
|
||||
playbook: version.yml
|
||||
|
||||
steps:
|
||||
- name: Check out the codebase.
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'geerlingguy.gitlab'
|
||||
|
||||
- name: Set up Python 3.
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install test dependencies.
|
||||
run: pip3 install ansible molecule[docker] docker
|
||||
|
||||
- name: Run Molecule tests.
|
||||
run: molecule test
|
||||
env:
|
||||
PY_COLORS: '1'
|
||||
ANSIBLE_FORCE_COLOR: '1'
|
||||
MOLECULE_DISTRO: ${{ matrix.distro }}
|
||||
MOLECULE_PLAYBOOK: ${{ matrix.playbook }}
|
||||
38
roles/geerlingguy.gitlab/.github/workflows/release.yml
vendored
Normal file
38
roles/geerlingguy.gitlab/.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
# This workflow requires a GALAXY_API_KEY secret present in the GitHub
|
||||
# repository or organization.
|
||||
#
|
||||
# See: https://github.com/marketplace/actions/publish-ansible-role-to-galaxy
|
||||
# See: https://github.com/ansible/galaxy/issues/46
|
||||
|
||||
name: Release
|
||||
'on':
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: 'geerlingguy.gitlab'
|
||||
|
||||
jobs:
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the codebase.
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'geerlingguy.gitlab'
|
||||
|
||||
- name: Set up Python 3.
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install Ansible.
|
||||
run: pip3 install ansible-base
|
||||
|
||||
- name: Trigger a new import on Galaxy.
|
||||
run: ansible-galaxy role import --api-key ${{ secrets.GALAXY_API_KEY }} $(echo ${{ github.repository }} | cut -d/ -f1) $(echo ${{ github.repository }} | cut -d/ -f2)
|
||||
@@ -1,2 +1,2 @@
|
||||
install_date: Tue Apr 20 16:13:49 2021
|
||||
install_date: Wed Apr 21 16:48:33 2021
|
||||
version: 3.1.0
|
||||
|
||||
31
roles/geerlingguy.gitlab/molecule/default/version.yml
Normal file
31
roles/geerlingguy.gitlab/molecule/default/version.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
|
||||
vars:
|
||||
gitlab_restart_handler_failed_when: false
|
||||
|
||||
pre_tasks:
|
||||
- name: Update apt cache.
|
||||
apt: update_cache=true cache_valid_time=600
|
||||
when: ansible_os_family == 'Debian'
|
||||
changed_when: false
|
||||
|
||||
- name: Remove the .dockerenv file so GitLab Omnibus doesn't get confused.
|
||||
file:
|
||||
path: /.dockerenv
|
||||
state: absent
|
||||
|
||||
- name: Set the test GitLab version number for Debian.
|
||||
set_fact:
|
||||
gitlab_version: '11.4.0-ce.0'
|
||||
when: ansible_os_family == 'Debian'
|
||||
|
||||
- name: Set the test GitLab version number for RedHat.
|
||||
set_fact:
|
||||
gitlab_version: '11.4.0-ce.0.el7'
|
||||
when: ansible_os_family == 'RedHat'
|
||||
|
||||
roles:
|
||||
- role: geerlingguy.gitlab
|
||||
@@ -1,2 +1,2 @@
|
||||
install_date: Tue Apr 20 16:13:48 2021
|
||||
install_date: Wed Apr 21 16:48:32 2021
|
||||
version: 1.10.0
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
install_date: Tue Apr 20 16:13:55 2021
|
||||
install_date: Wed Apr 21 16:48:44 2021
|
||||
version: master
|
||||
|
||||
12
roles/ikke_t.podman_container_systemd/.yamllint.yml
Normal file
12
roles/ikke_t.podman_container_systemd/.yamllint.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
# 80 chars should be enough, but don't fail if a line is longer
|
||||
line-length:
|
||||
max: 80
|
||||
level: warning
|
||||
indentation:
|
||||
spaces: 2
|
||||
indent-sequences: whatever
|
||||
@@ -1,2 +1,2 @@
|
||||
install_date: Tue Apr 20 16:13:54 2021
|
||||
install_date: Wed Apr 21 16:48:42 2021
|
||||
version: 2.1.0
|
||||
|
||||
3
roles/ikke_t.podman_container_systemd/requirements.yml
Normal file
3
roles/ikke_t.podman_container_systemd/requirements.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
collections:
|
||||
- containers.podman
|
||||
37
roles/ikke_t.podman_container_systemd/tasks/check_subid.yml
Normal file
37
roles/ikke_t.podman_container_systemd/tasks/check_subid.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
|
||||
- name: check if user is in subuid file
|
||||
find:
|
||||
path: /etc/subuid
|
||||
contains: '^{{ container_run_as_user }}:.*$'
|
||||
register: uid_line_found
|
||||
when: container_run_as_user != 'root'
|
||||
|
||||
- name: check if group is in subgid file
|
||||
find:
|
||||
path: /etc/subgid
|
||||
contains: '^{{ container_run_as_group }}:.*$'
|
||||
register: gid_line_found
|
||||
when: container_run_as_group != 'root'
|
||||
|
||||
- name: ensure user is in subuid file, if it was missing
|
||||
lineinfile:
|
||||
path: /etc/subuid
|
||||
regexp: "^{{ container_run_as_user }}:.*"
|
||||
line: "{{ container_run_as_user }}:165536:65536"
|
||||
create: true
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
when: container_run_as_user != 'root' and not uid_line_found.matched
|
||||
|
||||
- name: ensure group is in subgid file, if it was missing
|
||||
lineinfile:
|
||||
path: /etc/subgid
|
||||
regexp: "^{{ container_run_as_group }}:.*"
|
||||
line: "{{ container_run_as_group }}:165536:65536"
|
||||
create: true
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
when: container_run_as_group != 'root' and not gid_line_found.matched
|
||||
12
roles/linux-system-roles.network/.ansible-lint
Normal file
12
roles/linux-system-roles.network/.ansible-lint
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
skip_list:
|
||||
- '106' # Role name does not match ^[a-z][a-z0-9_]+$ pattern
|
||||
- '206' # Variables should have spaces before and after: {{ var_name }}
|
||||
- '208' # File permissions unset or incorrect
|
||||
- '301' # Commands should not change things if nothing needs doing
|
||||
- '303' # Using command rather than module
|
||||
- '305' # Use shell only when shell functionality is required
|
||||
- '403' # Package installs should not use latest
|
||||
- '502' # All tasks should be named
|
||||
- '601' # Don't compare to literal True/False
|
||||
- '602' # Don't compare to empty string
|
||||
12
roles/linux-system-roles.network/.github/stale.yml
vendored
Normal file
12
roles/linux-system-roles.network/.github/stale.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
daysUntilStale: 30
|
||||
daysUntilClose: 14
|
||||
staleLabel: stale
|
||||
markComment: >
|
||||
Thank you for your contribution! There was no activity in this pull request
|
||||
recently. To avoid pull requests to pile up, an automated process marked this
|
||||
pull request as stale. It will close this pull request if no further activity
|
||||
occurs. The current policy is available at:
|
||||
https://github.com//linux-system-roles/network/blob/main/.github/stale.yml
|
||||
only: pulls
|
||||
11
roles/linux-system-roles.network/.github/workflows/markdownlint.yml
vendored
Normal file
11
roles/linux-system-roles.network/.github/workflows/markdownlint.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: markdownlint
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
markdownlint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@main
|
||||
- name: Run mdl
|
||||
uses: actionshub/markdownlint@main
|
||||
51
roles/linux-system-roles.network/.github/workflows/tox.yml
vendored
Normal file
51
roles/linux-system-roles.network/.github/workflows/tox.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
# yamllint disable rule:line-length
|
||||
name: tox
|
||||
on: # yamllint disable-line rule:truthy
|
||||
- pull_request
|
||||
env:
|
||||
TOX_LSR: "git+https://github.com/linux-system-roles/tox-lsr@2.4.0"
|
||||
LSR_ANSIBLE_TEST_DOCKER: "true"
|
||||
LSR_ANSIBLES: 'ansible==2.8.* ansible==2.9.*'
|
||||
LSR_MSCENARIOS: default
|
||||
# LSR_EXTRA_PACKAGES: "libdbus-1-dev libgirepository1.0-dev python3-dev"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
jobs:
|
||||
python:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
pyver: ['2.7', '3.6', '3.7', '3.8']
|
||||
steps:
|
||||
- name: checkout PR
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.pyver }}
|
||||
- name: Install platform dependencies, python, tox, tox-lsr
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
python -m pip install --upgrade pip
|
||||
sudo apt-get update
|
||||
sudo apt-get install git
|
||||
pip install "$TOX_LSR"
|
||||
lsr_ci_preinstall
|
||||
- name: Run tox tests
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
toxpyver=$(echo "${{ matrix.pyver }}" | tr -d .)
|
||||
toxenvs="py${toxpyver}"
|
||||
case "$toxpyver" in
|
||||
27) toxenvs="${toxenvs},coveralls,flake8,pylint" ;;
|
||||
36) toxenvs="${toxenvs},coveralls,black,yamllint,ansible-lint,collection" ;;
|
||||
37) toxenvs="${toxenvs},coveralls" ;;
|
||||
38) toxenvs="${toxenvs},coveralls" ;;
|
||||
esac
|
||||
TOXENV="$toxenvs" lsr_ci_runtox
|
||||
python-26:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout PR
|
||||
uses: actions/checkout@v2
|
||||
- name: Run py26 tests
|
||||
uses: linux-system-roles/lsr-gh-action-py26@1.0.1
|
||||
5
roles/linux-system-roles.network/.lgtm.yml
Normal file
5
roles/linux-system-roles.network/.lgtm.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
extraction:
|
||||
python:
|
||||
python_setup:
|
||||
version: 2
|
||||
9
roles/linux-system-roles.network/.mdl_style.rb
Normal file
9
roles/linux-system-roles.network/.mdl_style.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
all
|
||||
# https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md#md003---header-style
|
||||
rule 'MD003', :style => :setext_with_atx
|
||||
# https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md#md013---line-length
|
||||
rule 'MD013', :line_length => 88
|
||||
# https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md#md029---ordered-list-item-prefix
|
||||
rule 'MD029', :style => :ordered
|
||||
# https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md#md024---multiple-headers-with-the-same-content
|
||||
rule "MD024", :allow_different_nesting => true
|
||||
1
roles/linux-system-roles.network/.mdlrc
Normal file
1
roles/linux-system-roles.network/.mdlrc
Normal file
@@ -0,0 +1 @@
|
||||
style '.mdl_style.rb'
|
||||
11
roles/linux-system-roles.network/.travis/custom.sh
Executable file
11
roles/linux-system-roles.network/.travis/custom.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
set -e
|
||||
|
||||
. "$LSR_SCRIPTDIR/utils.sh"
|
||||
|
||||
# Write your custom commands here that should be run when `tox -e custom`:
|
||||
if lsr_check_python_version python -eq '3.6'; then
|
||||
(set -x; cd "${TOPDIR}/tests"; python ./ensure_provider_tests.py)
|
||||
fi
|
||||
23
roles/linux-system-roles.network/.yamllint.yml
Normal file
23
roles/linux-system-roles.network/.yamllint.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
---
|
||||
extends: yamllint_defaults.yml
|
||||
# possible customizations over the base yamllint config
|
||||
# skip the yaml files in the /tests/ directory
|
||||
# NOTE: If you want to customize `ignore` you'll have to
|
||||
# copy in all of the config from .yamllint.yml, then
|
||||
# add your own - so if you want to just add /tests/ to
|
||||
# be ignored, you'll have to add the ignores from the base
|
||||
# ignore: |
|
||||
# /tests/
|
||||
# /.tox/
|
||||
# skip checking line length
|
||||
# NOTE: the above does not apply to `rules` - you do not
|
||||
# have to copy all of the rules from the base config
|
||||
# rules:
|
||||
# line-length: disable
|
||||
rules:
|
||||
truthy: disable
|
||||
line-length:
|
||||
ignore: |
|
||||
/tests/tests_wireless_plugin_installation_nm.yml
|
||||
/tests/tests_team_plugin_installation_nm.yml
|
||||
49
roles/linux-system-roles.network/CHANGELOG.md
Normal file
49
roles/linux-system-roles.network/CHANGELOG.md
Normal file
@@ -0,0 +1,49 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
[1.3.0] - 2021-04-08
|
||||
--------------------
|
||||
|
||||
### Changes
|
||||
|
||||
- Use inclusive language
|
||||
- `slave` is deprecated in favor of `port`
|
||||
- `master` is deprecated in favor of `controller`
|
||||
|
||||
### New features
|
||||
|
||||
- Support disabling IPv6
|
||||
- Support `dns_options` when using one or more IPv4 nameservers
|
||||
- Support Ethtool coalesce settings
|
||||
- Support dummy interfaces
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix static IPv6 support for initscripts provider
|
||||
|
||||
[1.2.0] - 2020-08-26
|
||||
--------------------
|
||||
|
||||
### Changes
|
||||
|
||||
- Rename ethtool features to use underscores instead of dashes to support
|
||||
Jinja2 dot notation. Accept old notation for compatibility with existing
|
||||
playbooks.
|
||||
|
||||
### New features
|
||||
|
||||
- Initial 802.1x authentication support (only EAP-TLS)
|
||||
- Wireless support
|
||||
- Handle OracleLinux as a RHEL clone
|
||||
- Remove dependency on ethtool command line tool
|
||||
- initscripts: Support creating and activating bond profiles in one run
|
||||
- Ignore up/down states if a profile is not defined and not present on the
|
||||
managed host
|
||||
- Document bond profiles
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- NetworkManager: Always rollback checkpoint on failure
|
||||
- NetworkManager: Try to reapply changes to reduce network interruptions
|
||||
- initscripts: Fix dependencies for Fedora 32
|
||||
- Only log actual warnings as Ansible warnings
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# ansible and dependencies for all supported platforms
|
||||
ansible ; python_version > "2.6"
|
||||
ansible<2.7 ; python_version < "2.7"
|
||||
idna<2.8 ; python_version < "2.7"
|
||||
PyYAML<5.1 ; python_version < "2.7"
|
||||
409
roles/linux-system-roles.network/contributing.md
Normal file
409
roles/linux-system-roles.network/contributing.md
Normal file
@@ -0,0 +1,409 @@
|
||||
Contributing to the Network Linux System Role
|
||||
=============================================
|
||||
|
||||
Where to start
|
||||
--------------
|
||||
|
||||
- **Bugs and needed implementations** are listed on [Github
|
||||
Issues](https://github.com/linux-system-roles/network/issues). Issues labeled with
|
||||
[**help
|
||||
wanted**](https://github.com/linux-system-roles/network/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)
|
||||
are likely to be suitable for new contributors!
|
||||
|
||||
- **Code** is managed on
|
||||
[Github](https://github.com/linux-system-roles/network), using [Pull
|
||||
Requests](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests).
|
||||
|
||||
- The code needs to be **compatible with Python 2.6, 2.7, 3.6, 3.7 and 3.8**.
|
||||
|
||||
Code structure
|
||||
--------------
|
||||
|
||||
The repository is structured as follows:
|
||||
|
||||
- `./defaults/` - Contains the default role configuration.
|
||||
|
||||
- `./examples/` - Contains YAML examples for different configurations.
|
||||
|
||||
- `./library/network_connections.py` - Contains the internal Ansible module, which is
|
||||
the main script. It controls the communication between the role and Ansible, imports
|
||||
the YAML configuration and applies the changes to the provider (i.e. NetworkManager,
|
||||
initscripts).
|
||||
|
||||
- `./meta/` - Metadata of the project.
|
||||
|
||||
- `./module_utils/network_lsr/` - Contains other files that are useful for the network
|
||||
role (e.g. the YAML argument validator)
|
||||
|
||||
- `./tasks/` - Declaration of the different tasks that the role is going to execute.
|
||||
|
||||
- `./tests/playbooks/` - Contains the complete tests for the role. `./tests/test_*.yml`
|
||||
are shims to run tests once for every provider. `./tests/tasks/` contains task
|
||||
snippets that are used in multiple tests to avoid having the same code repeated
|
||||
multiple times.
|
||||
|
||||
The rest of files in the root folder mostly serve as configuration files for diferent
|
||||
testing tools and bots that help with the manteinance of the project.
|
||||
|
||||
The code files will always have the imports on the first place, followed by constants
|
||||
and in the last place, classes and methods. The style of python coding for this project
|
||||
is [**PEP 8**](https://www.python.org/dev/peps/pep-0008/), with automatic formatting
|
||||
thanks to [Python Black](https://black.readthedocs.io/en/stable/). Make sure to install
|
||||
the formatter, it will help you a lot throughout the whole coding process!
|
||||
|
||||
Configuring Git
|
||||
---------------
|
||||
|
||||
Before starting to contribute, make sure you have the basic git configuration: Your name
|
||||
and email. This will be useful when signing your contributions. The following commands
|
||||
will set your global name and email, althought you can change it later for every repo:
|
||||
|
||||
```bash
|
||||
git config --global user.name "Jane Doe"
|
||||
git config --global user.email janedoe@example.com
|
||||
```
|
||||
|
||||
The git editor is your system's default. If you feel more comfortable with a different
|
||||
editor for writing your commits (such as Vim), change it with:
|
||||
|
||||
```bash
|
||||
git config --global core.editor vim
|
||||
```
|
||||
|
||||
If you want to check your settings, use `git config --list` to see all the settings Git
|
||||
can find.
|
||||
|
||||
How to contribute
|
||||
-----------------
|
||||
|
||||
1. Make a
|
||||
[fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo)
|
||||
of this repository.
|
||||
|
||||
2. Create a new git branch on your local fork (the name is not relevant) and make the
|
||||
changes you need to complete an issue.
|
||||
|
||||
3. Do not forget to run unit and integration tests before pushing any changes!
|
||||
1. This project uses [tox](https://tox.readthedocs.io/en/latest/) to run unit tests.
|
||||
You can try it with `tox -e py36` in case you want to try it using Python 3.6, or
|
||||
just `tox` if you want to run all the tests.
|
||||
|
||||
2. Check the formatting of the code with
|
||||
[Python Black](https://black.readthedocs.io/en/stable/)
|
||||
|
||||
3. Check the YAML files are correctly formatted using `tox -e yamllint`.
|
||||
|
||||
4. Integration tests are executed as
|
||||
[Ansible Playbooks](https://docs.ansible.com/ansible/latest/user_guide/playbooks.html).
|
||||
|
||||
To run them you can use a cloud image like the [CentOS Linux 8.1
|
||||
VM](https://cloud.centos.org/centos/8/x86_64/images/CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2)
|
||||
and execute the command and download the package
|
||||
`standard-test-roles-inventory-qemu` from the Fedora repository:
|
||||
|
||||
```bash
|
||||
dnf install standard-test-roles-inventory-qemu
|
||||
```
|
||||
|
||||
Note that the last path is the one of the test you want to run:
|
||||
|
||||
```bash
|
||||
TEST_SUBJECTS=CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2 \
|
||||
ansible-playbook -v -i /usr/share/ansible/inventory/standard-inventory-qcow2 \
|
||||
tests/test_default.yml
|
||||
```
|
||||
|
||||
5. Check the markdown format with
|
||||
[mdl](https://github.com/markdownlint/markdownlint) after changing any
|
||||
markdown document.
|
||||
|
||||
4. Once the work is ready and commited, push the branch to your remote fork and click on
|
||||
"new Pull Request" on Github.
|
||||
|
||||
5. All set! Now wait for the continuous integration to pass and go over your commit if
|
||||
there are any errors. If there is no problem with your contribution, the mantainer
|
||||
will merge it to the main project.
|
||||
|
||||
### Find other images for testing
|
||||
|
||||
The CentOS project publishes cloud images for
|
||||
[CentOS Linux 6](https://cloud.centos.org/centos/6/images/),
|
||||
[CentOS Linux 7](https://cloud.centos.org/centos/7/images/) and
|
||||
[CentOS Linux 8](https://cloud.centos.org/centos/8/x86_64/images/).
|
||||
|
||||
- For qemu testing cases, we prefer the image architecture to be `x86_64-GenericCloud`.
|
||||
- `2003` in `CentOS-7-x86_64-GenericCloud-2003.qcow2c` stands for image released in
|
||||
March 2020.
|
||||
- We can use the image with extension `.qcow2` and `.qcow2c`.
|
||||
- To save the image, right click on the link above, then select "Save link as...".
|
||||
|
||||
For Fedora, we recommend to use the [latest qcow2
|
||||
images](https://kojipkgs.fedoraproject.org/compose/cloud/).
|
||||
|
||||
### Some important tips
|
||||
|
||||
- Make sure your fork and branch are up-to-date with the main project. First of all,
|
||||
[configure a remote upstream for your
|
||||
fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/configuring-a-remote-for-a-fork),
|
||||
and keep your branch up-to-date with the upstream using
|
||||
`git pull --rebase upstream main`.
|
||||
|
||||
- Try to make a commit per issue.
|
||||
|
||||
- If you are asked to make changes to your PR, don't panic! Many times it is enough to
|
||||
amend your previous commit adding the new content to it (`git commit --amend`). Be
|
||||
sure to pull the latest upstream changes after that, and use `git push
|
||||
--force-with-lease` to re-upload your commit with the changes! Another way of doing
|
||||
changes to a PR is by [squashing
|
||||
commits](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-request-merges#squash-and-merge-your-pull-request-commits).
|
||||
|
||||
- There are times when someone has made changes on a file you were modifying while you
|
||||
were making changes to your unfinished commit. At times like this, you need to make a
|
||||
[**rebase**](https://help.github.com/en/github/using-git/about-git-rebase) with
|
||||
conflicts. On the rebase you have to compare what the other person added to what you
|
||||
added, and merge both file versions into one that combines it all.
|
||||
|
||||
- If you have any doubt, do not hesitate to ask! You can join IRC channel \#systemroles
|
||||
on freenode, or ask on the PR/issue itself.
|
||||
|
||||
### Naming Ansible Items
|
||||
|
||||
- All YAML or Python files, variables, arguments, repositories, and other such names
|
||||
should follow standard Python naming conventions of being in
|
||||
`snake_case_naming_schemes`.
|
||||
|
||||
- Names should be mnemonic and descriptive and not strive to shorten more than
|
||||
necessary. Systems support long identifier names, so use them to be descriptive
|
||||
|
||||
- All defaults and all arguments to a role should have a name that begins with the role
|
||||
name to help avoid collision with other names. Avoid names like `packages` in favor of
|
||||
a name like `network_packages`.
|
||||
|
||||
- Same argument applies for modules provided in the roles, they also need a `$ROLENAME_`
|
||||
prefix: `network_module`. While they are usually implementation details and not intended
|
||||
for direct use in playbooks, the unfortunate fact is that importing a role makes them
|
||||
available to the rest of the playbook and therefore creates opportunities for name
|
||||
collisions.
|
||||
|
||||
- Moreover, internal variables (those that are not expected to be set by users) are to
|
||||
be prefixed by two underscores: `__network_variable`. This includes variables set by
|
||||
`set_fact` and `register`, because they persist in the namespace after the role has
|
||||
finished!
|
||||
|
||||
- Do not use special characters other than underscore in variable names, even if
|
||||
YAML/JSON allow them. (Using such variables in Jinja2 or Python would be then very
|
||||
confusing and probably not functional.)
|
||||
|
||||
*Find more explanation on this matter in the [meta
|
||||
standards](https://github.com/oasis-roles/meta_standards#naming-things).*
|
||||
|
||||
### Write a good commit message
|
||||
|
||||
Here are a few rules to keep in mind while writing a commit message
|
||||
|
||||
1. Separate subject from body with a blank line
|
||||
2. Limit the subject line to 50 characters
|
||||
3. Capitalize the subject line
|
||||
4. Do not end the subject line with a period
|
||||
5. Use the imperative mood in the subject line
|
||||
6. Wrap the body at 72 characters
|
||||
7. Use the body to explain what and why vs. how
|
||||
|
||||
A good commit message looks something like this:
|
||||
|
||||
```text
|
||||
Summarize changes in around 50 characters or less
|
||||
|
||||
More detailed explanatory text, if necessary. Wrap it to about 72
|
||||
characters or so. In some contexts, the first line is treated as the
|
||||
subject of the commit and the rest of the text as the body. The
|
||||
blank line separating the summary from the body is critical (unless
|
||||
you omit the body entirely); various tools like `log`, `shortlog`
|
||||
and `rebase` can get confused if you run the two together.
|
||||
|
||||
Explain the problem that this commit is solving. Focus on why you
|
||||
are making this change as opposed to how (the code explains that).
|
||||
Are there side effects or other unintuitive consequences of this
|
||||
change? Here's the place to explain them.
|
||||
|
||||
Further paragraphs come after blank lines.
|
||||
|
||||
- Bullet points are okay, too
|
||||
|
||||
- Typically a hyphen or asterisk is used for the bullet, preceded
|
||||
by a single space, with blank lines in between, but conventions
|
||||
vary here
|
||||
|
||||
If you use an issue tracker, put references to them at the bottom,
|
||||
like this:
|
||||
|
||||
Resolves: #123
|
||||
See also: #456, #789
|
||||
|
||||
Do not forget to sign your commit! Use `git commit -s`
|
||||
```
|
||||
|
||||
This is taken from [chris beams git commit](https://chris.beams.io/posts/git-commit/).
|
||||
You may want to read this for a more detailed explanation (and links to other posts on
|
||||
how to write a good commit message). This content is licensed under
|
||||
[CC-BY-SA](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||
|
||||
### Sign off your commit
|
||||
|
||||
You need to sign off your commit. By adding your 'Signed-off-by' line to the commit
|
||||
messages you adhere to the
|
||||
[Developer Certificate of Origin (DCO)](https://developercertificate.org/).
|
||||
|
||||
Use the `-s` command-line option to append the `Signed-off-by` line when committing your
|
||||
code:
|
||||
|
||||
`$ git commit -s`
|
||||
|
||||
This is the full text of the Developer Certificate of Origin:
|
||||
|
||||
```text
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
1 Letterman Drive
|
||||
Suite D4700
|
||||
San Francisco, CA, 94129
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
When using the `nm` provider, NetworkManager create a checkpoint and reverts the changes
|
||||
on failures. This makes it hard to debug the error. To disable this, set the Ansible
|
||||
variable `__network_debug_flags` to include the value `disable-checkpoints`. Also tests
|
||||
clean up by default in case there are failures. They should be tagged as
|
||||
`tests::cleanup` and can be skipped. To use both, run the test playbooks like this:
|
||||
|
||||
```bash
|
||||
ansible-playbook --skip-tags tests::cleanup \
|
||||
-e "__network_debug_flags=disable-checkpoints" \
|
||||
-i testhost, tests/playbooks/tests_802_1x.yml
|
||||
```
|
||||
|
||||
### NetworkManager Documentation
|
||||
|
||||
- [NM 1.0](https://lazka.github.io/pgi-docs/#NM-1.0), it contains a full explanation
|
||||
about the NetworkManager API.
|
||||
|
||||
### Integration tests with podman
|
||||
|
||||
1. Create `~/.ansible/collections/ansible_collections/containers/podman/` if this
|
||||
directory does not exist and `cd` into this directory.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.ansible/collections/ansible_collections/containers/podman/
|
||||
cd ~/.ansible/collections/ansible_collections/containers/podman/
|
||||
```
|
||||
|
||||
2. Clone the collection plugins for Ansible-Podman into the current directory.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/containers/ansible-podman-collections.git .
|
||||
```
|
||||
|
||||
3. Change directory into the `tests` subdirectory.
|
||||
|
||||
```bash
|
||||
cd ~/network/tests
|
||||
```
|
||||
|
||||
4. Use podman with `-d` to run in the background (daemon). Use `c7` because
|
||||
`centos/systemd` is centos7.
|
||||
|
||||
```bash
|
||||
podman run --name lsr-ci-c7 --rm --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:ro \
|
||||
-d registry.centos.org/centos/systemd
|
||||
```
|
||||
|
||||
5. Use `podman unshare` first to run "podman mount" in root mode, use `-vi` to run
|
||||
ansible as inventory in verbose mode, use `-c podman` to use collection plugins. Note,
|
||||
the following tests are currently not working with podman:
|
||||
- `tests_802_1x_nm.yml`
|
||||
- `tests_802_1x_updated_nm.yml`
|
||||
- `tests_bond_initscripts.yml`
|
||||
- `tests_bond_nm.yml`
|
||||
- `tests_bridge_initscripts.yml`
|
||||
- `tests_bridge_nm.yml`
|
||||
- `tests_default_nm.yml`
|
||||
- `tests_ethernet_nm.yml`
|
||||
- `tests_reapply_nm.yml`
|
||||
- `tests_states_nm.yml`
|
||||
- `tests_vlan_mtu_initscripts.yml`
|
||||
- `tests_vlan_mtu_nm.yml`
|
||||
- `tests_wireless_nm.yml`
|
||||
|
||||
```bash
|
||||
podman unshare
|
||||
ansible-playbook -vi lsr-ci-c7, -c podman tests_provider_nm.yml
|
||||
```
|
||||
|
||||
6. NOTE that this leaves the container running in the background, to kill it:
|
||||
|
||||
```bash
|
||||
podman stop lsr-ci-c7
|
||||
podman rm lsr-ci-c7
|
||||
```
|
||||
|
||||
### Continuous integration
|
||||
|
||||
The [continuous integration](https://en.wikipedia.org/wiki/Continuous_integration) (CI)
|
||||
contains a set of automated tests that are triggered on a remote server. Some of them
|
||||
are immediately triggered when pushing new content to a PR (i.e. the tests hosted on
|
||||
TravisCI) while other need to be triggered by members of the project. This second
|
||||
set of tests can be manually triggered. To trigger them, write a command as a PR
|
||||
comment. The available commands are:
|
||||
|
||||
- [citest] - Trigger a re-test for all machines.
|
||||
- [citest bad] - Trigger a re-test for all machines with an error or failure status.
|
||||
- [citest pending] - Trigger a re-test for all machines with a pending status.
|
||||
- [citest commit:<sha1\>] - Whitelist a commit to be tested if the submitter is not
|
||||
trusted.
|
||||
|
||||
How to reach us
|
||||
---------------
|
||||
|
||||
The mailing list for developers: systemroles@lists.fedorahosted.org
|
||||
|
||||
[Subscribe to the mailing list](https://lists.fedorahosted.org/admin/lists/systemroles.lists.fedorahosted.org/)
|
||||
|
||||
[Archive of the mailing list](https://lists.fedorahosted.org/archives/list/systemroles@lists.fedorahosted.org/)
|
||||
|
||||
If you are using IRC, join the `#systemroles` IRC channel on
|
||||
[freenode](https://freenode.net/kb/answer/chat)
|
||||
|
||||
*Thanks for contributing and happy coding!!*
|
||||
4
roles/linux-system-roles.network/custom_requirements.txt
Normal file
4
roles/linux-system-roles.network/custom_requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Write requirements for running your custom commands in tox here:
|
||||
PyYAML; python_version == '2.7' or python_version >= '3.5'
|
||||
36
roles/linux-system-roles.network/examples/bond_simple.yml
Normal file
36
roles/linux-system-roles.network/examples/bond_simple.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
# Specify the bond profile
|
||||
- name: bond0
|
||||
state: up
|
||||
type: bond
|
||||
interface_name: bond0
|
||||
# ip configuration (optional)
|
||||
ip:
|
||||
address:
|
||||
- "192.0.2.24/24"
|
||||
- "2001:db8::23/64"
|
||||
# bond configuration settings: (optional)
|
||||
bond:
|
||||
mode: active-backup
|
||||
miimon: 110
|
||||
|
||||
# add an ethernet profile to the bond
|
||||
- name: member1
|
||||
state: up
|
||||
type: ethernet
|
||||
interface_name: eth1
|
||||
controller: bond0
|
||||
|
||||
# add a second ethernet profile to the bond
|
||||
- name: member2
|
||||
state: up
|
||||
type: ethernet
|
||||
interface_name: eth2
|
||||
controller: bond0
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
...
|
||||
38
roles/linux-system-roles.network/examples/bond_with_vlan.yml
Normal file
38
roles/linux-system-roles.network/examples/bond_with_vlan.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
|
||||
# Create a bond profile, which is the parent of VLAN.
|
||||
- name: prod2
|
||||
state: up
|
||||
type: bond
|
||||
interface_name: bond2
|
||||
ip:
|
||||
dhcp4: no
|
||||
auto6: no
|
||||
bond:
|
||||
mode: active-backup
|
||||
miimon: 110
|
||||
|
||||
# set an ethernet as port to the bond
|
||||
- name: prod2-port1
|
||||
state: up
|
||||
type: ethernet
|
||||
interface_name: "{{ network_interface_name2 }}"
|
||||
controller: prod2
|
||||
|
||||
# on top of it, create a VLAN with ID 100 and static
|
||||
# addressing
|
||||
- name: prod2.100
|
||||
state: up
|
||||
type: vlan
|
||||
parent: prod2
|
||||
vlan_id: 100
|
||||
ip:
|
||||
address:
|
||||
- "192.0.2.{{ network_iphost }}/24"
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
@@ -0,0 +1,36 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
|
||||
# Create a bridge profile, which is the parent of VLAN.
|
||||
- name: prod2
|
||||
state: up
|
||||
type: bridge
|
||||
interface_name: bridge2
|
||||
ip:
|
||||
dhcp4: no
|
||||
auto6: no
|
||||
|
||||
# set an ethernet port to the bridge
|
||||
- name: prod2-port1
|
||||
state: up
|
||||
type: ethernet
|
||||
interface_name: "{{ network_interface_name2 }}"
|
||||
controller: prod2
|
||||
port_type: bridge
|
||||
|
||||
# on top of it, create a VLAN with ID 100 and static
|
||||
# addressing
|
||||
- name: prod2.100
|
||||
state: up
|
||||
type: vlan
|
||||
parent: prod2
|
||||
vlan_id: 100
|
||||
ip:
|
||||
address:
|
||||
- "192.0.2.{{ network_iphost }}/24"
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
1
roles/linux-system-roles.network/examples/down_profile.yml
Symbolic link
1
roles/linux-system-roles.network/examples/down_profile.yml
Symbolic link
@@ -0,0 +1 @@
|
||||
../tests/playbooks/down_profile.yml
|
||||
17
roles/linux-system-roles.network/examples/dummy_simple.yml
Normal file
17
roles/linux-system-roles.network/examples/dummy_simple.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
vars:
|
||||
network_connections:
|
||||
# Specify the dummy profile
|
||||
- name: dummy0
|
||||
state: up
|
||||
type: dummy
|
||||
interface_name: dummy0
|
||||
ip:
|
||||
address:
|
||||
- "192.0.2.42/30"
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
...
|
||||
@@ -0,0 +1,44 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
vars:
|
||||
network_connections:
|
||||
- name: eth0
|
||||
type: ethernet
|
||||
ip:
|
||||
route_metric4: 100
|
||||
dhcp4: no
|
||||
gateway4: 192.0.2.1
|
||||
dns:
|
||||
- 192.0.2.2
|
||||
- 198.51.100.5
|
||||
dns_search:
|
||||
- example.com
|
||||
- subdomain.example.com
|
||||
dns_options:
|
||||
- rotate
|
||||
- timeout:1
|
||||
|
||||
route_metric6: -1
|
||||
auto6: no
|
||||
gateway6: 2001:db8::1
|
||||
|
||||
address:
|
||||
- 192.0.2.3/24
|
||||
- 198.51.100.3/26
|
||||
- 2001:db8::80/7
|
||||
|
||||
route:
|
||||
- network: 198.51.100.128
|
||||
prefix: 26
|
||||
gateway: 198.51.100.1
|
||||
metric: 2
|
||||
- network: 198.51.100.64
|
||||
prefix: 26
|
||||
gateway: 198.51.100.6
|
||||
metric: 4
|
||||
route_append_only: no
|
||||
rule_append_only: yes
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
...
|
||||
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
|
||||
# Create one ethernet profile and activate it.
|
||||
# The profile uses automatic IP addressing
|
||||
# and is tied to the interface by MAC address.
|
||||
- name: prod1
|
||||
state: up
|
||||
type: ethernet
|
||||
autoconnect: yes
|
||||
mac: "{{ network_mac1 }}"
|
||||
mtu: 1450
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
@@ -0,0 +1,30 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
- name: eth0
|
||||
type: ethernet
|
||||
ieee802_1x:
|
||||
identity: myhost
|
||||
eap: tls
|
||||
private_key: /etc/pki/tls/client.key
|
||||
# recommend vault encrypting the private key password
|
||||
# see https://docs.ansible.com/ansible/latest/user_guide/vault.html
|
||||
private_key_password: "p@55w0rD"
|
||||
client_cert: /etc/pki/tls/client.pem
|
||||
ca_cert: /etc/pki/tls/cacert.pem
|
||||
domain_suffix_match: example.com
|
||||
|
||||
# certs have to be deployed first
|
||||
pre_tasks:
|
||||
- name: copy certs/keys for 802.1x auth
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: "/etc/pki/tls/{{ item }}"
|
||||
with_items:
|
||||
- client.key
|
||||
- client.pem
|
||||
- cacert.pem
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
29
roles/linux-system-roles.network/examples/eth_with_vlan.yml
Normal file
29
roles/linux-system-roles.network/examples/eth_with_vlan.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
|
||||
# Create a profile for the underlying device of the VLAN.
|
||||
- name: prod2
|
||||
type: ethernet
|
||||
autoconnect: no
|
||||
state: up
|
||||
interface_name: "{{ network_interface_name2 }}"
|
||||
ip:
|
||||
dhcp4: no
|
||||
auto6: no
|
||||
|
||||
# on top of it, create a VLAN with ID 100 and static
|
||||
# addressing
|
||||
- name: prod2.100
|
||||
state: up
|
||||
type: vlan
|
||||
parent: prod2
|
||||
vlan_id: 100
|
||||
ip:
|
||||
address:
|
||||
- "192.0.2.{{ network_iphost }}/24"
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
@@ -0,0 +1,38 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
- include_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ network_interface_name1 }}"
|
||||
state: up
|
||||
type: ethernet
|
||||
ip:
|
||||
dhcp4: no
|
||||
auto6: no
|
||||
ethtool:
|
||||
coalesce:
|
||||
adaptive_rx: yes
|
||||
adaptive_tx: no
|
||||
pkt_rate_high: 128
|
||||
pkt_rate_low: 128
|
||||
rx_frames: 128
|
||||
rx_frames_high: 128
|
||||
rx_frames_irq: 128
|
||||
rx_frames_low: 128
|
||||
rx_usecs: 128
|
||||
rx_usecs_high: 128
|
||||
rx_usecs_irq: 128
|
||||
rx_usecs_low: 128
|
||||
sample_interval: 128
|
||||
stats_block_usecs: 128
|
||||
tx_frames: 128
|
||||
tx_frames_high: 128
|
||||
tx_frames_irq: 128
|
||||
tx_frames_low: 128
|
||||
tx_usecs: 128
|
||||
tx_usecs_high: 128
|
||||
tx_usecs_irq: 128
|
||||
tx_usecs_low: 128
|
||||
@@ -0,0 +1,19 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
- include_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ network_interface_name1 }}"
|
||||
state: up
|
||||
type: ethernet
|
||||
ip:
|
||||
dhcp4: "no"
|
||||
auto6: "no"
|
||||
ethtool:
|
||||
features:
|
||||
gro: "no"
|
||||
gso: "yes"
|
||||
tx_sctp_segmentation: "no"
|
||||
@@ -0,0 +1,14 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
- include_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ network_interface_name1 }}"
|
||||
state: up
|
||||
type: ethernet
|
||||
ip:
|
||||
dhcp4: "no"
|
||||
auto6: "no"
|
||||
12
roles/linux-system-roles.network/examples/ipv6_disabled.yml
Normal file
12
roles/linux-system-roles.network/examples/ipv6_disabled.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
vars:
|
||||
network_connections:
|
||||
- name: eth0
|
||||
type: ethernet
|
||||
ip:
|
||||
ipv6_disabled: true
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
...
|
||||
@@ -0,0 +1,12 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- name: Set {{ profile }} down
|
||||
hosts: all
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ profile }}"
|
||||
persistent_state: absent
|
||||
state: down
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
...
|
||||
1
roles/linux-system-roles.network/examples/remove_profile.yml
Symbolic link
1
roles/linux-system-roles.network/examples/remove_profile.yml
Symbolic link
@@ -0,0 +1 @@
|
||||
../tests/playbooks/remove_profile.yml
|
||||
33
roles/linux-system-roles.network/examples/team_simple.yml
Normal file
33
roles/linux-system-roles.network/examples/team_simple.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
# Specify the team profile
|
||||
- name: team0
|
||||
state: up
|
||||
type: team
|
||||
interface_name: team0
|
||||
# ip configuration (optional)
|
||||
ip:
|
||||
address:
|
||||
- "192.0.2.24/24"
|
||||
- "2001:db8::23/64"
|
||||
|
||||
# add an team profile to the team
|
||||
- name: member1
|
||||
state: up
|
||||
type: ethernet
|
||||
interface_name: eth1
|
||||
controller: team0
|
||||
|
||||
# add a second team profile to the team
|
||||
- name: member2
|
||||
state: up
|
||||
type: ethernet
|
||||
interface_name: eth2
|
||||
controller: team0
|
||||
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
...
|
||||
@@ -0,0 +1,15 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: network-test
|
||||
vars:
|
||||
network_connections:
|
||||
- name: wlan0
|
||||
type: wireless
|
||||
wireless:
|
||||
ssid: "My WPA2-PSK Network"
|
||||
key_mgmt: "wpa-psk"
|
||||
# recommend vault encrypting the wireless password
|
||||
# see https://docs.ansible.com/ansible/latest/user_guide/vault.html
|
||||
password: "p@55w0rD"
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
@@ -1,2 +1,2 @@
|
||||
install_date: Tue Apr 20 16:13:56 2021
|
||||
install_date: Wed Apr 21 16:48:45 2021
|
||||
version: 1.3.0
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import array
|
||||
import struct
|
||||
import fcntl
|
||||
import socket
|
||||
|
||||
from .utils import Util
|
||||
|
||||
ETHTOOL_GPERMADDR = 0x00000020
|
||||
SIOCETHTOOL = 0x8946
|
||||
MAX_ADDR_LEN = 32
|
||||
IFNAMESIZ = 16
|
||||
|
||||
|
||||
def get_perm_addr(ifname):
|
||||
"""
|
||||
Return the Permanent address value for the specified interface using the
|
||||
ETHTOOL_GPERMADDR ioctl command.
|
||||
|
||||
Please for further documentation, see:
|
||||
https://github.com/torvalds/linux/blob/master/include/uapi/linux/ethtool.h#L734
|
||||
https://github.com/torvalds/linux/blob/master/include/uapi/linux/ethtool.h#L1388
|
||||
https://git.kernel.org/pub/scm/network/ethtool/ethtool.git/tree/ethtool.c#n4172
|
||||
"""
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sockfd = sock.fileno()
|
||||
ifname = ifname.encode("utf-8")
|
||||
if len(ifname) > IFNAMESIZ:
|
||||
return None
|
||||
|
||||
ecmd = array.array(
|
||||
"B",
|
||||
struct.pack(
|
||||
"II%is" % MAX_ADDR_LEN,
|
||||
ETHTOOL_GPERMADDR,
|
||||
MAX_ADDR_LEN,
|
||||
b"\x00" * MAX_ADDR_LEN,
|
||||
),
|
||||
)
|
||||
ifreq = struct.pack("%isP" % IFNAMESIZ, ifname, ecmd.buffer_info()[0])
|
||||
|
||||
fcntl.ioctl(sockfd, SIOCETHTOOL, ifreq)
|
||||
try:
|
||||
res = ecmd.tobytes()
|
||||
except AttributeError: # tobytes() is not available in python2
|
||||
res = ecmd.tostring()
|
||||
_, size, perm_addr = struct.unpack("II%is" % MAX_ADDR_LEN, res)
|
||||
perm_addr = Util.mac_ntoa(perm_addr[:size])
|
||||
except IOError:
|
||||
perm_addr = None
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
return perm_addr
|
||||
@@ -0,0 +1,7 @@
|
||||
# Relative import is not support by ansible 2.8 yet
|
||||
# pylint: disable=import-error, no-name-in-module
|
||||
from ansible.module_utils.network_lsr.nm import provider # noqa:E501
|
||||
|
||||
# pylint: enable=import-error, no-name-in-module
|
||||
|
||||
provider.NetworkManagerProvider
|
||||
@@ -0,0 +1,128 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# Handle NM.ActiveConnection
|
||||
|
||||
import logging
|
||||
|
||||
# Relative import is not support by ansible 2.8 yet
|
||||
# pylint: disable=import-error, no-name-in-module
|
||||
from ansible.module_utils.network_lsr.nm import client # noqa:E501
|
||||
from ansible.module_utils.network_lsr.nm import error # noqa:E501
|
||||
|
||||
# pylint: enable=import-error, no-name-in-module
|
||||
|
||||
|
||||
NM_AC_STATE_CHANGED_SIGNAL = "state-changed"
|
||||
|
||||
|
||||
def deactivate_active_connection(nm_ac, timeout, check_mode):
|
||||
if not nm_ac or nm_ac.props.state == client.NM.ActiveConnectionState.DEACTIVATED:
|
||||
logging.info("Connection is not active, no need to deactivate")
|
||||
return False
|
||||
if not check_mode:
|
||||
main_loop = client.get_mainloop(timeout)
|
||||
logging.debug(
|
||||
"Deactivating {id} with timeout {timeout}".format(
|
||||
id=nm_ac.get_id(), timeout=timeout
|
||||
)
|
||||
)
|
||||
user_data = main_loop
|
||||
handler_id = nm_ac.connect(
|
||||
NM_AC_STATE_CHANGED_SIGNAL, _nm_ac_state_change_callback, user_data
|
||||
)
|
||||
logging.debug(
|
||||
"Registered {signal} on client.NM.ActiveConnection {id}".format(
|
||||
signal=NM_AC_STATE_CHANGED_SIGNAL, id=nm_ac.get_id()
|
||||
)
|
||||
)
|
||||
if nm_ac.props.state != client.NM.ActiveConnectionState.DEACTIVATING:
|
||||
nm_client = client.get_client()
|
||||
user_data = (main_loop, nm_ac, nm_ac.get_id(), handler_id)
|
||||
nm_client.deactivate_connection_async(
|
||||
nm_ac,
|
||||
main_loop.cancellable,
|
||||
_nm_ac_deactivate_call_back,
|
||||
user_data,
|
||||
)
|
||||
logging.debug(
|
||||
"Deactivating client.NM.ActiveConnection {0}".format(nm_ac.get_id())
|
||||
)
|
||||
main_loop.run()
|
||||
return True
|
||||
|
||||
|
||||
def _nm_ac_state_change_callback(nm_ac, state, reason, user_data):
|
||||
main_loop = user_data
|
||||
if main_loop.is_cancelled:
|
||||
return
|
||||
logging.debug(
|
||||
"Got client.NM.ActiveConnection state change: {id}: {state} {reason}".format(
|
||||
id=nm_ac.get_id(), state=state, reason=reason
|
||||
)
|
||||
)
|
||||
if nm_ac.props.state == client.NM.ActiveConnectionState.DEACTIVATED:
|
||||
logging.debug(
|
||||
"client.NM.ActiveConnection {0} is deactivated".format(nm_ac.get_id())
|
||||
)
|
||||
main_loop.quit()
|
||||
|
||||
|
||||
def _nm_ac_deactivate_call_back(nm_client, result, user_data):
|
||||
main_loop, nm_ac, nm_ac_id, handler_id = user_data
|
||||
logging.debug("client.NM.ActiveConnection deactivating callback")
|
||||
if main_loop.is_cancelled:
|
||||
if nm_ac:
|
||||
nm_ac.handler_disconnect(handler_id)
|
||||
return
|
||||
|
||||
try:
|
||||
success = nm_client.deactivate_connection_finish(result)
|
||||
except client.GLib.Error as e:
|
||||
if e.matches(
|
||||
client.NM.ManagerError.quark(), client.NM.ManagerError.CONNECTIONNOTACTIVE
|
||||
):
|
||||
logging.info(
|
||||
"Connection is not active on {0}, no need to deactivate".format(
|
||||
nm_ac_id
|
||||
)
|
||||
)
|
||||
if nm_ac:
|
||||
nm_ac.handler_disconnect(handler_id)
|
||||
main_loop.quit()
|
||||
return
|
||||
else:
|
||||
_deactivate_fail(
|
||||
main_loop,
|
||||
handler_id,
|
||||
nm_ac,
|
||||
"Failed to deactivate connection {id}, error={error}".format(
|
||||
id=nm_ac_id, error=e
|
||||
),
|
||||
)
|
||||
return
|
||||
except Exception as e:
|
||||
_deactivate_fail(
|
||||
main_loop,
|
||||
handler_id,
|
||||
nm_ac,
|
||||
"Failed to deactivate connection {id}, error={error}".format(
|
||||
id=nm_ac_id, error=e
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
if not success:
|
||||
_deactivate_fail(
|
||||
main_loop,
|
||||
handler_id,
|
||||
nm_ac,
|
||||
"Failed to deactivate connection {0}, error='None "
|
||||
"returned from deactivate_connection_finish()'".format(nm_ac_id),
|
||||
)
|
||||
|
||||
|
||||
def _deactivate_fail(main_loop, handler_id, nm_ac, msg):
|
||||
if nm_ac:
|
||||
nm_ac.handler_disconnect(handler_id)
|
||||
logging.error(msg)
|
||||
main_loop.fail(error.LsrNetworkNmError(msg))
|
||||
@@ -0,0 +1,96 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import logging
|
||||
|
||||
# Relative import is not support by ansible 2.8 yet
|
||||
# pylint: disable=import-error, no-name-in-module
|
||||
from ansible.module_utils.network_lsr.nm import error # noqa:E501
|
||||
|
||||
import gi
|
||||
|
||||
try:
|
||||
gi.require_version("NM", "1.0")
|
||||
|
||||
# It is required to state the NM version before importing it
|
||||
# But this break the flake8 rule: https://www.flake8rules.com/rules/E402.html
|
||||
# Use NOQA: E402 to suppress it.
|
||||
from gi.repository import NM # NOQA: E402
|
||||
from gi.repository import GLib # NOQA: E402
|
||||
from gi.repository import Gio # NOQA: E402
|
||||
|
||||
# pylint: enable=import-error, no-name-in-module
|
||||
|
||||
NM
|
||||
GLib
|
||||
Gio
|
||||
except ValueError:
|
||||
# This is to workaround a bug in ansible 2.9 which causes
|
||||
# this code to be executed on the control node, where NM
|
||||
# is not guaranteed to exist. On the other hand, it is
|
||||
# ensured on the managed nodes as NM package is installed
|
||||
# in the network role. Therefore, this exception handling
|
||||
# does not affect the network installation and configuration
|
||||
# on the managed nodes.
|
||||
pass
|
||||
|
||||
|
||||
def get_client():
|
||||
return NM.Client.new()
|
||||
|
||||
|
||||
class _NmMainLoop(object):
|
||||
def __init__(self, timeout):
|
||||
self._mainloop = GLib.MainLoop()
|
||||
self._cancellable = Gio.Cancellable.new()
|
||||
self._timeout = timeout
|
||||
self._timeout_id = None
|
||||
|
||||
def run(self):
|
||||
logging.debug("NM mainloop running")
|
||||
user_data = None
|
||||
self._timeout_id = GLib.timeout_add(
|
||||
int(self._timeout * 1000),
|
||||
self._timeout_call_back,
|
||||
user_data,
|
||||
)
|
||||
logging.debug("Added timeout checker")
|
||||
self._mainloop.run()
|
||||
|
||||
def _timeout_call_back(self, _user_data):
|
||||
logging.error("Timeout")
|
||||
self.fail(error.LsrNetworkNmError("Timeout"))
|
||||
|
||||
@property
|
||||
def cancellable(self):
|
||||
return self._cancellable
|
||||
|
||||
@property
|
||||
def is_cancelled(self):
|
||||
if self._cancellable:
|
||||
return self._cancellable.is_cancelled()
|
||||
return True
|
||||
|
||||
def _clean_up(self):
|
||||
logging.debug("NM mainloop cleaning up")
|
||||
if self._timeout_id:
|
||||
logging.debug("Removing timeout checker")
|
||||
GLib.source_remove(self._timeout_id)
|
||||
self._timeout_id = None
|
||||
if self._cancellable:
|
||||
logging.debug("Canceling all pending tasks")
|
||||
self._cancellable.cancel()
|
||||
self._cancellable = None
|
||||
self._mainloop = None
|
||||
|
||||
def quit(self):
|
||||
logging.debug("NM mainloop quiting")
|
||||
self._mainloop.quit()
|
||||
self._clean_up()
|
||||
|
||||
def fail(self, exception):
|
||||
self.quit()
|
||||
raise exception
|
||||
|
||||
|
||||
def get_mainloop(timeout):
|
||||
return _NmMainLoop(timeout)
|
||||
@@ -0,0 +1,113 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# Handle NM.RemoteConnection
|
||||
|
||||
import logging
|
||||
|
||||
# Relative import is not support by ansible 2.8 yet
|
||||
# pylint: disable=import-error, no-name-in-module
|
||||
from ansible.module_utils.network_lsr.nm import client # noqa:E501
|
||||
from ansible.module_utils.network_lsr.nm import error # noqa:E501
|
||||
|
||||
# pylint: enable=import-error, no-name-in-module
|
||||
|
||||
|
||||
def delete_remote_connection(nm_profile, timeout, check_mode):
|
||||
if not nm_profile:
|
||||
logging.info("NULL NM.RemoteConnection, no need to delete")
|
||||
return False
|
||||
|
||||
if not check_mode:
|
||||
main_loop = client.get_mainloop(timeout)
|
||||
user_data = main_loop
|
||||
nm_profile.delete_async(
|
||||
main_loop.cancellable,
|
||||
_nm_profile_delete_call_back,
|
||||
user_data,
|
||||
)
|
||||
logging.debug(
|
||||
"Deleting profile {id}/{uuid} with timeout {timeout}".format(
|
||||
id=nm_profile.get_id(), uuid=nm_profile.get_uuid(), timeout=timeout
|
||||
)
|
||||
)
|
||||
main_loop.run()
|
||||
return True
|
||||
|
||||
|
||||
def _nm_profile_delete_call_back(nm_profile, result, user_data):
|
||||
main_loop = user_data
|
||||
if main_loop.is_cancelled:
|
||||
return
|
||||
|
||||
try:
|
||||
success = nm_profile.delete_finish(result)
|
||||
except Exception as e:
|
||||
main_loop.fail(
|
||||
error.LsrNetworkNmError(
|
||||
"Connection deletion aborted on {id}/{uuid}: error={error}".format(
|
||||
id=nm_profile.get_id(), uuid=nm_profile.get_uuid(), error=e
|
||||
)
|
||||
)
|
||||
)
|
||||
if success:
|
||||
main_loop.quit()
|
||||
else:
|
||||
main_loop.fail(
|
||||
error.LsrNetworkNmError(
|
||||
"Connection deletion aborted on {id}/{uuid}: error=unknown".format(
|
||||
id=nm_profile.get_id(), uuid=nm_profile.get_uuid()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def volatilize_remote_connection(nm_profile, timeout, check_mode):
|
||||
if not nm_profile:
|
||||
logging.info("NULL NM.RemoteConnection, no need to volatilize")
|
||||
return False
|
||||
if not check_mode:
|
||||
main_loop = client.get_mainloop(timeout)
|
||||
user_data = main_loop
|
||||
nm_profile.update2(
|
||||
None, # settings
|
||||
client.NM.SettingsUpdate2Flags.IN_MEMORY_ONLY
|
||||
| client.NM.SettingsUpdate2Flags.VOLATILE,
|
||||
None, # args
|
||||
main_loop.cancellable,
|
||||
_nm_profile_volatile_update2_call_back,
|
||||
user_data,
|
||||
)
|
||||
logging.debug(
|
||||
"Volatilizing profile {id}/{uuid} with timeout {timeout}".format(
|
||||
id=nm_profile.get_id(), uuid=nm_profile.get_uuid(), timeout=timeout
|
||||
)
|
||||
)
|
||||
main_loop.run()
|
||||
return True
|
||||
|
||||
|
||||
def _nm_profile_volatile_update2_call_back(nm_profile, result, user_data):
|
||||
main_loop = user_data
|
||||
if main_loop.is_cancelled:
|
||||
return
|
||||
|
||||
try:
|
||||
success = nm_profile.update2_finish(result)
|
||||
except Exception as e:
|
||||
main_loop.fail(
|
||||
error.LsrNetworkNmError(
|
||||
"Connection volatilize aborted on {id}/{uuid}: error={error}".format(
|
||||
id=nm_profile.get_id(), uuid=nm_profile.get_uuid(), error=e
|
||||
)
|
||||
)
|
||||
)
|
||||
if success:
|
||||
main_loop.quit()
|
||||
else:
|
||||
main_loop.fail(
|
||||
error.LsrNetworkNmError(
|
||||
"Connection volatilize aborted on {id}/{uuid}: error=unknown".format(
|
||||
id=nm_profile.get_id(), uuid=nm_profile.get_uuid()
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
|
||||
class LsrNetworkNmError(Exception):
|
||||
pass
|
||||
@@ -0,0 +1,58 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import logging
|
||||
|
||||
# Relative import is not support by ansible 2.8 yet
|
||||
# pylint: disable=import-error, no-name-in-module
|
||||
from ansible.module_utils.network_lsr.nm import active_connection # noqa:E501
|
||||
from ansible.module_utils.network_lsr.nm import client # noqa:E501
|
||||
from ansible.module_utils.network_lsr.nm import connection # noqa:E501
|
||||
|
||||
# pylint: enable=import-error, no-name-in-module
|
||||
|
||||
|
||||
class NetworkManagerProvider:
|
||||
def deactivate_connection(self, connection_name, timeout, check_mode):
|
||||
"""
|
||||
Return True if changed.
|
||||
"""
|
||||
nm_client = client.get_client()
|
||||
changed = False
|
||||
for nm_ac in nm_client.get_active_connections():
|
||||
nm_profile = nm_ac.get_connection()
|
||||
if nm_profile and nm_profile.get_id() == connection_name:
|
||||
changed |= active_connection.deactivate_active_connection(
|
||||
nm_ac, timeout, check_mode
|
||||
)
|
||||
if not changed:
|
||||
logging.info("No active connection for {0}".format(connection_name))
|
||||
|
||||
return changed
|
||||
|
||||
def volatilize_connection_by_uuid(self, uuid, timeout, check_mode):
|
||||
"""
|
||||
Mark NM.RemoteConnection as volatile(delete on deactivation) via Update2,
|
||||
if not supported, delete the profile.
|
||||
|
||||
Return True if changed.
|
||||
"""
|
||||
nm_client = client.get_client()
|
||||
changed = False
|
||||
for nm_profile in nm_client.get_connections():
|
||||
if nm_profile and nm_profile.get_uuid() == uuid:
|
||||
if hasattr(nm_profile, "update2"):
|
||||
changed |= connection.volatilize_remote_connection(
|
||||
nm_profile, timeout, check_mode
|
||||
)
|
||||
else:
|
||||
changed |= connection.delete_remote_connection(
|
||||
nm_profile, timeout, check_mode
|
||||
)
|
||||
if not changed:
|
||||
logging.info("No connection with UUID {0} to volatilize".format(uuid))
|
||||
|
||||
return changed
|
||||
|
||||
def get_connections(self):
|
||||
nm_client = client.get_client()
|
||||
return nm_client.get_connections()
|
||||
@@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Write extra requirements for running molecule here:
|
||||
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Write extra requirements for running pylint here:
|
||||
mock
|
||||
pytest
|
||||
@@ -0,0 +1,12 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Write extra requirements for running pytest here:
|
||||
# If you need ansible then uncomment the following line:
|
||||
#-ransible_pytest_extra_requirements.txt
|
||||
# If you need mock then uncomment the following line:
|
||||
mock ; python_version < "3.0"
|
||||
# ansible and dependencies for all supported platforms
|
||||
ansible ; python_version > "2.6"
|
||||
ansible<2.7 ; python_version < "2.7"
|
||||
idna<2.8 ; python_version < "2.7"
|
||||
PyYAML<5.1 ; python_version < "2.7"
|
||||
184
roles/linux-system-roles.network/scripts/print_all_options.py
Executable file
184
roles/linux-system-roles.network/scripts/print_all_options.py
Executable file
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/python3 -tt
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# Helper to print all options that the module in the network role accepts for
|
||||
# profiles
|
||||
|
||||
from collections.abc import Mapping
|
||||
from collections.abc import Sequence
|
||||
from copy import deepcopy
|
||||
from unittest import mock
|
||||
import os
|
||||
import sys
|
||||
|
||||
PRIORITIES = (
|
||||
"name",
|
||||
"type",
|
||||
"interface_name",
|
||||
"mac",
|
||||
"state",
|
||||
"persistent_state",
|
||||
"controller",
|
||||
"port_type",
|
||||
"parent",
|
||||
"ignore_errors",
|
||||
"force_state_change",
|
||||
"check_iface_exists",
|
||||
"autoconnect",
|
||||
"wait",
|
||||
"zone",
|
||||
"mtu",
|
||||
"ip",
|
||||
"ethernet",
|
||||
"ethtool",
|
||||
"bridge",
|
||||
"bond",
|
||||
"team",
|
||||
"vlan",
|
||||
"wireless",
|
||||
"macvlan",
|
||||
"infiniband",
|
||||
)
|
||||
|
||||
|
||||
import yaml
|
||||
|
||||
parentdir = os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
with mock.patch.object(
|
||||
sys,
|
||||
"path",
|
||||
[parentdir, os.path.join(parentdir, "module_utils/network_lsr")] + sys.path,
|
||||
):
|
||||
with mock.patch.dict(
|
||||
"sys.modules",
|
||||
{"ansible": mock.Mock(), "ansible.module_utils": __import__("module_utils")},
|
||||
):
|
||||
import argument_validator as av
|
||||
|
||||
COMMENT = "@@"
|
||||
EMPTY = "/EMPTY/"
|
||||
|
||||
|
||||
def parse_validator(validator):
|
||||
default = validator.default_value
|
||||
if isinstance(validator, av.ArgValidatorDict):
|
||||
res = {}
|
||||
for k, v in validator.nested.items():
|
||||
if (
|
||||
v.name
|
||||
not in (
|
||||
"infiniband_transport_mode",
|
||||
"infiniband_p_key",
|
||||
"vlan_id",
|
||||
)
|
||||
and not isinstance(v, av.ArgValidatorDeprecated)
|
||||
):
|
||||
name = k
|
||||
if not validator.required:
|
||||
pass
|
||||
# name += " DICT optional"
|
||||
res[name] = parse_validator(v)
|
||||
elif isinstance(validator, av.ArgValidatorList):
|
||||
res = [parse_validator(validator.nested)]
|
||||
elif isinstance(validator, av.ArgValidatorNum):
|
||||
|
||||
minval = validator.val_min
|
||||
maxval = validator.val_max
|
||||
comment = f" {COMMENT}"
|
||||
if not validator.required:
|
||||
comment += " optional"
|
||||
if minval is not None:
|
||||
comment += " mininum=" + str(minval)
|
||||
if maxval:
|
||||
if maxval == 0xFFFFFFFF:
|
||||
maxval = hex(maxval)
|
||||
comment += " maximum=" + str(maxval)
|
||||
|
||||
if default is not None:
|
||||
res = str(default)
|
||||
elif minval is not None:
|
||||
res = str(minval)
|
||||
elif maxval is not None:
|
||||
res = str(maxval)
|
||||
else:
|
||||
res = ""
|
||||
|
||||
res += comment
|
||||
elif isinstance(validator, av.ArgValidatorIP):
|
||||
res = f"{EMPTY} {COMMENT} IP Address"
|
||||
elif isinstance(validator, av.ArgValidatorStr):
|
||||
if default:
|
||||
res = default
|
||||
elif validator.enum_values:
|
||||
res = "|".join(validator.enum_values)
|
||||
else:
|
||||
res = EMPTY
|
||||
if not validator.required:
|
||||
res += f" {COMMENT} optional"
|
||||
|
||||
# res += " " + str(validator.__class__)
|
||||
elif isinstance(validator, av.ArgValidatorBool):
|
||||
if default is not None:
|
||||
res = "yes" if default else "no"
|
||||
else:
|
||||
res = "yes|no"
|
||||
|
||||
if not validator.required:
|
||||
res += f" {COMMENT} optional"
|
||||
else:
|
||||
res = validator.name + f" {COMMENT} FIXME " + str(validator.__class__)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def represent_dict(dumper, data):
|
||||
"""
|
||||
Represent dictionary with insert order
|
||||
"""
|
||||
value = []
|
||||
|
||||
for item_key, item_value in data.items():
|
||||
node_key = dumper.represent_data(item_key)
|
||||
node_value = dumper.represent_data(item_value)
|
||||
value.append((node_key, node_value))
|
||||
|
||||
return yaml.nodes.MappingNode("tag:yaml.org,2002:map", value)
|
||||
|
||||
|
||||
def priority_sorted(data):
|
||||
if isinstance(data, Sequence) and not isinstance(data, str):
|
||||
return [priority_sorted(item) for item in data]
|
||||
|
||||
if isinstance(data, Mapping):
|
||||
sorted_data = {}
|
||||
for key in sorted(data, key=prioritize):
|
||||
sorted_data[key] = priority_sorted(data[key])
|
||||
return sorted_data
|
||||
|
||||
return deepcopy(data)
|
||||
|
||||
|
||||
def prioritize(key):
|
||||
try:
|
||||
priority = PRIORITIES.index(key)
|
||||
except ValueError:
|
||||
priority = len(PRIORITIES)
|
||||
return (priority, key)
|
||||
|
||||
|
||||
yaml.add_representer(dict, represent_dict)
|
||||
sorted_data = priority_sorted([parse_validator(av.ArgValidator_DictConnection())])
|
||||
yaml_example = (
|
||||
yaml.dump(
|
||||
sorted_data,
|
||||
explicit_start=True,
|
||||
default_flow_style=False,
|
||||
width=100,
|
||||
)
|
||||
.replace(COMMENT, "#")
|
||||
.replace(EMPTY, "")
|
||||
)
|
||||
|
||||
# yaml_example = re.sub(r"# ([^:]*):", r": # \1", yaml_example)
|
||||
|
||||
print(yaml_example)
|
||||
227
roles/linux-system-roles.network/tests/ensure_provider_tests.py
Executable file
227
roles/linux-system-roles.network/tests/ensure_provider_tests.py
Executable file
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
""" Check that there is a playbook to run all role tests with both providers
|
||||
"""
|
||||
# vim: fileencoding=utf8
|
||||
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
GET_NM_VERSION = """
|
||||
- block:
|
||||
- name: Install NetworkManager
|
||||
package:
|
||||
name: NetworkManager
|
||||
state: present
|
||||
- name: Get NetworkManager version
|
||||
command: rpm -q --qf "%{version}" NetworkManager
|
||||
args:
|
||||
warn: false
|
||||
register: NetworkManager_version
|
||||
when: true
|
||||
when:
|
||||
- ansible_distribution_major_version != '6'
|
||||
tags:
|
||||
- always
|
||||
"""
|
||||
|
||||
MINIMUM_NM_VERSION_CHECK = """
|
||||
- NetworkManager_version.stdout is version({minimum_nm_version}, '>=')
|
||||
"""
|
||||
|
||||
EXTRA_RUN_CONDITION_PREFIX = " - "
|
||||
|
||||
RUN_PLAYBOOK_WITH_NM = """# SPDX-License-Identifier: BSD-3-Clause
|
||||
# This file was generated by ensure_provider_tests.py
|
||||
---
|
||||
# set network provider and gather facts
|
||||
- hosts: all
|
||||
name: Run playbook '{test_playbook}' with nm as provider
|
||||
tasks:
|
||||
- name: Set network provider to 'nm'
|
||||
set_fact:
|
||||
network_provider: nm
|
||||
tags:
|
||||
- always
|
||||
{get_nm_version}
|
||||
|
||||
# The test requires or should run with NetworkManager, therefore it cannot run
|
||||
# on RHEL/CentOS 6
|
||||
- import_playbook: {test_playbook}
|
||||
when:
|
||||
- ansible_distribution_major_version != '6'
|
||||
{minimum_nm_version_check}{extra_run_condition}"""
|
||||
|
||||
MINIMUM_VERSION = "minimum_version"
|
||||
EXTRA_RUN_CONDITION = "extra_run_condition"
|
||||
NM_ONLY_TESTS = {
|
||||
"playbooks/tests_802_1x_updated.yml": {},
|
||||
"playbooks/tests_802_1x.yml": {},
|
||||
"playbooks/tests_eth_dns_support.yml": {},
|
||||
"playbooks/tests_dummy.yml": {},
|
||||
"playbooks/tests_ethtool_features.yml": {
|
||||
MINIMUM_VERSION: "'1.20.0'",
|
||||
"comment": "# NetworkManager 1.20.0 introduced ethtool settings support",
|
||||
},
|
||||
"playbooks/tests_ipv6_disabled.yml": {
|
||||
EXTRA_RUN_CONDITION: "ansible_distribution_major_version == '8'",
|
||||
},
|
||||
"playbooks/tests_provider.yml": {
|
||||
MINIMUM_VERSION: "'1.20.0'",
|
||||
"comment": "# NetworKmanager 1.20.0 added support for forgetting profiles",
|
||||
},
|
||||
"playbooks/tests_ethtool_coalesce.yml": {
|
||||
MINIMUM_VERSION: "'1.25.1'",
|
||||
"comment": "# NetworkManager 1.25.1 introduced ethtool coalesce support",
|
||||
},
|
||||
"playbooks/tests_802_1x_updated.yml": {},
|
||||
"playbooks/tests_802_1x.yml": {},
|
||||
"playbooks/tests_reapply.yml": {},
|
||||
# team interface is not supported on Fedora
|
||||
"playbooks/tests_team.yml": {
|
||||
EXTRA_RUN_CONDITION: "ansible_distribution != 'Fedora'",
|
||||
},
|
||||
"playbooks/tests_team_plugin_installation.yml": {},
|
||||
# mac80211_hwsim (used for tests_wireless) only seems to be available
|
||||
# and working on RHEL/CentOS 7
|
||||
"playbooks/tests_wireless.yml": {
|
||||
EXTRA_RUN_CONDITION: "ansible_distribution_major_version == '7'",
|
||||
},
|
||||
"playbooks/tests_wireless_plugin_installation.yml": {},
|
||||
}
|
||||
|
||||
IGNORE = [
|
||||
# checked by tests_regression_nm.yml
|
||||
"playbooks/tests_checkpoint_cleanup.yml",
|
||||
]
|
||||
|
||||
RUN_PLAYBOOK_WITH_INITSCRIPTS = """# SPDX-License-Identifier: BSD-3-Clause
|
||||
# This file was generated by ensure_provider_tests.py
|
||||
---
|
||||
- hosts: all
|
||||
name: Run playbook '{test_playbook}' with initscripts as provider
|
||||
tasks:
|
||||
- name: Set network provider to 'initscripts'
|
||||
set_fact:
|
||||
network_provider: initscripts
|
||||
tags:
|
||||
- always
|
||||
|
||||
- import_playbook: {test_playbook}
|
||||
"""
|
||||
|
||||
|
||||
def create_nm_playbook(test_playbook):
|
||||
fileroot = os.path.splitext(os.path.basename(test_playbook))[0]
|
||||
nm_testfile = fileroot + "_nm.yml"
|
||||
|
||||
minimum_nm_version = NM_ONLY_TESTS.get(test_playbook, {}).get(MINIMUM_VERSION)
|
||||
extra_run_condition = NM_ONLY_TESTS.get(test_playbook, {}).get(
|
||||
EXTRA_RUN_CONDITION, ""
|
||||
)
|
||||
if extra_run_condition:
|
||||
extra_run_condition = "{}{}\n".format(
|
||||
EXTRA_RUN_CONDITION_PREFIX, extra_run_condition
|
||||
)
|
||||
|
||||
nm_version_check = ""
|
||||
if minimum_nm_version:
|
||||
nm_version_check = MINIMUM_NM_VERSION_CHECK.format(
|
||||
minimum_nm_version=minimum_nm_version
|
||||
)
|
||||
|
||||
nominal_nm_testfile_data = RUN_PLAYBOOK_WITH_NM.format(
|
||||
test_playbook=test_playbook,
|
||||
get_nm_version=minimum_nm_version and GET_NM_VERSION or "",
|
||||
minimum_nm_version_check=nm_version_check,
|
||||
extra_run_condition=extra_run_condition,
|
||||
)
|
||||
|
||||
return nm_testfile, nominal_nm_testfile_data
|
||||
|
||||
|
||||
def create_initscripts_playbook(test_playbook):
|
||||
fileroot = os.path.splitext(os.path.basename(test_playbook))[0]
|
||||
init_testfile = fileroot + "_initscripts.yml"
|
||||
|
||||
nominal_data = RUN_PLAYBOOK_WITH_INITSCRIPTS.format(test_playbook=test_playbook)
|
||||
|
||||
return init_testfile, nominal_data
|
||||
|
||||
|
||||
def check_playbook(generate, testfile, test_playbook, nominal_data):
|
||||
is_missing = False
|
||||
returncode = None
|
||||
if generate:
|
||||
print(testfile)
|
||||
with open(testfile, "w") as ofile:
|
||||
ofile.write(nominal_data)
|
||||
|
||||
if not os.path.isfile(testfile) and not generate:
|
||||
is_missing = True
|
||||
else:
|
||||
with open(testfile) as ifile:
|
||||
testdata = ifile.read()
|
||||
if testdata != nominal_data:
|
||||
print(f"ERROR: Playbook does not match nominal value: {testfile}")
|
||||
returncode = 1
|
||||
|
||||
return is_missing, returncode
|
||||
|
||||
|
||||
def main():
|
||||
testsfiles = glob.glob("playbooks/tests_*.yml")
|
||||
missing = []
|
||||
returncode = 0
|
||||
|
||||
# Generate files when specified
|
||||
generate = bool(len(sys.argv) > 1 and sys.argv[1] == "generate")
|
||||
|
||||
if not testsfiles:
|
||||
print("ERROR: No tests found")
|
||||
returncode = 1
|
||||
|
||||
for test_playbook in testsfiles:
|
||||
if test_playbook in IGNORE:
|
||||
continue
|
||||
|
||||
nm_testfile, nominal_nm_testfile_data = create_nm_playbook(test_playbook)
|
||||
|
||||
is_missing, new_returncode = check_playbook(
|
||||
generate=generate,
|
||||
testfile=nm_testfile,
|
||||
test_playbook=test_playbook,
|
||||
nominal_data=nominal_nm_testfile_data,
|
||||
)
|
||||
if is_missing:
|
||||
missing.append(test_playbook)
|
||||
if new_returncode:
|
||||
returncode = new_returncode
|
||||
|
||||
if test_playbook not in NM_ONLY_TESTS:
|
||||
init_testfile, nominal_init_testfile_data = create_initscripts_playbook(
|
||||
test_playbook
|
||||
)
|
||||
is_missing, new_returncode = check_playbook(
|
||||
generate=generate,
|
||||
testfile=init_testfile,
|
||||
test_playbook=test_playbook,
|
||||
nominal_data=nominal_init_testfile_data,
|
||||
)
|
||||
if is_missing:
|
||||
missing.append(test_playbook)
|
||||
if new_returncode:
|
||||
returncode = new_returncode
|
||||
|
||||
if missing:
|
||||
print("ERROR: No NM or initscripts tests found for:\n" + ", \n".join(missing))
|
||||
print("Try to generate them with '{} generate'".format(sys.argv[0]))
|
||||
returncode = 1
|
||||
|
||||
return returncode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
30
roles/linux-system-roles.network/tests/files/cacert.key
Normal file
30
roles/linux-system-roles.network/tests/files/cacert.key
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-256-CBC,B773C37C13C791B1B2F735A7D6D22F1D
|
||||
|
||||
KcpCACKK2i/zLDkH/e2bM/3hzyuC7UkSJ32Vn2xvH6ukKzOpt71PJjtzucY3TgB7
|
||||
T8fYDJ0OGFfW/97M9OSjY10+wo/Vn+aTTCJWe2Y0+JeoV+bFJq33fuP0SlJI1PIU
|
||||
CrxnWhFUM3iaDHjuJ32GaUCkLozKTRdb5KT0BttSdSudnT+9d6zHejCwvYEaGek0
|
||||
C3fifoN2xC47P+63UF40KWMP0+j83ZRtHXUUgQ9E0Eqmbag6jTBh2TvV/PiaWlRv
|
||||
YCVMapOBs0ktSPPJACygRJcR63MocS9of7aRaPMCDP7HpzrjzKnHqJ+bPteuaE4k
|
||||
UmVOlrBsJb4g/zpfT4Ee2waT/mKEiRtNhf8a7DNkc34I50iMqhOojM1zRPtQugO6
|
||||
5BGhFeciHCe7RzHvltWJRmLrl+H7Z8wvusxbSQRM5ZT18+wgBkgTb8dA3bmZS0Ws
|
||||
JYcd9BN8zbsxETo/IFZ2gFOaVvOymVE5mscRR21RsiBi1vfqjl+pAt4ZrlGwVpxL
|
||||
3z3yvT3lAx8Cgeg8dCxrDNb14Xwk+hkBblExLMXsUGCsRXJglk9QVPE0XjKD9XNa
|
||||
mZnBHOpAsdPun58PRiaPpC+VgaFBhzPHTyBczCG1sjpkOiTJpGLpgveAq4wOXQGH
|
||||
PMcux4ZDARYbJfGXANNqloIO3PHDPuhVmSAJZSMixDd4SLKjT6tALdqIv1BvOLl7
|
||||
Ay0y3Vie4oGc4EWjHqQA+r+6CATHHXtIOvWLJQ4/KQa/R+pTp0qDtXdOeHaAZzhv
|
||||
BpqvQUouKUyxXlGFZrGUq9l+sFtjLlcKP33Yb2WHg4ct0gAVDIA6SK4rNH6+h/NS
|
||||
rFQNOvArTeZgLCaG6htJh68WLF8p6687s4bKNM8niZ5VcsFTvMYPbfF5WdE0l53s
|
||||
fZpZBf1v03ZRJYg2V9a0sNPEysaIaTJzs5lFeya78iTF/Epo4GtTHv8sWebVwh/H
|
||||
FYINLIcPzzxAvw7a+7ymIsYZphomuEoCCoX85DPPbXfZOb2Bdysfdr7uyRsB480E
|
||||
or6+gQxZJWxcO5tMR7+G8EuUgnPMelVNczw3UJHM+sl4Kjh9q3hF4ppWFTIOaPQ/
|
||||
BL3qPE/ZxSFC8UcG+QJEbNmPPQLXnpWPUZ3GmyH/+pPUZCkcWanpn0W3chGlJCsW
|
||||
spkDMt/dpPtje1q7rfrWCVAYo4AeYzigSuxoyfpBfqcpD6wAssPQmWj4fFr91RW7
|
||||
p/iLlACpevyecALrJpU65yGWDvGWlx+dEqvdz7FRUSTkVrted/W3pmro8eDAInWx
|
||||
17VM0hHfNE00hwpGaga2CY8q3EC+3kApSE6d8dbBtSzBp4YZsGq+p+Xkj7mTc/rn
|
||||
mXJazUSPjNhWooI+0pN2VxB3HRBloNjsQOLaWVcSiv6l3wKl70ZbBjPkikO05k+v
|
||||
QXayu3i9RjXvhT974atOqoqCSigc8ROsCYGxgHjwVMU9Spc9i8y6PrgX9ID6yk9f
|
||||
9YcJjmtEi6MYh0uXNkx2m6utMjgcuAqP8yfPqeBRK2SOoLuBM9JKP8tjwq4ZBawj
|
||||
SuWe82zTRjR2oXMgNy6gBBDGky+W7kNaNw/KksZUxdiNhzeDRbDG8hMJI1HcY4xQ
|
||||
-----END RSA PRIVATE KEY-----
|
||||
21
roles/linux-system-roles.network/tests/files/cacert.pem
Normal file
21
roles/linux-system-roles.network/tests/files/cacert.pem
Normal file
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDizCCAnOgAwIBAgIUG1DftQ2xyrN+HE+KHLFmKHZnIkcwDQYJKoZIhvcNAQEL
|
||||
BQAwVDELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
|
||||
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTAgFw0yMDA1
|
||||
MDMwNTI2MTFaGA8yMjk0MDIxNTA1MjYxMVowVDELMAkGA1UEBhMCWFgxFTATBgNV
|
||||
BAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQ
|
||||
MA4GA1UEAwwHVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AMGAmO9ugnI/jaw4qNTyh/O65BNEvzOIwLU0mo3wTOSiakoOuC0gqO4S+0FOmC6v
|
||||
ceoArS+GllowzrgnnmM4EH9hqmiLeFKa4Z2graIm2W86ayN5k3psiMolONOZ8y0r
|
||||
nAMj84FifDYIOHoYbKUeN5BDsotrHbrZ/PZhlZgN1ou3gapXqM12TkXdzaj//vRd
|
||||
CORjwO1ubpzb17PFUNOLWaDf3ohfoMCG08UkGwIGK0mouJ1yflda27MCcLzmDxV8
|
||||
4dfI//R/6WtN1hzWSW9ae99VwSjlACH2go/0fDD+K9jvKkEVRZAqBEnM3voQCOah
|
||||
P9NMJ30R9Sh8B/D2KXGyIU0CAwEAAaNTMFEwHQYDVR0OBBYEFDUKdAwDiWpUpayU
|
||||
mjiWEcMcXjQdMB8GA1UdIwQYMBaAFDUKdAwDiWpUpayUmjiWEcMcXjQdMA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKEyNiDawDJeaauDUmHgdNlG
|
||||
WuBlvn4Lph/+J27njmAoIbKv3aDw+kndxI02ryCZTJOm8a1NqHfkNct4ny+Cj4cz
|
||||
rNoZIyMucVoKGgCMYb5zwYtW3W7RshUZoBdQDBLiIuktNsWTyqss3yVPPq8Q1JJY
|
||||
89dtjCNydL6dunFSrGjVJ2K5HaTyidti2IN9g2Sbxmxgoz71ZP09xmBxaY+O738M
|
||||
z5nRdrb2DX0flmv5pcqSzn7063t9FGKOp2bF9NTpcEWkultsCOvsVcsO4X/18L4J
|
||||
3W8FVltyCvunv4GQecWqlNHTRT+QI2h48EVEzHQnOGEe9q1C8WVGeQ3cZXMei8k=
|
||||
-----END CERTIFICATE-----
|
||||
31
roles/linux-system-roles.network/tests/files/client.key
Normal file
31
roles/linux-system-roles.network/tests/files/client.key
Normal file
@@ -0,0 +1,31 @@
|
||||
# password=test
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-256-CBC,C4A5E9A189773AB0F3CE3DCC98F208AE
|
||||
|
||||
LPNSExpEERS+/qHJxd8puT+EaZ/dZ20gkU/C2eaNNJerzr4moSXG4ioh5ggz4utQ
|
||||
w57fD5OYqPiloNIawi/Ta5Opo3zU+iMZPVQALLbemXWXmNMxqxNCGdonc4enxMoN
|
||||
auLxpdYPW+infFmf0UPwZjWkrLnK8XFapTGDaNesfgMNSRVSt+DQL3xeKUjcuXfh
|
||||
rYvF26/Ls8NHB0tCU449vCa5ta1fHPT78B0cWgCmhcg/L8/0veBYfwxnyu6l3E6Q
|
||||
RXWcyaJoihhCSg9kCZOqQFKDtz3B9G8/G8P5n5udN2TYUK0ieCktocOip0r/aUfk
|
||||
Rz/NPjej18tuvA9e+uho2DuEj7OV1Rt0Fr6G2NySDYAIjlzM1+GoDdX3R8Rva2eX
|
||||
SJYEjQvvLMAXU9wLEGd2u9jw3h8g2rNPF34Mo/fZsU6f83WceN7wzaDjKBM9TC/U
|
||||
DjeUpJ2LHr3SduRoq5K7PqTG6LlRx4ZC06P8Gwu/cjlHqHuMlLE6wWPHowp9O08S
|
||||
zMzJji6csSzZ5x5U41xiBJd19G0tbfjGBOvxhVLC3hmfqMtRwgeKSZMUz5f0iFvS
|
||||
V4LE/ZNXWv5OybEzMyIiQBRB0G8mq5BkQ3rU9uTMO6Xc6mosQy0jiCsQLYaX2IoT
|
||||
kyU6ZqPgAeBD3g5zCGudcF4qqY3pWRU6cijpivsuyX58YmulhQJsB2rnoImv8ZOR
|
||||
4Uw+fvAx38v/dH/aAGKNdQV/4z+CXpAX4SdqYgBx9wXu6Wva31AVrbDrKnpSlWYF
|
||||
M9gAHgpuhW9OH7du/y7sePU6k37fHtqDX0V5XoyeRxixR+KGb8k3tt0HFA1GExSu
|
||||
XyXcOOfwec7xNQjZBM9jREI0yO1tCbHEeLsLpQnf31cpfSQumBZoiim6Vyk7vCN8
|
||||
YBJ9qiVNrFiVogWl5hUrSS2MLQP1ZQBkedmOeKZpkZ26GW5yY0y27v2mHdhU2Dvd
|
||||
otvLGiVKxSXlu+tqt1WkMvu6hcfrDZDCONW7emGW7xs2vdYdvADVlYs/Eb0WFXb1
|
||||
tLkwg3v7I23LeFRrKX4Fm5/biG4GuR4sj9iPLayrKWhpujIVFJqHTI3YhjIU56Qp
|
||||
uPuClnoFsKrWS9DXaziuuXmLZlXH3e5aOO+M2H3JmXTRCojyjKlIJiJJmHGrfwfe
|
||||
oJkSF+ABs2zrpteXU+Cnfn8V01TrtxPYIBF3CbOMZEvwgjPLX0UNtnss0hXH4rJe
|
||||
9yF/PiKWehUow8q4Gpwt2PnLkUWyL21GwCwXf5Cq3yRAKtyrJTlJsdYV1f3brzfb
|
||||
JkBgKaFJ44Ee7D75PAio8g/BIDpvUdZVXwn3FizjfAU+HhXonPSYb2M34C6I/frk
|
||||
mJPgZ5hbpt1SoCCER48+rQygiLdNQH6OsuhJeEElPFYwNo6i5jZsZ9iE0rmJxGgk
|
||||
m7Mhi491NdK8L6Kh8kM2Dgupsfcstmx4+pI3gmgnsYZApmFoQlfcg4MhbWqxznv+
|
||||
cPm1n2SZMoMLru44vbnjW+ZAggen5zNZOrsVt8UImSBVKfAIrgDUuYIv7uqUiKHI
|
||||
yHmAkZDlqEbpkbUG9m60OeuEIgpN7MT3Kod387ZyOu9uaTZWdD18/N83E4eFecND
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAuQipSk9+0rd/qBMDRiFzV8vDksaueVphejGEgiQhqtUDgjc/
|
||||
ot/o7M8fFVC6wau2ixTnEMHuZXgoBOKATxX805FggEsLLL98OnN7AyTTKOtHVfIm
|
||||
gK3fJ1Y9l95+2nuJWhmaan0vr3YMp6z3lSa+hlhhTYx/mIvTZho/K3+METg8DEfl
|
||||
QUSkhAlrFSEahr2Pu/yETr8c+8vKTDnZDLvcFyyuDtAz+clEQUVndWJQpQpSVfR9
|
||||
4xKuzaj10mUA9Utv4RkbNJ78/KgdTbaGIOVLUnYCJUg8d3/YV7aNCqraHBAZ9aoP
|
||||
S4dl46KXC3qpaEBFfKaF+RcPSVUtc4eCQ74kKwIDAQABAoIBAGoArUN2IVjEaSy3
|
||||
n7OIrFSK1oL6sa+x+JARWDFaU7NTj0wFLL65ee5Yhh0m/6a+IbiyA+IUx+d3m62Y
|
||||
uRsVpJ7r9RXqZ/99v8SYrctSSGpzx41USXyEn4ggnu6nN5MhHMHyUwVYrH3fqkZR
|
||||
EBFxfcrnTO8pY1vYFwayWKgpzOt7ip30JF1E7RH0IWfA2koJ+hZgSumPmF31btBK
|
||||
eqDaQ168u0at6I7nYvRIWVT68D2k+PMb/c/rlOUYSyy+VfCgnShWD+m1hlyaDF1c
|
||||
cbVvOhsul3rFeEqbToGN/6yyDDcyolTvYxMm3vb6jmoExZyRsShv0XyhokSuCN9P
|
||||
v5SeNpkCgYEA7OpIlsZUoTXm2ffCQiZd8gRtKk0O3dzmWTkcNEgj2uUNH6ANNy3W
|
||||
gLojKeF2EyC3appRWLVRYN/m6r/Qj+rztZfW3Jw1UJQV+tLEOBzk3yBnRdh1aRgW
|
||||
8YTH1+HJqlJ/2iKJRKRhseM5AHiTslp7ude6cWQxO52pJ6Rbp1z3fBUCgYEAx/B4
|
||||
LreIDJYDnYSyL/CvVkHEn1hCYX0oBpefzV6ofYDqv0OLe8BWOBsShQ3Crh0FuQTa
|
||||
xV2xc+OzDewlu2OwNm4/X0qjXvoWkEMLBXKEHjPyxnbHLCYaaA/9ENmVIkc8aZWE
|
||||
p7KcCYGlfiHpbdYWAD8KYdv5CsFHFbwhPwrD7z8CgYAEtsSq+1dDvebR/3QGDO1h
|
||||
m2TwqofZMkQDEnfVMnpEKLqSHoUky+ywswNwGeRXjRcZL+jecv0jiFD36skjk/E1
|
||||
c8f6q8ED0W5+hyMQWsLTDboAUcZESQ5rz9CKIxv4H5wbowRIMV0gRP0lXUDTE6nS
|
||||
kNBM4Ul5fjGXcFXChr8F4QKBgGSmAeoKi9tCHTnLVePaNnmmi/Nm+6uV1HNVGqXI
|
||||
k+rx3bpAp1O5o+2Ee1MtdSYvB/V2oyadnrnnEvjcOrZVXZxY7V/r88fY/0jJ5x9r
|
||||
4WRO5FTR8DuiRsLB4bP8xB1IXPoNwYSl3fTPJd8T9S1MizC+i1xt3rVyTHV9igLx
|
||||
SWcDAoGBAMoynJvQUOssWwFTtNQK0ptz95rrTkO2bri+8MJfSh8tessekwPHVe6M
|
||||
SBofFhDiesrHBHczJ61qDnb3GemA0kEbo023mxNo0HPam+OFgX5mrihizBZnRZjh
|
||||
aecVouDd0uwacsB76fwP6Fl5GhkFvOSBKr2IKNJjUMXyvW8/XGZE
|
||||
-----END RSA PRIVATE KEY-----
|
||||
22
roles/linux-system-roles.network/tests/files/client.pem
Normal file
22
roles/linux-system-roles.network/tests/files/client.pem
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDrDCCApSgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UEBhMCWFgx
|
||||
FTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55
|
||||
IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTAgFw0yMDA1MDMwODUxMTdaGA8yMjk0MDIx
|
||||
NTA4NTExN1owXzELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc
|
||||
MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEbMBkGA1UEAwwSY2xpZW50LmV4
|
||||
YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuQipSk9+
|
||||
0rd/qBMDRiFzV8vDksaueVphejGEgiQhqtUDgjc/ot/o7M8fFVC6wau2ixTnEMHu
|
||||
ZXgoBOKATxX805FggEsLLL98OnN7AyTTKOtHVfImgK3fJ1Y9l95+2nuJWhmaan0v
|
||||
r3YMp6z3lSa+hlhhTYx/mIvTZho/K3+METg8DEflQUSkhAlrFSEahr2Pu/yETr8c
|
||||
+8vKTDnZDLvcFyyuDtAz+clEQUVndWJQpQpSVfR94xKuzaj10mUA9Utv4RkbNJ78
|
||||
/KgdTbaGIOVLUnYCJUg8d3/YV7aNCqraHBAZ9aoPS4dl46KXC3qpaEBFfKaF+RcP
|
||||
SVUtc4eCQ74kKwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1P
|
||||
cGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUoUCV4T3pFwaQ
|
||||
HYSlCr8Iqdd+/TcwHwYDVR0jBBgwFoAUNQp0DAOJalSlrJSaOJYRwxxeNB0wDQYJ
|
||||
KoZIhvcNAQELBQADggEBALXhDSFirybmhZXcHuSqXn0tLp6mZintW+91B81bDUtO
|
||||
FuCrWqXwV0iensm94mOeykGIR/r0Y0Y4uqOHpIznY+q5NIek0qIdirbdr5mCXK5y
|
||||
fxXVIMM14GMTyIR9A4+IZaRkFbcrVnBhOdUpTQjp88jlzDr5jdyjTEnOZyOJH9kL
|
||||
Qpd417iB4X5TxuQ2xe5EgHOCb8OfxO0a2BzlwtfUQAkz2v+h0RlVBwQFcE2NCJ3z
|
||||
hvF3AWGl+5pkfWpY6d+1EPI3+82C6uRf8be/WKHPKu3i0irrVtZdMsKNkRiD5UUK
|
||||
S4Y0WnoVu/DWSR8h9iPGSFKMkUcjFI8hgc4YQ6G4Odc=
|
||||
-----END CERTIFICATE-----
|
||||
8
roles/linux-system-roles.network/tests/files/dh.pem
Normal file
8
roles/linux-system-roles.network/tests/files/dh.pem
Normal file
@@ -0,0 +1,8 @@
|
||||
-----BEGIN DH PARAMETERS-----
|
||||
MIIBCAKCAQEAjbYPkANn2XGqDGCzse9wAfM0I5WJpp+Xl+iNJFmaKXBguo0BPYQt
|
||||
hZOpJbKL3aNaFsRxhdAJ8UXzBP6oIzCejcGti+jw+xtVk8ietWEK6e91yi+Ak2g2
|
||||
/Xtt9hoYQkeoe5hkcv35NcJ0xdQwlSvMbY/j8HtKamx/A3zu+YPQAe/3AOe3L+JT
|
||||
iEL5Gw00NPVnyEWKX4fVchAbMUkRsQKeXtsyOyDc4/RccjfLa1toyj8PRommK5UH
|
||||
dkSqi04FTOUIx6aTwt21EehJuggLVDShoQdxGV+FzXmdtelLmerGMtVPBbf8DSkN
|
||||
MKMBEg4d28DzjXPAWUHMD+JGPzAlvf87EwIBAg==
|
||||
-----END DH PARAMETERS-----
|
||||
31
roles/linux-system-roles.network/tests/files/server.key
Normal file
31
roles/linux-system-roles.network/tests/files/server.key
Normal file
@@ -0,0 +1,31 @@
|
||||
# password=test
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-256-CBC,ED349A8B098E2D1DB70C30F77EF599AB
|
||||
|
||||
j1rzje2sWFk3B9kD6eE7WrqVDynFEJ3t3kdOv0iUvH5Ybll1C7Qx3EFEdoM4z2OV
|
||||
E6q3nr2DOvpMPox1DvBdIipWOQWJxkZyBHqNn4v4GR4c0uxLswsk7XSBQLUclRsn
|
||||
QBGO6x8pcEA9u/O3PSrTt+pVozWrXWmR2UHNM//9WUsRpWF4Lv0EINzsfwmD7aJQ
|
||||
nRcSfXsCggXP6wnJX5dgo5PlRm6R+bodgzePr0QRlh8TT6wnixZfWalYM5iUKlEF
|
||||
GcE+VejZuBL69byl2AcRt8I5tQ+UZxmzhKPSsYN0NKD8vbcVVnp2sre/rbdTzWz5
|
||||
laF386g1M8QBimDE/V3Bw5b9Bg1ZP3arlpugVXGVNA+HFti8PVdkaMqLgkFIC2Xu
|
||||
OwmNKffAPIItuB8leg5A76oLoIlllRqjWO9M/O+MqAlrJ96xLRiUeGkez4Pp7eFV
|
||||
30YrlOXyzwZKfXoOPIfE5Mbz4CPqR67XuqW8jOryIGOryMB17b0+vdRpDY0wxk8/
|
||||
lGmc5rglDxLFA8dNemAHDednasCuVlrbsQsZRnPkKavXiSu7QCbvm1frAXZfnyRp
|
||||
TpPmE6L4+nEy8PQnK/IxOCqRcy6e1SPezRpajRjB5ooDT8hDmDkG47NdnrB+kOKL
|
||||
5LIpATLSGS9IVk0RW/M8EqJP1kRh2JOCQT3V+gUN0ttz8bjZpivKnp76/ztg0lo0
|
||||
oC2lhuXV5HOYHw1z5jDazsYpQDYoHgYWXnzPJJp6Ecn+nkjZMKQjDV9ZqE1miPrZ
|
||||
E4V0ULNmWaAQHvwc98yR97ui1YHmw5XVMoeDhy3fhB6IOyaGGdEj9o2iQr8kp9GC
|
||||
dxBKK/xMOU6kwDF9Nsfh46veRGTbhAJdGeWqdxscdCupkO8KRtZqzL454+9GnYfe
|
||||
n1f7wxJh7aTLNjF2an5Qa9v7uU6D58+9blxG7ls5qGt4xjBNAXCc8bPpmLqeCW4G
|
||||
Xz8iwxECvwWIQ+SjUcXuP8+/NO58B14kDNP03+1gA7AHIesa2CTvHLCyMPaN2oGK
|
||||
3R4LNxQQDNygEzRj8vHjURU1FNRJ4RjCi7SbqoOsl31Hvef6j0lcW0Sz4UICcCJI
|
||||
p4NPnApoaHewL4exvlJ80qPbFscuVevXBlUC2LdxXS+9E+c0NaLauEeNYCUoaBDi
|
||||
HIpbxRKXmqLc4LAKYVuEcIBFhdXp3UC9niVd7Nrguu0lUJXC78OzpltxWrqX/u4E
|
||||
O2aCNK0Yg9U+rxm6wyccqEyptIS2GRCIpUGD/LVF3mOC16NB/JeYGrOWvDptdCeg
|
||||
9pJrakJjE1Fm3pg4Xc74bT6IDj0EKwKSvZhtlcsM9JaXWChe/ZrDPPI/NP6MuyW4
|
||||
jcqpa9HPBBSyaxKsEPXFJhdhrz8VfsU2e5VvcALaJaAOpHwZgaNUpvpsY4LPW9mi
|
||||
lHsecEBiq6re0r7TAgBE1AnlaI4ho0fKSgSub3NWUZlEaBK3X2n/Li6op6LIsvM5
|
||||
iySYaAluQy4dANww0KhQHMIh0jbuZGzmG2Hxk/poorYRf60YJlbTnHVD/FKUdFX+
|
||||
rUow0iy8Ez1uF272u5orYW2tBbkhSaieKOT8f4HFCxUsgITbd8Lf/XJ6l6Qns6SK
|
||||
-----END RSA PRIVATE KEY-----
|
||||
22
roles/linux-system-roles.network/tests/files/server.pem
Normal file
22
roles/linux-system-roles.network/tests/files/server.pem
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDrDCCApSgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UEBhMCWFgx
|
||||
FTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55
|
||||
IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTAgFw0yMDA1MDMwODUxMzBaGA8yMjk0MDIx
|
||||
NTA4NTEzMFowXzELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc
|
||||
MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEbMBkGA1UEAwwSc2VydmVyLmV4
|
||||
YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxcC44Amd
|
||||
KQBDwR67aMTPqmNu6HfjadZqsD2xZj5XMVdn4karqsVYIbKMOq+SRzgm5aZ/kzQI
|
||||
CpXJMXfj16cID6BCxNecfJVOfvPyI0kCUbMf1YZiRG2FmB2VsG8AVDGWmn4a7SmX
|
||||
yaCA0ac8dkipnlCF2nddLhcBak/Ls+hjRYN7VSLLvxO8KT42ivhuP9YgGY1K5Yta
|
||||
e90H4HBiKxbnkwOUxi9wobERSXSLgb4e+uX8WRrqxIIYmHF+Gzv5kilRFrPwKBmo
|
||||
3idVPrqjschZe0o8m/nbNo3SzWGI9fdXn0+KgZoQdG3ZixX6uOhrCqJ3iJmnHkp4
|
||||
aXKL5Y7JmX/FFQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1P
|
||||
cGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUx7TXCxUioob7
|
||||
5r/1kMypCYy9Mj0wHwYDVR0jBBgwFoAUNQp0DAOJalSlrJSaOJYRwxxeNB0wDQYJ
|
||||
KoZIhvcNAQELBQADggEBAKtTPl4WJuxfMeut+aEw7vVRU+z5A7D35nlZPQI5nBTt
|
||||
ybgqMNIjdcYT/JwT2GhbzcObc3STNEo582clVN9gTpK7mYKzBBf69nTsWeZzPuNt
|
||||
JQbVbK4RHwFvyosJcw6NfzxE9OxeXhTcKQDQSGKP338sAWoapEZlXNrYOIJac6HX
|
||||
Xo3dQqx/8BdO9hSv1u0/zClnL5lbk1RBylS24wIe8wLoiy4ftLjL4aOYOlonj7HU
|
||||
hknTY6L30oOpG5VtH8SEv3xveH/5GNKwfoGltTzemCgVfb9IhyVTLB3tIv8OW6k1
|
||||
y3+YEzVniVB4gtJ5UniLN1V4lBf6t7MGn0ybAEbOxPI=
|
||||
-----END CERTIFICATE-----
|
||||
65
roles/linux-system-roles.network/tests/get_coverage.sh
Executable file
65
roles/linux-system-roles.network/tests/get_coverage.sh
Executable file
@@ -0,0 +1,65 @@
|
||||
#! /bin/bash
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if [ -n "${DEBUG}" ]
|
||||
then
|
||||
set -x
|
||||
fi
|
||||
set -e
|
||||
|
||||
if [ "$#" -lt 2 ]
|
||||
then
|
||||
echo "USAGE: ${0} host playbook"
|
||||
echo "Get coverage info from host for playbook"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
host="${1}"
|
||||
shift
|
||||
playbook="${1}"
|
||||
|
||||
coverage_data="remote-coveragedata-${host}-${playbook%.yml}"
|
||||
coverage="/root/.local/bin/coverage"
|
||||
|
||||
echo "Getting coverage for ${playbook} on ${host}" >&2
|
||||
|
||||
call_ansible() {
|
||||
local module="${1}"
|
||||
shift
|
||||
local args="${1}"
|
||||
shift
|
||||
ansible -m "${module}" -i "${host}", -a "${args}" all "${@}"
|
||||
}
|
||||
|
||||
remote_coverage_dir="$(mktemp -d /tmp/remote_coverage-XXXXXX)"
|
||||
trap "rm -rf '${remote_coverage_dir}'" EXIT
|
||||
ansible-playbook -i "${host}", get_coverage.yml -e "test_playbook=${playbook} destdir=${remote_coverage_dir}"
|
||||
|
||||
#COVERAGE_FILE=remote-coverage coverage combine remote-coverage/tests_*/*/root/.coverage
|
||||
./merge_coverage.sh coverage "${coverage_data}"-tmp $(find "${remote_coverage_dir}" -type f | tr , _)
|
||||
|
||||
cat > tmp_merge_coveragerc <<EOF
|
||||
[paths]
|
||||
source =
|
||||
.
|
||||
EOF
|
||||
# example path with Ansible 2.9.6:
|
||||
# /tmp/ansible_network_connections_payload_psugdf6r/ansible_network_connections_payload.zip/ansible/modules/network_connections.py
|
||||
# /tmp/ansible_network_connections_payload_psugdf6r/ansible_network_connections_payload.zip/ansible/module_utils/network_lsr/__init__.py
|
||||
# /tmp/ansible_network_connections_payload_psugdf6r/ansible_network_connections_payload.zip/ansible/module_utils/network_lsr/argument_validator.py
|
||||
# /tmp/ansible_network_connections_payload_psugdf6r/ansible_network_connections_payload.zip/ansible/module_utils/network_lsr/utils.py
|
||||
# /tmp/ansible_network_connections_payload_psugdf6r/ansible_network_connections_payload.zip/ansible/module_utils/network_lsr/nm_provider.py
|
||||
for file in $(echo 'SELECT path FROM file;' | sqlite3 "${coverage_data}"-tmp | sed s,/module.*.py,, | sort -u)
|
||||
do
|
||||
echo " ${file}" >> tmp_merge_coveragerc
|
||||
done
|
||||
|
||||
COVERAGE_FILE="${coverage_data}" coverage combine --rcfile tmp_merge_coveragerc "${coverage_data}"-tmp
|
||||
|
||||
test -n "${DEBUG}" && cat tmp_merge_coveragerc
|
||||
rm tmp_merge_coveragerc
|
||||
|
||||
COVERAGE_FILE="${coverage_data}" coverage report ||:
|
||||
COVERAGE_FILE="${coverage_data}" coverage html --directory "htmlcov-${coverage_data}" ||:
|
||||
|
||||
echo "Coverage collected in: ${coverage_data}"
|
||||
82
roles/linux-system-roles.network/tests/get_coverage.yml
Normal file
82
roles/linux-system-roles.network/tests/get_coverage.yml
Normal file
@@ -0,0 +1,82 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
# This expects the variable test_playbook to be set from the outside
|
||||
- name: Prepare for coverage extraction
|
||||
hosts: all
|
||||
tasks:
|
||||
# Use set_fact to set variables to make them available in all plays
|
||||
# 'vars:' Would only set variables for the current play
|
||||
- name: set facts
|
||||
set_fact:
|
||||
coverage_module: network_connections
|
||||
coverage: /root/.local/bin/coverage
|
||||
destdir: "remote_coverage/{{ test_playbook }}"
|
||||
|
||||
# This uses variables from the other set_fact task, therefore it needs to
|
||||
# be its own task
|
||||
- name: set more facts
|
||||
set_fact:
|
||||
coverage_file:
|
||||
# yamllint disable-line rule:line-length
|
||||
ansible-coverage-{{ coverage_module }}-{{ test_playbook|replace('.yml', '') }}
|
||||
|
||||
- name: debug info
|
||||
debug:
|
||||
msg:
|
||||
# yamllint disable-line rule:line-length
|
||||
Getting coverage for '{{ coverage_module }}' with '{{ test_playbook }}'
|
||||
|
||||
# combine data in case old data is left there
|
||||
- command: "{{ coverage }} combine"
|
||||
environment:
|
||||
COVERAGE_FILE: "{{ coverage_file }}"
|
||||
ignore_errors: yes
|
||||
|
||||
- name: remove old data
|
||||
file:
|
||||
state: absent
|
||||
path: "{{ coverage_file }}"
|
||||
|
||||
- name: find coverage files to delete
|
||||
find:
|
||||
path: "{{ ansible_env.HOME }}"
|
||||
patterns: ".coverage.*"
|
||||
hidden: yes
|
||||
register: files_to_delete
|
||||
|
||||
- name: remove old data
|
||||
file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
with_items: "{{ files_to_delete.files }}"
|
||||
|
||||
- name: copy coveragerc
|
||||
copy:
|
||||
content: "[run]\ndisable_warnings = no-data-collected\n"
|
||||
dest: .coveragerc
|
||||
|
||||
- name: install latest pip
|
||||
pip:
|
||||
name: coverage
|
||||
extra_args: --user --upgrade
|
||||
|
||||
- import_playbook: "{{ test_playbook }}"
|
||||
vars:
|
||||
ansible_python_interpreter:
|
||||
# yamllint disable-line rule:line-length
|
||||
"{{ coverage }} run -p --include /*/modules/network_connections.py,/*/module_utils/network_lsr/*"
|
||||
|
||||
- name: Gather coverage data
|
||||
hosts: all
|
||||
tasks:
|
||||
- shell: "{{ coverage }} combine .coverage.*"
|
||||
environment:
|
||||
COVERAGE_FILE: "{{ coverage_file }}"
|
||||
|
||||
- name: Get coverage data
|
||||
hosts: all
|
||||
tasks:
|
||||
- fetch:
|
||||
src: "{{ coverage_file }}"
|
||||
dest: "{{ destdir }}"
|
||||
flat: no
|
||||
34
roles/linux-system-roles.network/tests/get_total_coverage.sh
Executable file
34
roles/linux-system-roles.network/tests/get_total_coverage.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#! /bin/bash
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
set -e
|
||||
coverage_data=total-coveragedata
|
||||
testhost="${1}"
|
||||
|
||||
if [ "$#" -lt 1 ]
|
||||
then
|
||||
echo "USAGE: ${0} host"
|
||||
echo "Get local and all remote coverage data for host"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f remote-coveragedata* "${coveragedata}"
|
||||
|
||||
|
||||
# collect pytest coverage
|
||||
tox -e py26,py27,py36,py37 -- --cov-append
|
||||
|
||||
for test_playbook in tests_*.yml
|
||||
do
|
||||
./get_coverage.sh "${testhost}" "${test_playbook}"
|
||||
done
|
||||
|
||||
./merge_coverage.sh coverage "total-remote-coveragedata" remote-coveragedata-*
|
||||
./covstats .coverage remote-coveragedata-* "total-remote-coveragedata"
|
||||
|
||||
./merge_coverage.sh coverage "${coverage_data}" .coverage remote-coveragedata-*
|
||||
echo "Total coverage:"
|
||||
COVERAGE_FILE="${coverage_data}" coverage report ||:
|
||||
COVERAGE_FILE="${coverage_data}" coverage html --directory "htmlcov-${coverage_data}" ||:
|
||||
echo "Open HTML report with:"
|
||||
echo "xdg-open htmlcov-${coverage_data}/index.html"
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--provider", action="store", default="nm", help="Network provider"
|
||||
)
|
||||
@@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
parent_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
|
||||
with mock.patch.dict(
|
||||
"sys.modules",
|
||||
{
|
||||
"ansible.module_utils.basic": mock.Mock(),
|
||||
},
|
||||
):
|
||||
import network_connections as nc
|
||||
|
||||
|
||||
class PytestRunEnvironment(nc.RunEnvironment):
|
||||
def log(self, connections, idx, severity, msg, **kwargs):
|
||||
if severity == nc.LogLevel.ERROR:
|
||||
logging.error("Error: {}".format(connections[idx]))
|
||||
raise RuntimeError(msg)
|
||||
else:
|
||||
logging.debug("Log: {}".format(connections[idx]))
|
||||
|
||||
def run_command(self, argv, encoding=None):
|
||||
command = subprocess.Popen(
|
||||
argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
return_code = command.wait()
|
||||
out, err = command.communicate()
|
||||
return return_code, out.decode("utf-8"), err.decode("utf-8")
|
||||
|
||||
def _check_mode_changed(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def _configure_network(connections, provider):
|
||||
cmd = nc.Cmd.create(
|
||||
provider,
|
||||
run_env=PytestRunEnvironment(),
|
||||
connections_unvalidated=connections,
|
||||
connection_validator=nc.ArgValidator_ListConnections(),
|
||||
)
|
||||
cmd.run()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def provider(request):
|
||||
return request.config.getoption("--provider")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def testnic1():
|
||||
veth_name = "testeth"
|
||||
try:
|
||||
subprocess.call(
|
||||
[
|
||||
"ip",
|
||||
"link",
|
||||
"add",
|
||||
veth_name,
|
||||
"type",
|
||||
"veth",
|
||||
"peer",
|
||||
"name",
|
||||
veth_name + "peer",
|
||||
],
|
||||
close_fds=True,
|
||||
)
|
||||
yield veth_name
|
||||
finally:
|
||||
subprocess.call(["ip", "link", "delete", veth_name])
|
||||
|
||||
|
||||
def _get_ip_addresses(interface):
|
||||
ip_address = subprocess.check_output(["ip", "address", "show", interface])
|
||||
return ip_address.decode("UTF-8")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def network_lsr_nm_mock():
|
||||
with mock.patch.dict(
|
||||
"sys.modules",
|
||||
{
|
||||
"ansible.module_utils.basic": mock.Mock(),
|
||||
},
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
def test_static_ip_with_ethernet(testnic1, provider, network_lsr_nm_mock):
|
||||
ip_address = "192.0.2.127/24"
|
||||
connections = [
|
||||
{
|
||||
"name": testnic1,
|
||||
"type": "ethernet",
|
||||
"state": "up",
|
||||
"ip": {"address": [ip_address]},
|
||||
}
|
||||
]
|
||||
_configure_network(connections, provider)
|
||||
assert ip_address in _get_ip_addresses(testnic1)
|
||||
if provider == "initscripts":
|
||||
assert os.path.exists("/etc/sysconfig/network-scripts/ifcfg-" + testnic1)
|
||||
else:
|
||||
subprocess.check_call(["nmcli", "connection", "show", testnic1])
|
||||
35
roles/linux-system-roles.network/tests/merge_coverage.sh
Executable file
35
roles/linux-system-roles.network/tests/merge_coverage.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#! /bin/bash
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if [ -n "${DEBUG}" ]
|
||||
then
|
||||
set -x
|
||||
fi
|
||||
set -e
|
||||
|
||||
if [ "$#" -lt 3 ]
|
||||
then
|
||||
echo "USAGE: ${0} path_to_coverage_binary output_file input_files..."
|
||||
echo "Merges all input_files into output file without removing input_files"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# path to coverage binary
|
||||
coverage="${1}"
|
||||
shift
|
||||
|
||||
# read by coverage binary
|
||||
export COVERAGE_FILE="${1}"
|
||||
shift
|
||||
|
||||
tempdir="$(mktemp -d /tmp/coverage_merge-XXXXXX)"
|
||||
trap "rm -rf '${tempdir}'" EXIT
|
||||
|
||||
cp --backup=numbered -- "${@}" "${tempdir}"
|
||||
# FIXME: Would not work if coverage files are not hidden but they are by
|
||||
# default
|
||||
shopt -s dotglob
|
||||
"${coverage}" combine "${tempdir}/"*
|
||||
|
||||
echo "Merged data into ${COVERAGE_FILE}"
|
||||
./covstats "${COVERAGE_FILE}"
|
||||
1
roles/linux-system-roles.network/tests/module_utils
Symbolic link
1
roles/linux-system-roles.network/tests/module_utils
Symbolic link
@@ -0,0 +1 @@
|
||||
../module_utils/
|
||||
1
roles/linux-system-roles.network/tests/modules
Symbolic link
1
roles/linux-system-roles.network/tests/modules
Symbolic link
@@ -0,0 +1 @@
|
||||
../library/
|
||||
@@ -0,0 +1,10 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- name: Set {{ profile }} down
|
||||
hosts: all
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ profile }}"
|
||||
state: down
|
||||
roles:
|
||||
- linux-system-roles.network
|
||||
1
roles/linux-system-roles.network/tests/playbooks/files
Symbolic link
1
roles/linux-system-roles.network/tests/playbooks/files
Symbolic link
@@ -0,0 +1 @@
|
||||
../files
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user