Add create vm script and foreman config 93/693/5
authorarnaudmorin <arnaud.morin@gmail.com>
Fri, 29 May 2015 13:56:50 +0000 (15:56 +0200)
committerarnaudmorin <arnaud.morin@gmail.com>
Fri, 26 Jun 2015 12:10:20 +0000 (14:10 +0200)
Add a script to create a KVM machine and install foreman in it.
This is the beginning of the OpenSteak installation mecanism.
It is very similare as what is done in the Foreman/Quickstack approach
with small differences:
 - Ubuntu as base OS (instead of CentOS)
 - Foreman v1.8 (instead of 1.7.5
 - KVM (libvirt) virtual machine (instead of Vagrant)
 - Only python/bash scripts to manage all the stuff (instead of
   Khaleesi/Ansible/Astaport playbooks)

Change-Id: Ie66b1da4288372927e30163f82f5a0f45e2e73d0
JIRA: BGS-9
Signed-off-by: arnaudmorin <arnaud.morin@gmail.com>
40 files changed:
opensteak/ci/build.sh
opensteak/ci/deploy.sh
opensteak/config/common.yaml [new file with mode: 0644]
opensteak/config/infra.yaml [new file with mode: 0644]
opensteak/tools/README.rst [new file with mode: 0644]
opensteak/tools/config.yaml [new file with mode: 0644]
opensteak/tools/create_foreman.py [new file with mode: 0644]
opensteak/tools/files_foreman/id_rsa [new file with mode: 0644]
opensteak/tools/files_foreman/id_rsa.pub [new file with mode: 0644]
opensteak/tools/opensteak/.gitignore [new file with mode: 0644]
opensteak/tools/opensteak/__init__.py [new file with mode: 0644]
opensteak/tools/opensteak/argparser.py [new file with mode: 0644]
opensteak/tools/opensteak/conf.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/.gitignore [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/__init__.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/api.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/architectures.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/compute_resources.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/domains.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/freeip.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/hostgroups.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/hosts.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/item.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/itemHost.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/itemHostsGroup.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/itemOverrideValues.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/itemSmartClassParameter.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/objects.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/operatingsystems.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/puppetClasses.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/smart_proxies.py [new file with mode: 0644]
opensteak/tools/opensteak/foreman_objects/subnets.py [new file with mode: 0644]
opensteak/tools/opensteak/printer.py [new file with mode: 0644]
opensteak/tools/opensteak/templateparser.py [new file with mode: 0644]
opensteak/tools/opensteak/virsh.py [new file with mode: 0644]
opensteak/tools/templates_foreman/install.sh [new file with mode: 0644]
opensteak/tools/templates_foreman/kvm-config [new file with mode: 0644]
opensteak/tools/templates_foreman/meta-data [new file with mode: 0644]
opensteak/tools/templates_foreman/user-data [new file with mode: 0644]

index e69de29..7a85332 100644 (file)
@@ -0,0 +1,19 @@
+#!/bin/bash
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+#placeholder
index e69de29..bd6ff86 100644 (file)
@@ -0,0 +1,28 @@
+#!/bin/bash
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+
+# TODO: find a way to create the openvswitch bridge
+
+
+# This will create a Foreman Virtual Machine with KVM (libvirt)
+cd ../tools/
+sudo python3 create_foreman.py --config ../config/infra.yaml
+
+
diff --git a/opensteak/config/common.yaml b/opensteak/config/common.yaml
new file mode 100644 (file)
index 0000000..144e84f
--- /dev/null
@@ -0,0 +1,119 @@
+# common.yaml
+---
+
+###
+##  OpenStack passwords
+###
+ceph_password: "password"
+admin_password: "password"
+mysql_service_password: "password"
+mysql_root_password: "password"
+rabbitmq_password: "password"
+glance_password: "password"
+nova_password: "password"
+neutron_shared_secret: "password"
+neutron_password: "password"
+cinder_password: "password"
+keystone_admin_token: "password"
+horizon_secret_key: "12345"
+
+domain: "infra.opensteak.fr"
+
+###
+## Class parameters
+###
+# Rabbit
+opensteak::rabbitmq::rabbitmq_password: "%{hiera('rabbitmq_password')}"
+
+# MySQL
+opensteak::mysql::root_password: "%{hiera('mysql_root_password')}"
+opensteak::mysql::mysql_password: "%{hiera('mysql_service_password')}"
+
+# Key
+opensteak::key::password: "%{hiera('admin_password')}"
+opensteak::key::stack_domain: "%{hiera('domain')}"
+
+# Keystone
+opensteak::keystone::mysql_password: "%{hiera('mysql_root_password')}"
+opensteak::keystone::rabbitmq_password: "%{hiera('rabbitmq_password')}"
+opensteak::keystone::keystone_token: "%{hiera('keystone_admin_token')}"
+opensteak::keystone::stack_domain: "%{hiera('domain')}"
+opensteak::keystone::admin_mail: "admin@opensteak.fr"
+opensteak::keystone::admin_password: "%{hiera('admin_password')}"
+opensteak::keystone::glance_password: "%{hiera('glance_password')}"
+opensteak::keystone::nova_password: "%{hiera('nova_password')}"
+opensteak::keystone::neutron_password: "%{hiera('neutron_password')}"
+opensteak::keystone::cinder_password: "%{hiera('cinder_password')}"
+
+# Glance
+opensteak::glance::mysql_password: "%{hiera('mysql_root_password')}"
+opensteak::glance::rabbitmq_password: "%{hiera('rabbitmq_password')}"
+opensteak::glance::stack_domain: "%{hiera('domain')}"
+opensteak::glance::glance_password: "%{hiera('glance_password')}"
+
+# Nova
+opensteak::nova::mysql_password: "%{hiera('mysql_root_password')}"
+opensteak::nova::rabbitmq_password: "%{hiera('rabbitmq_password')}"
+opensteak::nova::stack_domain: "%{hiera('domain')}"
+opensteak::nova::nova_password: "%{hiera('nova_password')}"
+opensteak::nova::neutron_password: "%{hiera('neutron_password')}"
+opensteak::nova::neutron_shared: "%{hiera('neutron_shared_secret')}"
+
+# Cinder
+opensteak::cinder::mysql_password: "%{hiera('mysql_root_password')}"
+opensteak::cinder::rabbitmq_password: "%{hiera('rabbitmq_password')}"
+opensteak::cinder::stack_domain: "%{hiera('domain')}"
+opensteak::cinder::nova_password: "%{hiera('cinder_password')}"
+
+# Compute
+opensteak::nova-compute::mysql_password: "%{hiera('mysql_root_password')}"
+opensteak::nova-compute::rabbitmq_password: "%{hiera('rabbitmq_password')}"
+opensteak::nova-compute::stack_domain: "%{hiera('domain')}"
+opensteak::nova-compute::neutron_password: "%{hiera('neutron_password')}"
+
+
+# Neutron controller
+opensteak::neutron-controller::mysql_password: "%{hiera('mysql_root_password')}"
+opensteak::neutron-controller::rabbitmq_password: "%{hiera('rabbitmq_password')}"
+opensteak::neutron-controller::stack_domain: "%{hiera('domain')}"
+opensteak::neutron-controller::nova_password: "%{hiera('nova_password')}"
+opensteak::neutron-controller::neutron_password: "%{hiera('neutron_password')}"
+# Neutron compute
+opensteak::neutron-compute::mysql_password: "%{hiera('mysql_root_password')}"
+opensteak::neutron-compute::rabbitmq_password: "%{hiera('rabbitmq_password')}"
+opensteak::neutron-compute::stack_domain: "%{hiera('domain')}"
+opensteak::neutron-compute::neutron_password: "%{hiera('neutron_password')}"
+opensteak::neutron-compute::neutron_shared: "%{hiera('neutron_shared_secret')}"
+opensteak::neutron-compute::infra_nodes:
+ server186:
+  ip: 192.168.1.27
+  bridge_uplinks:
+   - 'br-vm:p3p1'
+ server187:
+  ip: 192.168.1.155
+  bridge_uplinks:
+   - 'br-vm:p3p1'
+ server188:
+  ip: 192.168.1.116
+  bridge_uplinks:
+   - 'br-vm:p3p1'
+ server189:
+  ip: 192.168.1.117
+  bridge_uplinks:
+   - 'br-vm:p3p1'
+# Neutron network
+opensteak::neutron-network::mysql_password: "%{hiera('mysql_root_password')}"
+opensteak::neutron-network::rabbitmq_password: "%{hiera('rabbitmq_password')}"
+opensteak::neutron-network::stack_domain: "%{hiera('domain')}"
+opensteak::neutron-network::neutron_password: "%{hiera('neutron_password')}"
+opensteak::neutron-network::neutron_shared: "%{hiera('neutron_shared_secret')}"
+opensteak::neutron-network::infra_nodes:
+ server98:
+  ip: 192.168.1.58
+  bridge_uplinks:
+   - 'br-ex:em2'
+   - 'br-vm:em5'
+
+# Horizon
+opensteak::horizon::stack_domain: "%{hiera('domain')}"
+opensteak::horizon::secret_key: "%{hiera('horizon_secret_key')}"
diff --git a/opensteak/config/infra.yaml b/opensteak/config/infra.yaml
new file mode 100644 (file)
index 0000000..2ff02a1
--- /dev/null
@@ -0,0 +1,81 @@
+domains: "infra.opensteak.fr"
+media: "Ubuntu mirror"
+environments: "production"
+operatingsystems: "Ubuntu14.04Cloud"
+subnets: "Admin"
+compute_profiles: "Test"
+smart_proxies: "foreman.infra.opensteak.fr"
+ptables: "Preseed default"
+architectures: "x86_64"
+
+operatingsystems:
+ "Ubuntu 14.04.2 LTS":
+  name: "Ubuntu"
+  description: "Ubuntu 14.04.2 LTS"
+  major: "14"
+  minor: "04"
+  family: "Debian"
+  release_name: "trusty"
+  password_hash: "MD5"
+ "Ubuntu 14.04 Cloud":
+  name: "Ubuntu14.04Cloud"
+  description: "Ubuntu 14.04 Cloud"
+  major: "14"
+  minor: "04"
+  family: "Debian"
+  release_name: "trusty"
+  password_hash: "MD5"
+
+hostgroupTop:
+ name: 'test'
+ classes:
+ - "ntp"
+ subnet: "Admin"
+ params:
+  password: 'toto'
+hostgroups:
+ hostgroupController:
+  name: 'controller'
+  classes:
+   - "opensteak::base-network"
+   - "opensteak::libvirt"
+  params:
+   foreman_sshkey: 'xxxx'
+ hostgroupControllerVM:
+  name: 'controller_VM'
+  classes:
+   - "opensteak::apt"
+  params:
+   foreman_sshkey: 'xxxx'
+   password: 'toto'
+ hostgroupCompute:
+  name: 'compute'
+  classes:
+   - "opensteak::neutron-compute"
+   - "opensteak::nova-compute"
+subnets:
+ Admin:
+  shared: False
+  data:
+   network: "192.168.4.0"
+   mask: "255.255.255.0"
+   vlanid:
+   gateway: "192.168.4.1"
+   dns_primary: "192.168.1.4"
+   from: "192.168.4.10"
+   to: "192.168.4.200"
+   ipam: "DHCP"
+   boot_mode: "DHCP"
+
+foreman:
+ ip: "192.168.4.2"
+ admin: "admin"
+ password: "opnfv"
+ cpu: "4"
+ ram: "4194304"
+ iso: "trusty-server-cloudimg-amd64-disk1.img"
+ disksize: "5G"
+ force: True
+ dns: "8.8.8.8"
+ bridge: "br-libvirt"
+ bridge_type: "openvswitch"
diff --git a/opensteak/tools/README.rst b/opensteak/tools/README.rst
new file mode 100644 (file)
index 0000000..188addc
--- /dev/null
@@ -0,0 +1,52 @@
+:Authors: Arnaud Morin (arnaud1.morin@orange.com)
+:Version: 0.0.2
+
+=======================================================
+OPNFV Installation instructions using Foreman/OpenSteak
+=======================================================
+
+Abstract
+========
+
+This document describes how to setup OPNFV from a foreman Virtual Machine on an Ubuntu server.
+
+License
+=======
+OPNFV Installation instructions using Foreman/OpenSteak (c) by Arnaud Morin (Orange)
+
+OPNFV Installation instructions using Foreman/OpenSteak are licensed under a Creative Commons Attribution 4.0 International License. You should have received a copy of the license along with this. If not, see <http://creativecommons.org/licenses/by/4.0/>.
+
+Version history
+===================
+
++--------------------+--------------------+--------------------+--------------------+
+| **Date**           | **Ver.**           | **Author**         | **Comment**        |
+|                    |                    |                    |                    |
++--------------------+--------------------+--------------------+--------------------+
+| 2015-06-08         | 0.0.1              | Arnaud Morin       | First draft        |
+|                    |                    | (Orange)           |                    |
++--------------------+--------------------+--------------------+--------------------+
+
+Table of contents
+===================
+
+.. contents::
+   :backlinks: none
+
+Introduction
+============
+
+This document describes how to setup OPNFV from a foreman Virtual Machine on an Ubuntu server.
+Before starting, you should have an Ubuntu 14.04 LTS server already installed.
+
+Here is the manual workflow that you will have to perform:
+
+- Install
+- Manually prepare configuration files from templates.
+
+
+Here is the current workflow of the automated installation:
+
+- Dependencies installation (such as libvirt, impitools, etc.)
+- Foreman Virtual Machine creation
+- to be completed
diff --git a/opensteak/tools/config.yaml b/opensteak/tools/config.yaml
new file mode 100644 (file)
index 0000000..c618a52
--- /dev/null
@@ -0,0 +1,78 @@
+domains: "test-infra.opensteak.fr"
+media: "Ubuntu mirror"
+environments: "production"
+operatingsystems: "Ubuntu14.04Cloud"
+subnets: "Admin"
+compute_profiles: "Test"
+smart_proxies: "foreman.infra.opensteak.fr"
+ptables: "Preseed default"
+architectures: "x86_64"
+
+operatingsystems:
+ "Ubuntu 14.04.1 LTS":
+  name: "Ubuntu"
+  description: "Ubuntu 14.04.1 LTS"
+  major: "14"
+  minor: "04"
+  family: "Debian"
+  release_name: "trusty"
+  password_hash: "MD5"
+ "Ubuntu 14.04 Cloud":
+  name: "Ubuntu14.04Cloud"
+  description: "Ubuntu 14.04 Cloud"
+  major: "14"
+  minor: "04"
+  family: "Debian"
+  release_name: "trusty"
+  password_hash: "MD5"
+
+hostgroupTop:
+ name: 'test'
+ classes:
+ - "ntp"
+ subnet: "Admin"
+ params:
+  password: 'toto'
+hostgroups:
+ hostgroupController:
+  name: 'controller'
+  classes:
+   - "opensteak::base-network"
+   - "opensteak::libvirt"
+  params:
+   foreman_sshkey: 'xxxx'
+ hostgroupControllerVM:
+  name: 'controller_VM'
+  classes:
+   - "opensteak::apt"
+  params:
+   foreman_sshkey: 'xxxx'
+   password: 'toto'
+ hostgroupCompute:
+  name: 'compute'
+  classes:
+   - "opensteak::neutron-compute"
+   - "opensteak::nova-compute"
+subnets:
+ Admin:
+  shared: False
+  data:
+   network: "172.16.0.0"
+   mask: "255.255.255.0"
+   vlanid:
+   gateway: "172.16.0.1"
+   dns_primary: "172.16.0.1"
+   from: "172.16.0.10"
+   to: "172.16.0.200"
+   ipam: "DHCP"
+   boot_mode: "DHCP"
+
+foreman:
+ ip: "172.16.0.2"
+ password: "opnfv"
+ cpu: "2"
+ ram: "2097152"
+ iso: "trusty-server-cloudimg-amd64-disk1.img"
+ disksize: "5G"
+ force: True
+ dns: "8.8.8.8"
diff --git a/opensteak/tools/create_foreman.py b/opensteak/tools/create_foreman.py
new file mode 100644 (file)
index 0000000..6cf4510
--- /dev/null
@@ -0,0 +1,236 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+"""
+Create Virtual Machines
+"""
+
+# TODO: be sure that we are runnning as root
+
+from opensteak.conf import OpenSteakConfig
+from opensteak.printer import OpenSteakPrinter
+# from opensteak.argparser import OpenSteakArgParser
+from opensteak.templateparser import OpenSteakTemplateParser
+from opensteak.virsh import OpenSteakVirsh
+from pprint import pprint as pp
+# from ipaddress import IPv4Address
+import argparse
+import tempfile
+import shutil
+import os
+# import sys
+
+p = OpenSteakPrinter()
+
+#
+# Check for params
+#
+p.header("Check parameters")
+args = {}
+
+# Update args with values from CLI
+parser = argparse.ArgumentParser(description='This script will create a foreman VM.', usage='%(prog)s [options]')
+parser.add_argument('-c', '--config', help='YAML config file to use (default is config/infra.yaml).', default='config/infra.yaml')
+args.update(vars(parser.parse_args()))
+
+# Open config file
+conf = OpenSteakConfig(config_file=args["config"])
+# pp(conf.dump())
+
+a = {}
+a["name"] = "foreman"
+a["ip"] = conf["foreman"]["ip"]
+a["netmask"] = conf["subnets"]["Admin"]["data"]["mask"]
+a["netmaskshort"] = sum([bin(int(x)).count('1')
+                            for x in conf["subnets"]["Admin"]
+                                                    ["data"]["mask"]
+                            .split('.')])
+a["gateway"] = conf["subnets"]["Admin"]["data"]["gateway"]
+a["network"] = conf["subnets"]["Admin"]["data"]["network"]
+a["admin"] = conf["foreman"]["admin"]
+a["password"] = conf["foreman"]["password"]
+a["cpu"] = conf["foreman"]["cpu"]
+a["ram"] = conf["foreman"]["ram"]
+a["iso"] = conf["foreman"]["iso"]
+a["disksize"] = conf["foreman"]["disksize"]
+a["force"] = conf["foreman"]["force"]
+a["dhcprange"] = "{0} {1}".format(conf["subnets"]["Admin"]
+                                                    ["data"]["from"],
+                                     conf["subnets"]["Admin"]
+                                                    ["data"]["to"])
+a["domain"] = conf["domains"]
+reverse_octets = str(conf["foreman"]["ip"]).split('.')[-2::-1]
+a["reversedns"] = '.'.join(reverse_octets) + '.in-addr.arpa'
+a["dns"] = conf["foreman"]["dns"]
+a["bridge"] = conf["foreman"]["bridge"]
+if conf["foreman"]["bridge_type"] == "openvswitch":
+    a["bridgeconfig"] = "<virtualport type='openvswitch'></virtualport>"
+else:
+    # no specific config for linuxbridge
+    a["bridgeconfig"] = ""
+
+# Update args with values from config file
+args.update(a)
+del a
+
+p.list_id(args)
+
+# Ask confirmation
+if args["force"] is not True:
+    p.ask_validation()
+
+# Create the VM
+p.header("Initiate configuration")
+
+###
+# Work on templates
+###
+# Create temporary folders and files
+tempFolder = tempfile.mkdtemp(dir="/tmp")
+tempFiles = {}
+
+for f in os.listdir("templates_foreman/"):
+    tempFiles[f] = "{0}/{1}".format(tempFolder, f)
+    try:
+        OpenSteakTemplateParser("templates_foreman/{0}".format(f),
+                                tempFiles[f], args)
+    except Exception as err:
+        p.status(False, msg=("Something went wrong when trying to create "
+                             "the file {0} from the template "
+                             "templates_foreman/{1}").format(tempFiles[f], f),
+                 failed="{0}".format(err))
+
+###
+# Work on files
+###
+for f in os.listdir("files_foreman/"):
+    tempFiles[f] = "{0}/{1}".format(tempFolder, f)
+    shutil.copyfile("files_foreman/{0}".format(f), tempFiles[f])
+
+p.status(True, msg="Temporary files created:")
+p.list_id(tempFiles)
+
+
+###
+# Delete if already exists
+###
+
+# Get all volumes and VM
+p.header("Virsh calls")
+OpenSteakVirsh = OpenSteakVirsh()
+volumeList = OpenSteakVirsh.volumeList()
+domainList = OpenSteakVirsh.domainList()
+# p.list_id(volumeList)
+# p.list_id(domainList)
+
+# TODO: check that the default image is in the list
+# (trusty-server-cloudimg-amd64-disk1.img by default)
+
+# Delete the volume if exists
+try:
+    oldVolume = volumeList[args["name"]]
+
+    # Ask confirmation
+    if args["force"] is not True:
+        p.ask_validation()
+
+    status = OpenSteakVirsh.volumeDelete(volumeList[args["name"]])
+    if (status["stderr"]):
+        p.status(False, msg=status["stderr"])
+    p.status(True, msg=status["stdout"])
+except KeyError as err:
+    # no old volume, do nothing
+    pass
+
+# Delete the VM if exists
+try:
+    vmStatus = domainList[args["name"]]
+
+    # Ask confirmation
+    if args["force"] is not True:
+        p.ask_validation()
+
+    # Destroy (stop)
+    if vmStatus == "running":
+        status = OpenSteakVirsh.domainDestroy(args["name"])
+        if (status["stderr"]):
+            p.status(False, msg=status["stderr"])
+        p.status(True, msg=status["stdout"])
+
+    # Undefine (delete)
+    status = OpenSteakVirsh.domainUndefine(args["name"])
+    if (status["stderr"]):
+        p.status(False, msg=status["stderr"])
+    p.status(True, msg=status["stdout"])
+except KeyError as err:
+    # no old VM defined, do nothing
+    pass
+
+###
+# Create the configuration image file from metadata and userdata
+###
+status = OpenSteakVirsh.generateConfiguration(args["name"], tempFiles)
+if (status["stderr"]):
+    p.status(False, msg=status["stderr"])
+p.status(True, msg=("Configuration generated successfully in "
+                    "/var/lib/libvirt/images/{0}-configuration.iso")
+         .format(args["name"]))
+
+# Refresh the pool
+status = OpenSteakVirsh.poolRefresh()
+if (status["stderr"]):
+    p.status(False, msg=status["stderr"])
+p.status(True, msg=status["stdout"])
+
+###
+# Create the new VM
+###
+# Create the volume from a clone
+status = OpenSteakVirsh.volumeClone(args["iso"], args["name"])
+if (status["stderr"]):
+    p.status(False, msg=status["stderr"])
+p.status(True, msg=status["stdout"])
+
+# Resize the volume
+status = OpenSteakVirsh.volumeResize(args["name"], args["disksize"])
+if (status["stderr"]):
+    p.status(False, msg=status["stderr"])
+p.status(True, msg=status["stdout"])
+
+# Create the VM
+status = OpenSteakVirsh.domainDefine(tempFiles["kvm-config"])
+if (status["stderr"]):
+    p.status(False, msg=status["stderr"])
+p.status(True, msg=status["stdout"])
+
+
+###
+# Start the VM
+###
+status = OpenSteakVirsh.domainStart(args["name"])
+if (status["stderr"]):
+    p.status(False, msg=status["stderr"])
+p.status(True, msg=status["stdout"])
+
+p.status(True, msg="Log file is at: /var/log/libvirt/qemu/{0}-serial.log"
+                   .format(args["name"]))
+
+p.header("fini")
+
+# Delete temporary dir
+shutil.rmtree(tempFolder)
diff --git a/opensteak/tools/files_foreman/id_rsa b/opensteak/tools/files_foreman/id_rsa
new file mode 100644 (file)
index 0000000..d53ba88
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAz0jMplucYXoe0xJ21ASL98PGbwZYCI5Xr4/kHXOdGvHvZr3z
+58tWU1Ta4qMf0qa272VsdQiO1pCmSlqrDW5C9rEeqLhhRX/yLbgv35mOdjRoIIAX
+6RfNniT/xXrfvPZYdw603fIbbw5igTRwc6W5QvJHRcKRKb762Vw2gPSS0GgFBLCk
+vC2kQbW4cfP+9elo86FAhNBs2TbBHLc9H2W+9KzYfgsigjJLsgRXL6/uhu3+sL2d
+3F1J9Nhyy3aoUOVxD2YPJlJvzYhLZcSXgXI+Oi0gZmhh3uImc4WRyOihK5jRpJaw
+desygyXo4lVskzxBjm7L9ynbCNMOO85ZVVJGxQIDAQABAoIBAQCaOWcSy4yRtiPj
+FZTV8MAXS1GD36t2SjoRhLTL+O5GUwW1YtVrfA2xmKv2/jm6KJJpkgPdG83y9NLU
+9ZrZNlWaaHQQQocVB7ovrB/qdLzbU+i5bbTcl/pDlPG8g8yeMoflpUqK7AzfV0uR
+KGwWj5JErjC7RaVt8wt+164xykbFyZeUu9htNthFD/OPaIPqgv6AoJdEULyGrTbd
+SRyJ01n0beGkB0o+0dnOEO34K+pU0Zzk+rAcOEl3UNkpxOzedEFOR6NdnX1eH4t4
+a6OZgskcVjyxFQPAyhcSkQ2iWncQx2ritTclst4NFjBae5hwYgEB4S9ZN5IOueMH
+eYhxYthNAoGBAPXtSDmRGPc4EHDBrbgDn4vhxK7QN35bWFW1KvHLD0hBBJO57GqT
+jGCJsbkw6peERuFV8qq+Bvz0nvlKl9humB1djlndUETksUTrNz73XxpJJ8L5parF
+okx0QLMXONOP5b6yGWYay3QD0gNz/HYVf//oDTdWRhbq5EY6VarOagfjAoGBANfG
+UrlxEYHwq3TE7unvgaao5Vpmw8Hqir2bnl2zKmPoV8ds/V+paMnV6Hhzgzu3bKgF
+ukZgAizEcfvxrxnfIraRJTI5xgBoIl8gdbsWkLre4qKpVSAkw4JLyzVVlXCyKYHp
+ocjeNVbO5Z2Yft0cv30LfeX+DEDeQS12RHLu/Sc3AoGBAMns2ZfC5p/encknje8A
+spjVeHwdJOOQNxiwl6FPHK40DIELcO4VVnbRuGaZnpVoHBbbTlQZkX1TkdCZCdLB
+BA9giQiKamUW7eLry0HdNW5M0OQLvZZZjih+b71c/ODhTz/j1mz65UDN/jutmYaP
+orjJnUhpg0U/+s0bCsojj/YHAoGBAKtsMhiFjaUv8OdJ9Y0A7H3dPKk/b1JF5YeR
+dJV4W7sXwXT8T6eKTWfce14GV0JADSDHvB9g8xlh0DSa48OoFEn6shRe9cEo+fWd
+Mis6WC0+Gcukv65TxsdjM8PhhGIOCQ/e7ttIPhQDN0Sm/FLqHe9YC+OGm3GFoT5e
+8S5mU9StAoGABFwqkFELU84twzKYJCVPZPktwtfrD0Hkbd9pk0ebuSnQ3bATFIyU
+CDspTADbY2IgC53u+XAhTd5BOsicTtMM9x1p5EOglbK1ANagWuGlzVfdbp+bmql9
+S8AaH22lha5vCfHHfAN2NSkQ+ABZnNpP66nFx06VcyEYkhuZgd6s5A0=
+-----END RSA PRIVATE KEY-----
diff --git a/opensteak/tools/files_foreman/id_rsa.pub b/opensteak/tools/files_foreman/id_rsa.pub
new file mode 100644 (file)
index 0000000..8b4c6a1
--- /dev/null
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPSMymW5xheh7TEnbUBIv3w8ZvBlgIjlevj+Qdc50a8e9mvfPny1ZTVNriox/SprbvZWx1CI7WkKZKWqsNbkL2sR6ouGFFf/ItuC/fmY52NGgggBfpF82eJP/Fet+89lh3DrTd8htvDmKBNHBzpblC8kdFwpEpvvrZXDaA9JLQaAUEsKS8LaRBtbhx8/716WjzoUCE0GzZNsEctz0fZb70rNh+CyKCMkuyBFcvr+6G7f6wvZ3cXUn02HLLdqhQ5XEPZg8mUm/NiEtlxJeBcj46LSBmaGHe4iZzhZHI6KErmNGklrB16zKDJejiVWyTPEGObsv3KdsI0w47zllVUkbF arnaud@l-bibicy
diff --git a/opensteak/tools/opensteak/.gitignore b/opensteak/tools/opensteak/.gitignore
new file mode 100644 (file)
index 0000000..a65d046
--- /dev/null
@@ -0,0 +1,58 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
diff --git a/opensteak/tools/opensteak/__init__.py b/opensteak/tools/opensteak/__init__.py
new file mode 100644 (file)
index 0000000..01f9c9a
--- /dev/null
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+# This directory is a Python package.
diff --git a/opensteak/tools/opensteak/argparser.py b/opensteak/tools/opensteak/argparser.py
new file mode 100644 (file)
index 0000000..de980b6
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+# @author: Pawel Chomicki <pawel.chomicki@nokia.com>
+
+"""
+Parse arguments from CLI
+"""
+
+import argparse
+
+class OpenSteakArgParser:
+
+    def __init__(self):
+        """
+        Parse the command line
+        """
+        self.parser = argparse.ArgumentParser(description='This script will create config files for a VM in current folder.', usage='%(prog)s [options] name')
+        self.parser.add_argument('name', help='Set the name of the machine')
+        self.parser.add_argument('-i', '--ip', help='Set the ip address of the machine. (Default is 192.168.42.42)', default='192.168.42.42')
+        self.parser.add_argument('-n', '--netmask', help='Set the netmask in short format. (Default is 24)', default='24')
+        self.parser.add_argument('-g', '--gateway', help='Set the gateway to ping internet. (Default is 192.168.42.1)', default='192.168.42.1')
+        self.parser.add_argument('-p', '--password', help='Set the ssh password. Login is ubuntu. (Default password is moutarde)', default='moutarde')
+        self.parser.add_argument('-u', '--cpu', help='Set number of CPU for the VM. (Default is 2)', default='2')
+        self.parser.add_argument('-r', '--ram', help='Set quantity of RAM for the VM in kB. (Default is 2097152)', default='2097152')
+        self.parser.add_argument('-o', '--iso', help='Use this iso file. (Default is trusty-server-cloudimg-amd64-disk1.img)', default='trusty-server-cloudimg-amd64-disk1.img')
+        self.parser.add_argument('-d', '--disksize', help='Create a disk with that size. (Default is 5G)', default='5G')
+        self.parser.add_argument('-f', '--force', help='Force creation without asking questions. This is dangerous as it will delete old VM with same name.', default=False, action='store_true')
+
+    def parse(self):
+        return self.parser.parse_args()
+
diff --git a/opensteak/tools/opensteak/conf.py b/opensteak/tools/opensteak/conf.py
new file mode 100644 (file)
index 0000000..65eaf43
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from yaml import load, dump
+try:
+    from yaml import CLoader as Loader, CDumper as Dumper
+except ImportError:
+    from yaml import Loader, Dumper
+
+
+class OpenSteakConfig:
+    """OpenSteak config class
+    Use this object as a dict
+    """
+
+    def __init__(self,
+                 config_file="/usr/local/opensteak/infra/config/common.yaml",
+                 autosave=False):
+        """ Function __init__
+        Load saved opensteak config.
+
+        @param PARAM: DESCRIPTION
+        @return RETURN: DESCRIPTION
+        @param config_file: the yaml config file to read.
+            default is '/usr/local/opensteak/infra/config/common.yaml'
+        @param autosave: save automaticly the config at destroy
+            default is False
+        """
+        self.config_file = config_file
+        self.autosave = autosave
+        with open(self.config_file, 'r') as stream:
+            self._data = load(stream, Loader=Loader)
+
+    def __getitem__(self, index):
+        """Get an item of the configuration"""
+        return self._data[index]
+
+    def __setitem__(self, index, value):
+        """Set an item of the configuration"""
+        self._data[index] = value
+
+    def list(self):
+        """Set an item of the configuration"""
+        return self._data.keys()
+
+    def dump(self):
+        """Dump the configuration"""
+        return dump(self._data, Dumper=Dumper)
+
+    def save(self):
+        """Save the configuration to the file"""
+        with open(self.config_file, 'w') as f:
+            f.write(dump(self._data, Dumper=Dumper))
+
+    def __del__(self):
+        if self.autosave:
+            self.save()
diff --git a/opensteak/tools/opensteak/foreman.py b/opensteak/tools/opensteak/foreman.py
new file mode 100644 (file)
index 0000000..b7cbf42
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.api import Api
+from opensteak.foreman_objects.objects import ForemanObjects
+from opensteak.foreman_objects.domains import Domains
+from opensteak.foreman_objects.smart_proxies import SmartProxies
+from opensteak.foreman_objects.operatingsystems import OperatingSystems
+from opensteak.foreman_objects.hostgroups import HostGroups
+from opensteak.foreman_objects.hosts import Hosts
+from opensteak.foreman_objects.architectures import Architectures
+from opensteak.foreman_objects.subnets import Subnets
+from opensteak.foreman_objects.puppetClasses import PuppetClasses
+from opensteak.foreman_objects.compute_resources import ComputeResources
+
+
+class OpenSteakForeman:
+    """
+    HostGroup class
+    """
+    def __init__(self, password, login='admin', ip='127.0.0.1'):
+        """ Function __init__
+        Init the API with the connection params
+        @param password: authentication password
+        @param password: authentication login - default is admin
+        @param ip: api ip - default is localhost
+        @return RETURN: self
+        """
+        self.api = Api(login=login, password=password, ip=ip,
+                       printErrors=False)
+        self.domains = Domains(self.api)
+        self.smartProxies = SmartProxies(self.api)
+        self.puppetClasses = PuppetClasses(self.api)
+        self.operatingSystems = OperatingSystems(self.api)
+        self.architectures = Architectures(self.api)
+        self.subnets = Subnets(self.api)
+        self.hostgroups = HostGroups(self.api)
+        self.hosts = Hosts(self.api)
+        self.computeResources = ComputeResources(self.api)
+        self.environments =  ForemanObjects(self.api,
+                                            'environments',
+                                            'environment')
+        self.smartClassParameters =  ForemanObjects(self.api,
+                                                   'smart_class_parameters',
+                                                   'smart_class_parameter')
diff --git a/opensteak/tools/opensteak/foreman_objects/.gitignore b/opensteak/tools/opensteak/foreman_objects/.gitignore
new file mode 100644 (file)
index 0000000..a65d046
--- /dev/null
@@ -0,0 +1,58 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
diff --git a/opensteak/tools/opensteak/foreman_objects/__init__.py b/opensteak/tools/opensteak/foreman_objects/__init__.py
new file mode 100644 (file)
index 0000000..01f9c9a
--- /dev/null
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+# This directory is a Python package.
diff --git a/opensteak/tools/opensteak/foreman_objects/api.py b/opensteak/tools/opensteak/foreman_objects/api.py
new file mode 100644 (file)
index 0000000..dc99734
--- /dev/null
@@ -0,0 +1,197 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+import json
+import requests
+from requests_futures.sessions import FuturesSession
+from pprint import pformat
+
+
+class Api:
+    """
+    Api class
+    Class to deal with the foreman API v2
+    """
+    def __init__(self, password, login='admin', ip='127.0.0.1', printErrors=False):
+        """ Function __init__
+        Init the API with the connection params
+
+        @param password: authentication password
+        @param password: authentication login - default is admin
+        @param ip: api ip - default is localhost
+        @return RETURN: self
+        """
+        self.base_url = 'http://{}/api/v2/'.format(ip)
+        self.headers = {'Accept': 'version=2',
+                        'Content-Type': 'application/json; charset=UTF-8'}
+        self.auth = (login, password)
+        self.errorMsg = ''
+        self.printErrors = printErrors
+
+    def list(self, obj, filter=False, only_id=False, limit=20):
+        """ Function list
+        Get the list of an object
+
+        @param obj: object name ('hosts', 'puppetclasses'...)
+        @param filter: filter for objects
+        @param only_id: boolean to only return dict with name/id
+        @return RETURN: the list of the object
+        """
+        self.url = '{}{}/?per_page={}'.format(self.base_url, obj, limit)
+        if filter:
+            self.url += '&search={}'.format(filter)
+        self.resp = requests.get(url=self.url, auth=self.auth,
+                                 headers=self.headers)
+        if only_id:
+            if self.__process_resp__(obj) is False:
+                return False
+            if type(self.res['results']) is list:
+                return dict((x['name'], x['id']) for x in self.res['results'])
+            elif type(self.res['results']) is dict:
+                r = {}
+                for v in self.res['results'].values():
+                    for vv in v:
+                        r[vv['name']] = vv['id']
+                return r
+            else:
+                return False
+        else:
+            return self.__process_resp__(obj)
+
+    def get(self, obj, id, sub_object=None):
+        """ Function get
+        Get an object by id
+
+        @param obj: object name ('hosts', 'puppetclasses'...)
+        @param id: the id of the object (name or id)
+        @return RETURN: the targeted object
+        """
+        self.url = '{}{}/{}'.format(self.base_url, obj, id)
+        if sub_object:
+            self.url += '/' + sub_object
+        self.resp = requests.get(url=self.url, auth=self.auth,
+                                 headers=self.headers)
+        if self.__process_resp__(obj):
+            return self.res
+        return False
+
+    def get_id_by_name(self, obj, name):
+        """ Function get_id_by_name
+        Get the id of an object
+
+        @param obj: object name ('hosts', 'puppetclasses'...)
+        @param id: the id of the object (name or id)
+        @return RETURN: the targeted object
+        """
+        list = self.list(obj, filter='name = "{}"'.format(name),
+                         only_id=True, limit=1)
+        return list[name] if name in list.keys() else False
+
+    def set(self, obj, id, payload, action='', async=False):
+        """ Function set
+        Set an object by id
+
+        @param obj: object name ('hosts', 'puppetclasses'...)
+        @param id: the id of the object (name or id)
+        @param action: specific action of an object ('power'...)
+        @param payload: the dict of the payload
+        @param async: should this request be async, if true use
+                        return.result() to get the response
+        @return RETURN: the server response
+        """
+        self.url = '{}{}/{}'.format(self.base_url, obj, id)
+        if action:
+            self.url += '/{}'.format(action)
+        self.payload = json.dumps(payload)
+        if async:
+            session = FuturesSession()
+            return session.put(url=self.url, auth=self.auth,
+                               headers=self.headers, data=self.payload)
+        else:
+            self.resp = requests.put(url=self.url, auth=self.auth,
+                                     headers=self.headers, data=self.payload)
+            if self.__process_resp__(obj):
+                return self.res
+            return False
+
+    def create(self, obj, payload, async=False):
+        """ Function create
+        Create an new object
+
+        @param obj: object name ('hosts', 'puppetclasses'...)
+        @param payload: the dict of the payload
+        @param async: should this request be async, if true use
+                        return.result() to get the response
+        @return RETURN: the server response
+        """
+        self.url = self.base_url + obj
+        self.payload = json.dumps(payload)
+        if async:
+            session = FuturesSession()
+            return session.post(url=self.url, auth=self.auth,
+                                headers=self.headers, data=self.payload)
+        else:
+            self.resp = requests.post(url=self.url, auth=self.auth,
+                                      headers=self.headers,
+                                      data=self.payload)
+            return self.__process_resp__(obj)
+
+    def delete(self, obj, id):
+        """ Function delete
+        Delete an object by id
+
+        @param obj: object name ('hosts', 'puppetclasses'...)
+        @param id: the id of the object (name or id)
+        @return RETURN: the server response
+        """
+        self.url = '{}{}/{}'.format(self.base_url, obj, id)
+        self.resp = requests.delete(url=self.url,
+                                    auth=self.auth,
+                                    headers=self.headers, )
+        return self.__process_resp__(obj)
+
+    def __process_resp__(self, obj):
+        """ Function __process_resp__
+        Process the response sent by the server and store the result
+
+        @param obj: object name ('hosts', 'puppetclasses'...)
+        @return RETURN: the server response
+        """
+        self.last_obj = obj
+        if self.resp.status_code > 299:
+            self.errorMsg = ">> Error {} for object '{}'".format(self.resp.status_code,
+                                                                 self.last_obj)
+            try:
+                self.ret = json.loads(self.resp.text)
+                self.errorMsg += pformat(self.ret[list(self.ret.keys())[0]])
+            except:
+                self.ret = self.resp.text
+                self.errorMsg += self.ret
+            if self.printErrors:
+                print(self.errorMsg)
+            return False
+        self.res = json.loads(self.resp.text)
+        if 'results' in self.res.keys():
+            return self.res['results']
+        return self.res
+
+    def __str__(self):
+        ret = pformat(self.base_url) + "\n"
+        ret += pformat(self.headers) + "\n"
+        ret += pformat(self.auth) + "\n"
+        return ret
diff --git a/opensteak/tools/opensteak/foreman_objects/architectures.py b/opensteak/tools/opensteak/foreman_objects/architectures.py
new file mode 100644 (file)
index 0000000..5e4303e
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.objects import ForemanObjects
+
+
+class Architectures(ForemanObjects):
+    """
+    Architectures class
+    """
+    objName = 'architectures'
+    payloadObj = 'architecture'
+
+    def checkAndCreate(self, key, payload, osIds):
+        """ Function checkAndCreate
+        Check if an architectures exists and create it if not
+
+        @param key: The targeted architectures
+        @param payload: The targeted architectures description
+        @param osIds: The list of os ids liked with this architecture
+        @return RETURN: The id of the object
+        """
+        if key not in self:
+            self[key] = payload
+        oid = self[key]['id']
+        if not oid:
+            return False
+        #~ To be sure the OS list is good, we ensure our os are in the list
+        for os in self[key]['operatingsystems']:
+            osIds.add(os['id'])
+        self[key]["operatingsystem_ids"] = list(osIds)
+        if (len(self[key]['operatingsystems']) is not len(osIds)):
+            return False
+        return oid
diff --git a/opensteak/tools/opensteak/foreman_objects/compute_resources.py b/opensteak/tools/opensteak/foreman_objects/compute_resources.py
new file mode 100644 (file)
index 0000000..9ada9c4
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.objects import ForemanObjects
+from opensteak.foreman_objects.item import ForemanItem
+
+
+class ComputeResources(ForemanObjects):
+    """
+    HostGroups class
+    """
+    objName = 'compute_resources'
+    payloadObj = 'compute_resource'
+
+    def list(self):
+        """ Function list
+        list the hostgroups
+
+        @return RETURN: List of ForemanItemHostsGroup objects
+        """
+        return list(map(lambda x: ForemanItem(self.api, x['id'], x),
+                        self.api.list(self.objName)))
+
+    def __getitem__(self, key):
+        """ Function __getitem__
+        Get an hostgroup
+
+        @param key: The hostgroup name or ID
+        @return RETURN: The ForemanItemHostsGroup object of an host
+        """
+        # Because Hostgroup did not support get by name we need to do it by id
+        if type(key) is not int:
+            key = self.getId(key)
+        ret = self.api.get(self.objName, key)
+        return ForemanItem(self.api, key, ret)
+
+    def __delitem__(self, key):
+        """ Function __delitem__
+        Delete an hostgroup
+
+        @param key: The hostgroup name or ID
+        @return RETURN: The API result
+        """
+        # Because Hostgroup did not support get by name we need to do it by id
+        if type(key) is not int:
+            key = self.getId(key)
+        return self.api.delete(self.objName, key)
diff --git a/opensteak/tools/opensteak/foreman_objects/domains.py b/opensteak/tools/opensteak/foreman_objects/domains.py
new file mode 100644 (file)
index 0000000..753833f
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.objects import ForemanObjects
+
+
+class Domains(ForemanObjects):
+    """
+    Domain class
+    """
+    objName = 'domains'
+    payloadObj = 'domain'
+
+    def load(self, id='0', name=''):
+        """ Function load
+        To be rewriten
+
+        @param id: The Domain ID
+        @return RETURN: DESCRIPTION
+        """
+
+        if name:
+            id = self.__getIdByName__(name)
+        self.data = self.foreman.get('domains', id)
+        if 'parameters' in self.data:
+            self.params = self.data['parameters']
+        else:
+            self.params = []
+        self.name = self.data['name']
diff --git a/opensteak/tools/opensteak/foreman_objects/freeip.py b/opensteak/tools/opensteak/foreman_objects/freeip.py
new file mode 100644 (file)
index 0000000..86c003f
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+#~ from foreman.api import Api
+import requests
+from bs4 import BeautifulSoup
+import sys
+import json
+
+class FreeIP:
+    """ FreeIP return an available IP in the targeted network """
+
+    def __init__ (self, login, password):
+        """ Init: get authenticity token """
+        with requests.session() as self.session:
+            try:
+                #~ 1/ Get login token and authentify
+                payload = {}
+                log_soup = BeautifulSoup(self.session.get('https://127.0.0.1/users/login', verify=False).text)
+                payload['utf8'] = log_soup.findAll('input',attrs={'name':'utf8'})[0].get('value')
+                payload['authenticity_token'] = log_soup.findAll('input',attrs={'name':'authenticity_token'})[0].get('value')
+                if payload['authenticity_token'] == None:
+                    raise requests.exceptions.RequestException("Bad catch of authenticity_token")
+                payload['commit']='Login'
+                payload['login[login]'] = login
+                payload['login[password]'] = password
+                #~ 2/ Log in
+                r = self.session.post('https://127.0.0.1/users/login', verify=False, data=payload)
+                if r.status_code != 200:
+                    raise requests.exceptions.RequestException("Bad login or password")
+                #~ Get token for host creation
+                log_soup = BeautifulSoup(self.session.get('https://127.0.0.1/hosts/new', verify=False).text)
+                self.authenticity_token = log_soup.findAll('input',attrs={'name':'authenticity_token'})[0].get('value')
+                if payload['authenticity_token'] == None:
+                    raise requests.exceptions.RequestException("Bad catch of authenticity_token")
+            except requests.exceptions.RequestException as e:
+                print("Error connection Foreman to get a free ip")
+                print(e)
+                sys.exit(1)
+        pass
+
+    def get(self, subnet, mac = ""):
+        payload = {"host_mac": mac, "subnet_id": subnet}
+        payload['authenticity_token'] = self.authenticity_token
+        try:
+            self.last_ip = json.loads(self.session.post('https://127.0.0.1/subnets/freeip', verify=False, data=payload).text)['ip']
+            if payload['authenticity_token'] == None:
+                raise requests.exceptions.RequestException("Error getting free IP")
+        except requests.exceptions.RequestException as e:
+            print("Error connection Foreman to get a free ip")
+            print(e)
+            sys.exit(1)
+        return self.last_ip
+
+
+
+if __name__ == "__main__":
+    import pprint
+    import sys
+    if len(sys.argv) == 4:
+        f = FreeIP(sys.argv[1], sys.argv[2])
+        print(f.get(sys.argv[3]))
+    else:
+        print('Error: Usage\npython {} foreman_user foreman_password subnet'.format(sys.argv[0]))
diff --git a/opensteak/tools/opensteak/foreman_objects/hostgroups.py b/opensteak/tools/opensteak/foreman_objects/hostgroups.py
new file mode 100644 (file)
index 0000000..55b8ba6
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.objects import ForemanObjects
+from opensteak.foreman_objects.itemHostsGroup import ItemHostsGroup
+from pprint import pprint as pp
+
+
+class HostGroups(ForemanObjects):
+    """
+    HostGroups class
+    """
+    objName = 'hostgroups'
+    payloadObj = 'hostgroup'
+
+    def list(self):
+        """ Function list
+        list the hostgroups
+
+        @return RETURN: List of ItemHostsGroup objects
+        """
+        return list(map(lambda x: ItemHostsGroup(self.api, x['id'], x),
+                        self.api.list(self.objName)))
+
+    def __getitem__(self, key):
+        """ Function __getitem__
+        Get an hostgroup
+
+        @param key: The hostgroup name or ID
+        @return RETURN: The ItemHostsGroup object of an host
+        """
+        # Because Hostgroup did not support get by name we need to do it by id
+        if type(key) is not int:
+            key = self.getId(key)
+        ret = self.api.get(self.objName, key)
+        return ItemHostsGroup(self.api, key, ret)
+
+    def __delitem__(self, key):
+        """ Function __delitem__
+        Delete an hostgroup
+
+        @param key: The hostgroup name or ID
+        @return RETURN: The API result
+        """
+        # Because Hostgroup did not support get by name we need to do it by id
+        if type(key) is not int:
+            key = self.getId(key)
+        return self.api.delete(self.objName, key)
+
+    def checkAndCreate(self, key, payload,
+                       hostgroupConf,
+                       hostgroupParent,
+                       puppetClassesId):
+        """ Function checkAndCreate
+        check And Create procedure for an hostgroup
+        - check the hostgroup is not existing
+        - create the hostgroup
+        - Add puppet classes from puppetClassesId
+        - Add params from hostgroupConf
+
+        @param key: The hostgroup name or ID
+        @param payload: The description of the hostgroup
+        @param hostgroupConf: The configuration of the host group from the
+                              foreman.conf
+        @param hostgroupParent: The id of the parent hostgroup
+        @param puppetClassesId: The dict of puppet classes ids in foreman
+        @return RETURN: The ItemHostsGroup object of an host
+        """
+        if key not in self:
+            self[key] = payload
+        oid = self[key]['id']
+        if not oid:
+            return False
+
+        # Create Hostgroup classes
+        hostgroupClassIds = self[key]['puppetclass_ids']
+        if 'classes' in hostgroupConf.keys():
+            if not self[key].checkAndCreateClasses(puppetClassesId.values()):
+                print("Failed in classes")
+                return False
+
+        # Set params
+        if 'params' in hostgroupConf.keys():
+            if not self[key].checkAndCreateParams(hostgroupConf['params']):
+                print("Failed in params")
+                return False
+
+        return oid
diff --git a/opensteak/tools/opensteak/foreman_objects/hosts.py b/opensteak/tools/opensteak/foreman_objects/hosts.py
new file mode 100644 (file)
index 0000000..95d47af
--- /dev/null
@@ -0,0 +1,142 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.objects import ForemanObjects
+from opensteak.foreman_objects.itemHost import ItemHost
+import time
+
+
+class Hosts(ForemanObjects):
+    """
+    Host sclass
+    """
+    objName = 'hosts'
+    payloadObj = 'host'
+
+    def list(self):
+        """ Function list
+        list the hosts
+
+        @return RETURN: List of ItemHost objects
+        """
+        return list(map(lambda x: ItemHost(self.api, x['id'], x),
+                        self.api.list(self.objName)))
+
+    def __getitem__(self, key):
+        """ Function __getitem__
+        Get an host
+
+        @param key: The host name or ID
+        @return RETURN: The ItemHost object of an host
+        """
+        return ItemHost(self.api, key, self.api.get(self.objName, key))
+
+    def __printProgression__(self, status, msg, eol):
+        """ Function __printProgression__
+        Print the creation progression or not
+        It uses the foreman.printer lib
+
+        @param status: Status of the message
+        @param msg: Message
+        @param eol: End Of Line (to get a new line or not)
+        @return RETURN: None
+        """
+        if self.printHostProgress:
+            self.__printProgression__(status, msg, eol=eol)
+
+    def createVM(self, key, attributes, printHostProgress=False):
+        """ Function createVM
+        Create a Virtual Machine
+
+        The creation of a VM with libVirt is a bit complexe.
+        We first create the element in foreman, the ask to start before
+        the result of the creation.
+        To do so, we make async calls to the API and check the results
+
+        @param key: The host name or ID
+        @param attributes:The payload of the host creation
+        @param printHostProgress: The link to opensteak.printerlib
+                                to print or not the
+                                progression of the host creation
+        @return RETURN: The API result
+        """
+
+        self.printHostProgress = printHostProgress
+        self.async = True
+        # Create the VM in foreman
+        self.__printProgression__('In progress',
+                                  key + ' creation: push in Foreman', eol='\r')
+        future1 = self.api.create('hosts', attributes, async=True)
+
+        #  Wait before asking to power on the VM
+        sleep = 5
+        for i in range(0, sleep):
+            time.sleep(1)
+            self.__printProgression__('In progress',
+                                      key + ' creation: start in {0}s'
+                                      .format(sleep - i),
+                                      eol='\r')
+
+        #  Power on the VM
+        self.__printProgression__('In progress',
+                                  key + ' creation: starting', eol='\r')
+        future2 = self[key].powerOn()
+
+        #  Show Power on result
+        if future2.result().status_code is 200:
+            self.__printProgression__('In progress',
+                                      key + ' creation: wait for end of boot',
+                                      eol='\r')
+        else:
+            self.__printProgression__(False,
+                                      key + ' creation: Error',
+                                      failed=str(future2.result().status_code))
+            return False
+        #  Show creation result
+        if future1.result().status_code is 200:
+            self.__printProgression__('In progress',
+                                      key + ' creation: created',
+                                      eol='\r')
+        else:
+            self.__printProgression__(False,
+                                      key + ' creation: Error',
+                                      failed=str(future1.result().status_code))
+            return False
+
+        # Wait for puppet catalog to be applied
+        loop_stop = False
+        while not loop_stop:
+            status = self[key].getStatus()
+            if status == 'No Changes' or status == 'Active':
+                self.__printProgression__(True,
+                                          key + ' creation: provisioning OK')
+                loop_stop = True
+            elif status == 'Error':
+                self.__printProgression__(False,
+                                          key + ' creation: Error',
+                                          failed="Error during provisioning")
+                loop_stop = True
+                return False
+            else:
+                self.__printProgression__('In progress',
+                                          key + ' creation: provisioning ({})'
+                                          .format(status),
+                                          eol='\r')
+            time.sleep(5)
+
+        return True
diff --git a/opensteak/tools/opensteak/foreman_objects/item.py b/opensteak/tools/opensteak/foreman_objects/item.py
new file mode 100644 (file)
index 0000000..f418f8c
--- /dev/null
@@ -0,0 +1,135 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+#
+#    Authors:
+#     David Blaisonneau <david.blaisonneau@orange.com>
+#     Arnaud Morin <arnaud1.morin@orange.com>
+
+from pprint import pprint as pp
+
+
+class ForemanItem(dict):
+    """
+    Item class
+    Represent the content of a foreman object as a dict
+    """
+
+    def __init__(self, api, key,
+                 objName, payloadObj,
+                 *args, **kwargs):
+        """ Function __init__
+        Represent the content of a foreman object as a dict
+
+        @param api: The foreman api
+        @param key: The object Key
+        @param *args, **kwargs: the dict representation
+        @return RETURN: Itself
+        """
+        self.api = api
+        self.key = key
+        if objName:
+            self.objName = objName
+        if payloadObj:
+            self.payloadObj = payloadObj
+        self.store = dict()
+        if args[0]:
+            self.update(dict(*args, **kwargs))
+        # We get the smart class parameters for the good items
+        if objName in ['hosts', 'hostgroups',
+                       'puppet_classes', 'environments']:
+            from opensteak.foreman_objects.itemSmartClassParameter\
+                import ItemSmartClassParameter
+            scp_ids = map(lambda x: x['id'],
+                          self.api.list('{}/{}/smart_class_parameters'
+                                        .format(self.objName, key)))
+            scp_items = list(map(lambda x: ItemSmartClassParameter(self.api, x,
+                                 self.api.get('smart_class_parameters', x)),
+                                 scp_ids))
+            scp = {'{}::{}'.format(x['puppetclass']['name'],
+                                   x['parameter']): x
+                   for x in scp_items}
+            self.update({'smart_class_parameters_dict': scp})
+
+    def __setitem__(self, key, attributes):
+        """ Function __setitem__
+        Set a parameter of a foreman object as a dict
+
+        @param key: The key to modify
+        @param attribute: The data
+        @return RETURN: The API result
+        """
+        if key is 'puppetclass_ids':
+            payload = {"puppetclass_id": attributes,
+                       self.payloadObj + "_class":
+                           {"puppetclass_id": attributes}}
+            return self.api.create("{}/{}/{}"
+                                   .format(self.objName,
+                                           self.key,
+                                           "puppetclass_ids"),
+                                   payload)
+        elif key is 'parameters':
+            payload = {"parameter": attributes}
+            return self.api.create("{}/{}/{}"
+                                   .format(self.objName,
+                                           self.key,
+                                           "parameters"),
+                                   payload)
+        else:
+            payload = {self.payloadObj: {key: attributes}}
+            return self.api.set(self.objName, self.key, payload)
+
+    def getParam(self, name=None):
+        """ Function getParam
+        Return a dict of parameters or a parameter value
+
+        @param key: The parameter name
+        @return RETURN: dict of parameters or a parameter value
+        """
+        if 'parameters' in self.keys():
+            l = {x['name']: x['value'] for x in self['parameters']}
+            if name:
+                if name in l.keys():
+                    return l[name]
+                else:
+                    return False
+            else:
+                return l
+
+    def checkAndCreateClasses(self, classes):
+        """ Function checkAndCreateClasses
+        Check and add puppet classe
+
+        @param key: The parameter name
+        @param classes: The classes ids list
+        @return RETURN: boolean
+        """
+        actual_classes = self['puppetclass_ids']
+        for v in classes:
+            if v not in actual_classes:
+                self['puppetclass_ids'] = v
+        return list(classes).sort() is list(self['puppetclass_ids']).sort()
+
+    def checkAndCreateParams(self, params):
+        """ Function checkAndCreateParams
+        Check and add global parameters
+
+        @param key: The parameter name
+        @param params: The params dict
+        @return RETURN: boolean
+        """
+        actual_params = self['param_ids']
+        for k, v in params.items():
+            if k not in actual_params:
+                self['parameters'] = {"name": k, "value": v}
+        return self['param_ids'].sort() == list(params.values()).sort()
diff --git a/opensteak/tools/opensteak/foreman_objects/itemHost.py b/opensteak/tools/opensteak/foreman_objects/itemHost.py
new file mode 100644 (file)
index 0000000..c531e5c
--- /dev/null
@@ -0,0 +1,141 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+import base64
+from string import Template
+from opensteak.foreman_objects.item import ForemanItem
+
+
+class ItemHost(ForemanItem):
+    """
+    ItemHostsGroup class
+    Represent the content of a foreman hostgroup as a dict
+    """
+
+    objName = 'hosts'
+    payloadObj = 'host'
+
+    def __init__(self, api, key, *args, **kwargs):
+        """ Function __init__
+        Represent the content of a foreman object as a dict
+
+        @param api: The foreman api
+        @param key: The object Key
+        @param *args, **kwargs: the dict representation
+        @return RETURN: Itself
+        """
+        ForemanItem.__init__(self, api, key,
+                             self.objName, self.payloadObj,
+                             *args, **kwargs)
+        self.update({'puppetclass_ids':
+                     self.api.list('{}/{}/puppetclass_ids'
+                                   .format(self.objName, key))})
+        self.update({'param_ids':
+                     list(self.api.list('{}/{}/parameters'
+                                        .format(self.objName, key),
+                                        only_id=True)
+                          .keys())})
+
+
+    def getStatus(self):
+        """ Function getStatus
+        Get the status of an host
+
+        @return RETURN: The host status
+        """
+        return self.api.get('hosts', self.key, 'status')['status']
+
+    def powerOn(self):
+        """ Function powerOn
+        Power on a host
+
+        @return RETURN: The API result
+        """
+        return self.api.set('hosts', self.key,
+                            {"power_action": "start"},
+                            'power', async=self.async)
+
+    def getParamFromEnv(self, var, default=''):
+        """ Function getParamFromEnv
+        Search a parameter in the host environment
+
+        @param var: the var name
+        @param hostgroup: the hostgroup item linked to this host
+        @param default: default value
+        @return RETURN: the value
+        """
+        if self.getParam(var):
+            return self.getParam(var)
+        if self.hostgroup:
+            if self.hostgroup.getParam(var):
+                return self.hostgroup.getParam(var)
+        if self.domain.getParam('password'):
+            return self.domain.getParam('password')
+        else:
+            return default
+
+    def getUserData(self,
+                    hostgroup,
+                    domain,
+                    defaultPwd='',
+                    defaultSshKey='',
+                    proxyHostname='',
+                    tplFolder='templates_metadata/'):
+        """ Function getUserData
+        Generate a userdata script for metadata server from Foreman API
+
+        @param domain: the domain item linked to this host
+        @param hostgroup: the hostgroup item linked to this host
+        @param defaultPwd: the default password if no password is specified
+                           in the host>hostgroup>domain params
+        @param defaultSshKey: the default ssh key if no password is specified
+                              in the host>hostgroup>domain params
+        @param proxyHostname: hostname of the smartproxy
+        @param tplFolder: the templates folder
+        @return RETURN: the user data
+        """
+        if 'user-data' in self.keys():
+            return self['user-data']
+        else:
+            self.hostgroup = hostgroup
+            self.domain = domain
+            if proxyHostname == '':
+                proxyHostname = 'foreman.' + domain
+            password = self.getParamFromEnv('password', defaultPwd)
+            sshauthkeys = self.getParamFromEnv('global_sshkey', defaultSshKey)
+            with open(tplFolder+'puppet.conf', 'rb') as puppet_file:
+                p = MyTemplate(puppet_file.read())
+                enc_puppet_file = base64.b64encode(p.substitute(
+                    foremanHostname=proxyHostname))
+            with open(tplFolder+'cloud-init.tpl', 'r') as content_file:
+                s = MyTemplate(content_file.read())
+                if sshauthkeys:
+                    sshauthkeys = ' - '+sshauthkeys
+                self.userdata = s.substitute(
+                    password=password,
+                    fqdn=self['name'],
+                    sshauthkeys=sshauthkeys,
+                    foremanurlbuilt="http://{}/unattended/built"
+                                    .format(proxyHostname),
+                    puppet_conf_content=enc_puppet_file.decode('utf-8'))
+                return self.userdata
+
+
+class MyTemplate(Template):
+    delimiter = '%'
+    idpattern = r'[a-z][_a-z0-9]*'
diff --git a/opensteak/tools/opensteak/foreman_objects/itemHostsGroup.py b/opensteak/tools/opensteak/foreman_objects/itemHostsGroup.py
new file mode 100644 (file)
index 0000000..d6a641c
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.item import ForemanItem
+
+
+class ItemHostsGroup(ForemanItem):
+    """
+    ItemHostsGroup class
+    Represent the content of a foreman hostgroup as a dict
+    """
+
+    objName = 'hostgroups'
+    payloadObj = 'hostgroup'
+
+    def __init__(self, api, key, *args, **kwargs):
+        """ Function __init__
+        Represent the content of a foreman object as a dict
+
+        @param api: The foreman api
+        @param key: The object Key
+        @param *args, **kwargs: the dict representation
+        @return RETURN: Itself
+        """
+        ForemanItem.__init__(self, api, key,
+                             self.objName, self.payloadObj,
+                             *args, **kwargs)
+        self.update({'puppetclass_ids':
+                     self.api.list('{}/{}/puppetclass_ids'
+                                   .format(self.objName, key))})
+        self.update({'param_ids':
+                     list(self.api.list('{}/{}/parameters'
+                                        .format(self.objName, key),
+                                        only_id=True)
+                          .keys())})
diff --git a/opensteak/tools/opensteak/foreman_objects/itemOverrideValues.py b/opensteak/tools/opensteak/foreman_objects/itemOverrideValues.py
new file mode 100644 (file)
index 0000000..936185e
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+
+from opensteak.foreman_objects.item import ForemanItem
+from pprint import pprint as pp
+
+class ItemOverrideValues(ForemanItem):
+    """
+    ItemOverrideValues class
+    Represent the content of a foreman smart class parameter as a dict
+    """
+
+    objName = 'override_values'
+    payloadObj = 'override_value'
+
+    def __init__(self, api, key, parentName, parentKey, *args, **kwargs):
+        """ Function __init__
+        Represent the content of a foreman object as a dict
+
+        @param api: The foreman api
+        @param key: The object Key
+        @param parentName: The object parent name (eg: smart_class_parameter)
+        @param parentKey: The object parent key
+        @param *args, **kwargs: the dict representation
+        @return RETURN: Itself
+        """
+        self.parentName = parentName
+        self.parentKey = parentKey
+        ForemanItem.__init__(self, api, key,
+                             self.objName, self.payloadObj,
+                             *args, **kwargs)
+
+    def __setitem__(self, key, attributes):
+        """ Function __setitem__
+        Set a parameter of a foreman object as a dict
+
+        @param key: The key to modify
+        @param attribute: The data
+        @return RETURN: The API result
+        """
+        payload = {self.payloadObj: {key: attributes}}
+        return self.api.set('{}/{}/{}'.format(self.parentName,
+                                              self.parentKey,
+                                              self.objName),
+                            self.key, payload)
diff --git a/opensteak/tools/opensteak/foreman_objects/itemSmartClassParameter.py b/opensteak/tools/opensteak/foreman_objects/itemSmartClassParameter.py
new file mode 100644 (file)
index 0000000..2d7ca2a
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+
+from opensteak.foreman_objects.item import ForemanItem
+from opensteak.foreman_objects.itemOverrideValues import ItemOverrideValues
+
+
+class ItemSmartClassParameter(ForemanItem):
+    """
+    ItemSmartClassParameter class
+    Represent the content of a foreman smart class parameter as a dict
+    """
+
+    objName = 'smart_class_parameters'
+    payloadObj = 'smart_class_parameter'
+
+    def __init__(self, api, key, *args, **kwargs):
+        """ Function __init__
+        Represent the content of a foreman object as a dict
+
+        @param api: The foreman api
+        @param key: The object Key
+        @param *args, **kwargs: the dict representation
+        @return RETURN: Itself
+        """
+        ForemanItem.__init__(self, api, key,
+                             self.objName, self.payloadObj,
+                             *args, **kwargs)
+        self.update({'override_values':
+            list(map(lambda x: ItemOverrideValues(self.api,
+                                                  x['id'],
+                                                  self.objName,
+                                                  key,
+                                                  x),
+                 self['override_values']))})
+
+    def __setitem__(self, key, attributes):
+        """ Function __setitem__
+        Set a parameter of a foreman object as a dict
+
+        @param key: The key to modify
+        @param attribute: The data
+        @return RETURN: The API result
+        """
+        payload = {self.payloadObj: {key: attributes}}
+        return self.api.set(self.objName, self.key, payload)
diff --git a/opensteak/tools/opensteak/foreman_objects/objects.py b/opensteak/tools/opensteak/foreman_objects/objects.py
new file mode 100644 (file)
index 0000000..c20c5a1
--- /dev/null
@@ -0,0 +1,136 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.item import ForemanItem
+
+
+class ForemanObjects:
+    """
+    ForemanObjects class
+    Parent class for Foreman Objects
+    """
+
+    def __init__(self, api, objName=None, payloadObj=None):
+        """ Function __init__
+        Init the foreman object
+
+        @param api: The foreman API
+        @param objName: The object name (linked with the Foreman API)
+        @param payloadObj: The object name inside the payload (in general
+                           the singular of objName)
+        @return RETURN: Itself
+        """
+
+        self.api = api
+        if objName:
+            self.objName = objName
+        if payloadObj:
+            self.payloadObj = payloadObj
+        # For asynchronous creations
+        self.async = False
+
+    def __iter__(self):
+        """ Function __iter__
+
+        @return RETURN: The iteration of objects list
+        """
+        return iter(self.list())
+
+    def __getitem__(self, key):
+        """ Function __getitem__
+
+        @param key: The targeted object
+        @return RETURN: A ForemanItem
+        """
+        return ForemanItem(self.api,
+                           key,
+                           self.objName,
+                           self.payloadObj,
+                           self.api.get(self.objName, key))
+
+    def __setitem__(self, key, attributes):
+        """ Function __setitem__
+
+        @param key: The targeted object
+        @param attributes: The attributes to apply to the object
+        @return RETURN: API result if the object was not present, or False
+        """
+        if key not in self:
+            payload = {self.payloadObj: {'name': key}}
+            payload[self.payloadObj].update(attributes)
+            return self.api.create(self.objName, payload, async=self.async)
+        return False
+
+    def __delitem__(self, key):
+        """ Function __delitem__
+
+        @return RETURN: API result
+        """
+        return self.api.delete(self.objName, key)
+
+    def __contains__(self, key):
+        """ Function __contains__
+
+        @param key: The targeted object
+        @return RETURN: True if the object exists
+        """
+        return bool(key in self.listName().keys())
+
+    def getId(self, key):
+        """ Function getId
+        Get the id of an object
+
+        @param key: The targeted object
+        @return RETURN: The ID
+        """
+        return self.api.get_id_by_name(self.objName, key)
+
+    def list(self, limit=20):
+        """ Function list
+        Get the list of all objects
+
+        @param key: The targeted object
+        @param limit: The limit of items to return
+        @return RETURN: A ForemanItem list
+        """
+        return list(map(lambda x:
+                        ForemanItem(self.api, x['id'],
+                                    self.objName, self.payloadObj,
+                                    x),
+                        self.api.list(self.objName, limit=limit)))
+
+    def listName(self):
+        """ Function listName
+        Get the list of all objects name with Ids
+
+        @param key: The targeted object
+        @return RETURN: A dict of obejct name:id
+        """
+        return self.api.list(self.objName, limit=999999, only_id=True)
+
+    def checkAndCreate(self, key, payload):
+        """ Function checkAndCreate
+        Check if an object exists and create it if not
+
+        @param key: The targeted object
+        @param payload: The targeted object description
+        @return RETURN: The id of the object
+        """
+        if key not in self:
+            self[key] = payload
+        return self[key]['id']
diff --git a/opensteak/tools/opensteak/foreman_objects/operatingsystems.py b/opensteak/tools/opensteak/foreman_objects/operatingsystems.py
new file mode 100644 (file)
index 0000000..8cce606
--- /dev/null
@@ -0,0 +1,66 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.objects import ForemanObjects
+from opensteak.foreman_objects.item import ForemanItem
+
+
+class OperatingSystems(ForemanObjects):
+    """
+    OperatingSystems class
+    """
+    objName = 'operatingsystems'
+    payloadObj = 'operatingsystem'
+
+    def __getitem__(self, key):
+        """ Function __getitem__
+
+        @param key: The operating system id/name
+        @return RETURN: The item
+        """
+        ret = self.api.list(self.objName,
+                            filter='title = "{}"'.format(key))
+        if len(ret):
+            return ForemanItem(self.api, key,
+                               self.objName, self.payloadObj,
+                               ret[0])
+        else:
+            return None
+
+    def __setitem__(self, key, attributes):
+        """ Function __getitem__
+
+        @param key: The operating system id/name
+        @param attributes: The content of the operating system to create
+        @return RETURN: The API result
+        """
+        if key not in self:
+            payload = {self.payloadObj: {'title': key}}
+            payload[self.payloadObj].update(attributes)
+            return self.api.create(self.objName, payload)
+        return False
+
+    def listName(self):
+        """ Function listName
+        Get the list of all objects name with Ids
+
+        @param key: The targeted object
+        @return RETURN: A dict of obejct name:id
+        """
+        return { x['title']: x['id'] for x in self.api.list(self.objName,
+                                                            limit=999999)}
diff --git a/opensteak/tools/opensteak/foreman_objects/puppetClasses.py b/opensteak/tools/opensteak/foreman_objects/puppetClasses.py
new file mode 100644 (file)
index 0000000..7f397f2
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.objects import ForemanObjects
+from opensteak.foreman_objects.item import ForemanItem
+from pprint import pprint as pp
+
+
+class PuppetClasses(ForemanObjects):
+    """
+    OperatingSystems class
+    """
+    objName = 'puppetclasses'
+    payloadObj = 'puppetclass'
+
+    def list(self, limit=20):
+        """ Function list
+        Get the list of all objects
+
+        @param key: The targeted object
+        @param limit: The limit of items to return
+        @return RETURN: A ForemanItem list
+        """
+        puppetClassList = list()
+        for v in self.api.list(self.objName, limit=limit).values():
+            puppetClassList.extend(v)
+        return list(map(lambda x:
+                        ForemanItem(self.api, x['id'],
+                                    self.objName, self.payloadObj,
+                                    x),
+                        puppetClassList))
diff --git a/opensteak/tools/opensteak/foreman_objects/smart_proxies.py b/opensteak/tools/opensteak/foreman_objects/smart_proxies.py
new file mode 100644 (file)
index 0000000..2d6518b
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.objects import ForemanObjects
+
+
+class SmartProxies(ForemanObjects):
+    """
+    Domain class
+    """
+    objName = 'smart_proxies'
+    payloadObj = 'smart_proxy'
+
+    def importPuppetClasses(self, smartProxyId):
+        """ Function importPuppetClasses
+        Force the reload of puppet classes
+
+        @param smartProxyId: smartProxy Id
+        @return RETURN: the API result
+        """
+        return self.api.create('smart_proxies/{}/import_puppetclasses'.format(smartProxyId), '{}')
diff --git a/opensteak/tools/opensteak/foreman_objects/subnets.py b/opensteak/tools/opensteak/foreman_objects/subnets.py
new file mode 100644 (file)
index 0000000..b1cac54
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+from opensteak.foreman_objects.objects import ForemanObjects
+
+
+class Subnets(ForemanObjects):
+    """
+    Subnets class
+    """
+    objName = 'subnets'
+    payloadObj = 'subnet'
+
+    def checkAndCreate(self, key, payload, domainId):
+        """ Function checkAndCreate
+        Check if a subnet exists and create it if not
+
+        @param key: The targeted subnet
+        @param payload: The targeted subnet description
+        @param domainId: The domainId to be attached wiuth the subnet
+        @return RETURN: The id of the subnet
+        """
+        if key not in self:
+            self[key] = payload
+        oid = self[key]['id']
+        if not oid:
+            return False
+        #~ Ensure subnet contains the domain
+        subnetDomainIds = []
+        for domain in self[key]['domains']:
+            subnetDomainIds.append(domain['id'])
+        if domainId not in subnetDomainIds:
+            subnetDomainIds.append(domainId)
+            self[key]["domain_ids"] = subnetDomainIds
+            if len(self[key]["domains"]) is not len(subnetDomainIds):
+                return False
+        return oid
+
+    def removeDomain(self, subnetId, domainId):
+        """ Function removeDomain
+        Delete a domain from a subnet
+
+        @param subnetId: The subnet Id
+        @param domainId: The domainId to be attached wiuth the subnet
+        @return RETURN: boolean
+        """
+        subnetDomainIds = []
+        for domain in self[subnetId]['domains']:
+            subnetDomainIds.append(domain['id'])
+        subnetDomainIds.remove(domainId)
+        self[subnetId]["domain_ids"] = subnetDomainIds
+        return len(self[subnetId]["domains"]) is len(subnetDomainIds)
diff --git a/opensteak/tools/opensteak/printer.py b/opensteak/tools/opensteak/printer.py
new file mode 100644 (file)
index 0000000..98c5af5
--- /dev/null
@@ -0,0 +1,141 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+import sys
+
+
+class OpenSteakPrinter:
+    """ Just a nice message printer """
+    HEADER = '\033[95m'
+    OKBLUE = '\033[94m'
+    OKGREEN = '\033[92m'
+    WARNING = '\033[93m'
+    FAIL = '\033[91m'
+    ENDC = '\033[0m'
+    BOLD = '\033[1m'
+    UNDERLINE = '\033[4m'
+
+    TABSIZE = 4
+
+    def header(self, msg):
+        """ Function header
+        Print a header for a block
+
+        @param msg: The message to print in the header (limited to 78 chars)
+        @return RETURN: None
+        """
+        print("""
+#
+# {}
+#
+""".format(msg[0:78]))
+
+    def config(self, msg, name, value=None, indent=0):
+        """ Function config
+        Print a line with the value of a parameter
+
+        @param msg: The message to print in the header (limited to 78 chars)
+        @param name: The name of the prameter
+        @param value: The value of the parameter
+        @param indent: Tab size at the beginning of the line
+        @return RETURN: None
+        """
+        ind = ' ' * indent * self.TABSIZE
+        if value is None:
+            print('{} - {} = {}'.format(ind, msg, name))
+        elif value is False:
+            print('{} [{}KO{}] {} > {} (NOT found)'.
+                  format(ind, self.FAIL, self.ENDC, msg, name))
+        else:
+            print('{} [{}OK{}] {} > {} = {}'.
+                  format(ind, self.OKGREEN, self.ENDC, msg, name, str(value)))
+
+    def list(self, msg, indent=0):
+        """ Function list
+        Print a list item
+
+        @param msg: The message to print in the header (limited to 78 chars)
+        @param indent: Tab size at the beginning of the line
+        @return RETURN: None
+        """
+        print(' ' * indent * self.TABSIZE, '-', msg)
+
+    def list_id(self, dic, indent=0):
+        """ Function list_id
+        Print a list of dict items
+
+        @param dic: The dict to print
+        @param indent: Tab size at the beginning of the line
+        @return RETURN: None
+        """
+        for (k, v) in dic.items():
+            self.list("{}: {}".format(k, v), indent=indent)
+
+    def status(self, res, msg, failed="", eol="\n", quit=True, indent=0):
+        """ Function status
+        Print status message
+        - OK/KO if the result is a boolean
+        - Else the result text
+
+        @param res: The status to show
+        @param msg: The message to show
+        @param eol: End of line
+        @param quit: Exit the system in case of failure
+        @param indent: Tab size at the beginning of the line
+        @return RETURN: None
+        """
+        ind = ' ' * indent * self.TABSIZE
+        if res is True:
+            msg = '{} [{}OK{}] {}'.format(ind, self.OKGREEN, self.ENDC, msg)
+        elif res:
+            msg = '{} [{}{}{}] {}'.format(ind, self.OKBLUE, res,
+                                          self.ENDC, msg)
+        else:
+            msg = '{} [{}KO{}] {}'.format(ind, self.FAIL, self.ENDC, msg)
+            if failed:
+                msg += '\n > {}'.format(failed)
+        msg = msg.ljust(140) + eol
+        sys.stdout.write(msg)
+        if res is False and quit is True:
+            sys.exit(0)
+
+    def ask_validation(self, prompt=None, resp=False):
+        """ Function ask_validation
+        Ask a validation message
+
+        @param prompt: The question to ask ('Continue ?') if None
+        @param resp: The default value (Default is False)
+        @return RETURN: Trie or False
+        """
+        if prompt is None:
+            prompt = 'Continue ?'
+        if resp:
+            prompt += ' [{}Y{}/n]: '.format(self.BOLD, self.ENDC)
+        else:
+            prompt += ' [y/{}N{}]: '.format(self.BOLD, self.ENDC)
+        while True:
+            ans = input(prompt)
+            if not ans:
+                ans = 'y' if resp else 'n'
+            if ans not in ['y', 'Y', 'n', 'N']:
+                print('please enter y or n.')
+                continue
+            if ans == 'y' or ans == 'Y':
+                return True
+            if ans == 'n' or ans == 'N':
+                sys.exit(0)
diff --git a/opensteak/tools/opensteak/templateparser.py b/opensteak/tools/opensteak/templateparser.py
new file mode 100644 (file)
index 0000000..720f008
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+"""
+Template parser
+"""
+
+from string import Template
+
+class OpenSteakTemplateParser:
+
+    def __init__(self, filein, fileout, dictionary):
+        """
+        Parse the files with the dictionary
+        """
+        fin = open(filein)
+        fout = open(fileout,'w')
+        template = Template(fin.read())
+        fout.write(template.substitute(dictionary))
diff --git a/opensteak/tools/opensteak/virsh.py b/opensteak/tools/opensteak/virsh.py
new file mode 100644 (file)
index 0000000..594b842
--- /dev/null
@@ -0,0 +1,174 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+"""
+Virsh library
+"""
+
+import subprocess
+import os
+
+class OpenSteakVirsh:
+
+    virsh = "/usr/bin/virsh"
+    genisoimage = "/usr/bin/genisoimage"
+    environment = ""
+
+    ###
+    # INIT
+    ###
+    def __init__(self):
+        self.environment = dict(os.environ)  # Copy current environment
+        self.environment['LANG'] = 'en_US.UTF-8'
+
+
+    ###
+    # VOLUMES
+    ###
+    def volumeList(self, pool="default"):
+        """
+        Return all volumes from a pool
+        """
+        p = subprocess.Popen([self.virsh, "-q", "vol-list", pool], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment)
+        stdout, stderr = p.communicate()
+
+        # Split lines
+        lines = stdout.splitlines()
+
+        # Foreach line, split with space and construct a dictionnary
+        newLines = {}
+        for line in lines:
+            name, path = line.split(maxsplit=1)
+            newLines[name.strip()] = path.strip()
+
+        return newLines
+
+    def volumeDelete(self, path):
+        """
+        Delete a volume
+        """
+        p = subprocess.Popen([self.virsh, "-q", "vol-delete", path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment)
+        stdout, stderr = p.communicate()
+
+        return {"stdout":stdout, "stderr":stderr}
+
+    def volumeClone(self, origin, name, pool="default"):
+        """
+        Clone a volume
+        """
+        p = subprocess.Popen([self.virsh, "-q", "vol-clone", "--pool", pool, origin, name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment)
+        stdout, stderr = p.communicate()
+
+        return {"stdout":stdout, "stderr":stderr}
+
+    def volumeResize(self, name, size, pool="default"):
+        """
+        Resize a volume
+        """
+        p = subprocess.Popen([self.virsh, "-q", "vol-resize", "--pool", pool, name, size], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment)
+        stdout, stderr = p.communicate()
+
+        return {"stdout":stdout, "stderr":stderr}
+
+    ###
+    # POOLS
+    ###
+    def poolRefresh(self, pool="default"):
+        """
+        Refresh a pool
+        """
+        p = subprocess.Popen([self.virsh, "-q", "pool-refresh", pool], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment)
+        stdout, stderr = p.communicate()
+
+        return {"stdout":stdout, "stderr":stderr}
+
+    ###
+    # DOMAINS
+    ###
+    def domainList(self):
+        """
+        Return all domains (VM)
+        """
+        p = subprocess.Popen([self.virsh, "-q", "list", "--all"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment)
+        stdout, stderr = p.communicate()
+
+        # Split lines
+        lines = stdout.splitlines()
+
+        # Foreach line, split with space and construct a dictionnary
+        newLines = {}
+        for line in lines:
+            id, name, status = line.split(maxsplit=2)
+            newLines[name.strip()] = status.strip()
+
+        return newLines
+
+    def domainDefine(self, xml):
+        """
+        Define a domain (create a VM)
+        """
+        p = subprocess.Popen([self.virsh, "-q", "define", xml], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment)
+        stdout, stderr = p.communicate()
+
+        return {"stdout":stdout, "stderr":stderr}
+
+    def domainUndefine(self, name):
+        """
+        Undefine a domain (delete a VM)
+        """
+        p = subprocess.Popen([self.virsh, "-q", "undefine", name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment)
+        stdout, stderr = p.communicate()
+
+        return {"stdout":stdout, "stderr":stderr}
+
+    def domainStart(self, name):
+        """
+        Define a domain (create a VM)
+        """
+        p = subprocess.Popen([self.virsh, "-q", "start", name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment)
+        stdout, stderr = p.communicate()
+
+        return {"stdout":stdout, "stderr":stderr}
+
+    def domainDestroy(self, name):
+        """
+        Destroy a domain (stop a VM)
+        """
+        p = subprocess.Popen([self.virsh, "-q", "destroy", name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment)
+        stdout, stderr = p.communicate()
+
+        return {"stdout":stdout, "stderr":stderr}
+
+    ###
+    # ISO
+    ###
+    def generateConfiguration(self, name, files):
+        """
+        Generate an ISO file
+        """
+
+        commandArray = [self.genisoimage, "-quiet", "-o", "/var/lib/libvirt/images/{0}-configuration.iso".format(name), "-volid", "cidata", "-joliet", "-rock"]
+        for k, f in files.items():
+            commandArray.append(f)
+
+        # Generate the iso file
+        p = subprocess.Popen(commandArray, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment)
+        stdout, stderr = p.communicate()
+
+        return {"stdout":stdout, "stderr":stderr}
+
diff --git a/opensteak/tools/templates_foreman/install.sh b/opensteak/tools/templates_foreman/install.sh
new file mode 100644 (file)
index 0000000..497be86
--- /dev/null
@@ -0,0 +1,216 @@
+#!/bin/sh
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# Authors:
+# @author: David Blaisonneau <david.blaisonneau@orange.com>
+# @author: Arnaud Morin <arnaud1.morin@orange.com>
+
+### Set vars
+NAME="${name}"
+DOMAIN="${domain}"
+DATEE=$$(date +%F-%Hh%M)
+IP="${ip}"
+MASK="${netmaskshort}"
+NET="${network}"
+DHCP_RANGE="${dhcprange}"
+REVERSE_DNS="${reversedns}"
+DNS_FORWARDER="${dns}"
+ADMIN="${admin}"
+PASSWORD="${password}"
+
+### Set correct env
+#dpkg-reconfigure locales
+export LC_CTYPE=en_US.UTF-8
+export LANG=en_US.UTF-8
+unset LC_ALL
+umask 0022
+
+### Check hostname is on the public interface
+echo "* Ensure hostname point to external IP"
+# Remove useless lines
+perl -i -pe 's/^127.0.1.1.*\n$$//' /etc/hosts
+perl -i -pe "s/^$${IP}.*\n$$//" /etc/hosts
+# Append a line
+echo "$${IP} $${NAME}.$${DOMAIN} $${NAME}" >> /etc/hosts
+
+### Dependencies
+echo "* Install dependencies"
+apt-get -y install ca-certificates wget git isc-dhcp-server
+
+### Set AppArmor
+echo "* Set App armor"
+cat /etc/apparmor.d/local/usr.sbin.dhcpd | grep '/etc/bind/rndc.key r,' >/dev/null
+if [ $$? -eq 1 ] ; then
+    echo "/etc/bind/rndc.key r," >> /etc/apparmor.d/local/usr.sbin.dhcpd
+fi
+
+### Prepare repos
+echo "* Enable Puppet labs repo"
+if [ "Z" = "Z$$(dpkg -l |grep 'ii  puppetlabs-release')" ] ; then
+    wget https://apt.puppetlabs.com/puppetlabs-release-trusty.deb
+    dpkg -i puppetlabs-release-trusty.deb
+    apt-get update
+fi
+
+# Install puppetmaster
+echo "* Install puppetmaster"
+if [ "Z" = "Z$$(dpkg -l |grep 'ii  puppetmaster')" ] ; then
+    apt-get -y install puppetmaster
+fi
+
+# Enable the Foreman repo
+echo "* Enable Foreman repo"
+if [ ! -e /etc/apt/sources.list.d/foreman.list ] ; then
+    echo "deb http://deb.theforeman.org/ trusty 1.8" > /etc/apt/sources.list.d/foreman.list
+    echo "deb http://deb.theforeman.org/ plugins 1.8" >> /etc/apt/sources.list.d/foreman.list
+    wget -q http://deb.theforeman.org/pubkey.gpg -O- | apt-key add -
+    apt-get update
+fi
+
+### Install Foreman
+echo "* Install foreman-installer"
+if [ "Z" = "Z$$(dpkg -l |grep 'ii  foreman-installer')" ] ; then
+    apt-get -y install foreman-installer
+fi
+if [ "Z" = "Z$$(gem list --local |grep rubyipmi)" ] ; then
+    gem install -q rubyipmi
+fi
+
+### Execute foreman installer
+echo "* Execute foreman installer"
+
+foreman-installer \
+ --foreman-admin-username="$$ADMIN" \
+ --foreman-admin-password="$$PASSWORD" \
+ --enable-foreman-plugin-templates \
+ --enable-foreman-plugin-discovery \
+ --foreman-plugin-discovery-install-images=true \
+ --enable-foreman-compute-libvirt
+
+
+foreman-installer \
+ --foreman-admin-username="$$ADMIN" \
+ --foreman-admin-password="$$PASSWORD" \
+ --enable-foreman-plugin-templates \
+ --enable-foreman-plugin-discovery \
+ --foreman-plugin-discovery-install-images=true \
+ --enable-foreman-compute-libvirt \
+ --enable-foreman-proxy \
+ --foreman-proxy-bmc=true \
+ --foreman-proxy-tftp=true \
+ --foreman-proxy-tftp-servername="$$IP" \
+ --foreman-proxy-dhcp=true \
+ --foreman-proxy-dhcp-interface="eth0" \
+ --foreman-proxy-dhcp-gateway="$$IP" \
+ --foreman-proxy-dhcp-range="$$DHCP_RANGE" \
+ --foreman-proxy-dhcp-nameservers="$$IP" \
+ --foreman-proxy-dns=true \
+ --foreman-proxy-dns-interface="eth0" \
+ --foreman-proxy-dns-zone="$$DOMAIN" \
+ --foreman-proxy-dns-reverse="$$REVERSE_DNS" \
+ --foreman-proxy-dns-forwarders="$$DNS_FORWARDER" \
+ --foreman-proxy-foreman-base-url="https://localhost"
+
+### Sync community templates for last ubuntu versions
+
+echo "* Sync community templates for last ubuntu versions"
+foreman-rake templates:sync
+
+### Get and install OpenSteak files
+
+echo "* Get OpenSteak repos"
+if [ -d /usr/local/opensteak ] ; then
+    cd /usr/local/opensteak
+    git pull
+else
+    cd /usr/local/
+    git clone https://github.com/Orange-OpenSource/opnfv.git -b foreman opensteak
+fi
+cd /usr/local/opensteak/infra/puppet_master
+
+echo "* Set puppet auth"
+echo "*.$$DOMAIN" > /etc/puppet/autosign.conf
+if [ -e /etc/puppet/auth.conf ] ; then
+  # Make a backup
+  mv /etc/puppet/auth.conf /etc/puppet/auth.conf.$$DATEE
+fi
+cp etc/puppet/auth.conf /etc/puppet/auth.conf
+perl -i -pe "s/__NET__/$$NET/" /etc/puppet/auth.conf
+perl -i -pe "s/__MASK__/$$MASK/" /etc/puppet/auth.conf
+
+# Set Hiera Conf
+echo "* Push Hiera conf into /etc/puppet/"
+if [ -e /etc/puppet/hiera.yaml ] ; then
+  # Make a backup
+  mv /etc/puppet/hiera.yaml /etc/puppet/hiera.yaml.$$DATEE
+fi
+cp etc/puppet/hiera.yaml /etc/puppet/hiera.yaml
+if [ -e /etc/hiera.yaml ] ; then
+  rm /etc/hiera.yaml
+fi
+ln -s /etc/puppet/hiera.yaml /etc/hiera.yaml
+cp -rf etc/puppet/hieradata /etc/puppet/
+rename s/DOMAIN/$$DOMAIN/ /etc/puppet/hieradata/production/nodes/*.yaml
+cp etc/puppet/manifests/site.pp /etc/puppet/manifests/site.pp
+cp ../config/common.yaml /etc/puppet/hieradata/production/common.yaml
+chgrp puppet /etc/puppet/hieradata/production/*.yaml
+
+# Install and config r10k
+echo "* Install and setup r10k"
+if [ "Z" = "Z$$(gem list --local |grep r10k)" ] ; then
+    gem install -q r10k
+fi
+if [ -e /etc/r10k.yaml ] ; then
+  # Make a backup
+  mv /etc/r10k.yaml /etc/r10k.yaml.$$DATEE
+fi
+cp etc/r10k.yaml /etc/r10k.yaml
+
+# Install opensteak-r10k-update script
+echo "* Install opensteak-r10k-update script into /usr/local/bin"
+cp usr/local/bin/opensteak-r10k-update /usr/local/bin/opensteak-r10k-update
+chmod +x /usr/local/bin/opensteak-r10k-update
+
+echo "* Run R10k. You can re-run r10k by calling:"
+echo "   opensteak-r10k-update"
+opensteak-r10k-update
+
+#### Install VIM puppet
+echo "* Install VIM puppet"
+if [ ! -d ~/.vim/autoload ] ; then
+  mkdir -p ~/.vim/autoload
+fi
+if [ ! -d ~/.vim/bundle ] ; then
+  mkdir -p ~/.vim/bundle
+fi
+curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
+cat <<EOF > ~/.vimrc
+execute pathogen#infect()
+syntax on
+filetype plugin indent on
+EOF
+cd ~/.vim/bundle
+if [ ! -d vim-puppet ] ; then
+  git clone https://github.com/rodjek/vim-puppet.git > /dev/null
+fi
+
+### Gen SSH key for foreman
+echo "* SSH Key"
+cp /mnt/id_rsa /usr/share/foreman/.ssh/
+cp /mnt/id_rsa.pub /usr/share/foreman/.ssh/
+chown foreman:foreman /usr/share/foreman/.ssh/ -R
+
+### Run puppet
+puppet agent -t -v
+
diff --git a/opensteak/tools/templates_foreman/kvm-config b/opensteak/tools/templates_foreman/kvm-config
new file mode 100644 (file)
index 0000000..7e3d65d
--- /dev/null
@@ -0,0 +1,65 @@
+<domain type='kvm'>
+  <name>${name}</name>
+  <memory>${ram}</memory>
+  <currentMemory>${ram}</currentMemory>
+  <vcpu>${cpu}</vcpu>
+  <os>
+    <type arch='x86_64'>hvm</type>
+    <!-- uncomment to enable PXE boot
+    <boot dev='network'/>
+    -->
+    <boot dev='hd'/>
+  </os>
+  <features>
+    <acpi/><apic/><pae/>
+  </features>
+  <clock offset="utc"/>
+  <on_poweroff>preserve</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>restart</on_crash>
+  <devices>
+    <emulator>/usr/bin/qemu-system-x86_64</emulator>
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='qcow2'/>
+      <source file='/var/lib/libvirt/images/${name}'/>
+      <target dev='vda' bus='virtio'/>
+    </disk>
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='raw'/>
+      <source file='/var/lib/libvirt/images/${name}-configuration.iso'/>
+      <target dev='vdb' bus='virtio'/>
+    </disk>
+    <input type='mouse' bus='ps2'/>
+    <!-- uncomment to allow virsh console
+    <console type='pty'/>
+    <!- - end -->
+    <!-- uncomment to allow console to a log file -->
+    <serial type='file'>
+      <source path='/var/log/libvirt/qemu/${name}-serial.log'/>
+      <target port='0'/>
+      <alias name='serial0'/>
+    </serial>
+    <serial type='pty'>
+      <source path='/dev/pts/1'/>
+      <target port='1'/>
+      <alias name='serial1'/>
+    </serial>
+    <console type='file'>
+      <source path='/var/log/libvirt/qemu/${name}-serial.log'/>
+      <target type='serial' port='0'/>
+      <alias name='serial0'/>
+    </console>
+    <!-- end -->
+    <graphics type='spice' port='-1' autoport='no'/>
+    <video>
+      <model type='qxl' ram='65536' vram='65536' heads='1'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
+    </video>
+    <memballoon model='virtio'/>
+    <interface type='bridge'>
+      <source bridge='${bridge}'/>
+      ${bridgeconfig}
+      <model type='virtio'/>
+    </interface>
+  </devices>
+</domain>
diff --git a/opensteak/tools/templates_foreman/meta-data b/opensteak/tools/templates_foreman/meta-data
new file mode 100644 (file)
index 0000000..b4cb9b6
--- /dev/null
@@ -0,0 +1,12 @@
+instance-id: ${name};
+network-interfaces: |
+  auto lo
+  iface lo inet loopback
+  auto eth0
+  iface eth0 inet static
+    address ${ip}
+    netmask ${netmaskshort}
+    gateway ${gateway}
+    dns-nameservers ${dns}
+    dns-search ${domain}
+local-hostname: ${name}
diff --git a/opensteak/tools/templates_foreman/user-data b/opensteak/tools/templates_foreman/user-data
new file mode 100644 (file)
index 0000000..281b5d4
--- /dev/null
@@ -0,0 +1,25 @@
+#cloud-config
+#############################################
+# OPENSTEAK VM '${name}'
+#############################################
+password: ${password}
+chpasswd: { expire: False }
+ssh_pwauth: True
+dsmode: net
+hostname: ${name}
+#############################################
+# FIRST BOOT COMMAND
+# - reload main interface
+# - install puppet from puppetlabs
+# - remove cloud-init
+#############################################
+runcmd:
+ - [ sh, -c, "mount /dev/vdb /mnt"]
+ - [ sh, -c, "sudo bash /mnt/install.sh"]
+# This is the id_rsa.sansmotdepasse key
+ssh_authorized_keys:
+  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDY15cdBmIs2XOpe4EiFCsaY6bmUmK/GysMoLl4UG51JCfJwvwoWCoA+6mDIbymZxhxq9IGxilp/yTA6WQ9s/5pBag1cUMJmFuda9PjOkXl04jgqh5tR6I+GZ97AvCg93KAECis5ubSqw1xOCj4utfEUtPoF1OuzqM/lE5mY4N6VKXn+fT7pCD6cifBEs6JHhVNvs5OLLp/tO8Pa3kKYQOdyS0xc3rh+t2lrzvKUSWGZbX+dLiFiEpjsUL3tDqzkEMNUn4pdv69OJuzWHCxRWPfdrY9Wg0j3mJesP29EBht+w+EC9/kBKq+1VKdmsXUXAcjEvjovVL8l1BrX3BY0R8D sansmotdepasse
+#############################################
+# FINAL MESSAGE AT END OF BOOT
+#############################################
+final_message: "The system '${name}' is finally up, after $$UPTIME seconds"