Commit 467a447e by Karsa Zoltán István

add extra parameters to cloud-init

parent a1e745ed
{% load i18n %}
<p class="text-right">
<a class="btn btn-primary" data-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false" aria-controls="collapseExample">
Cloud-init options
</a>
</p>
<div class="collapse" id="collapseExample">
<div class="card card-body">
<b> {% trans "Use keys in ci-data:" %}</b>
<ul>
<li>{% trans "<strong>{{hostname}}</strong> - vm hostname" %}</li>
<li>{% trans "<strong>{{sysuser}}</strong> - vm system default user (cloud)" %}</li>
<li>{% trans "<strong>{{password}}</strong> - default user sha512 password" %}</li>
<li>{% trans "<strong>{{owner}}</strong> - vm owner django username" %}</li>
<li>{% trans "<strong>{{acl.allusers}}</strong> - get all associated user's username to vm" %}</li>
<li>{% trans "<strong>{{acl.operators}}</strong> - get all associated operator level user's username to vm" %}</li>
<li>{% trans "<strong>{{acl.users}}</strong> - get all associated operator user user's username to vm" %}</li>
<li>{% trans "<strong>{{net.ipv4}}</strong> - default host ip address" %}</li>
<li>{% trans "<strong>{{net.ipv6}}</strong> - default host ipv6 address" %}</li>
<li>{% trans "<strong>{{net.[vlan_name].ipv4/ipv6}}</strong> - associated vlan ([vlan_name]) host ip addresses, example: when vm associated to vlan 'public' and vlan 'internal', we can get these ips: {{net.public.ipv4}} and {{net.internal.ipv4}}" %}</li>
<li>{% trans "<strong>{{rndstr%[length]}}</strong> - make random string with [length] charachters lenght" %}</li>
</ul>
</div>
</div>
\ No newline at end of file
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
{{ form.description|as_crispy_field }} {{ form.description|as_crispy_field }}
{{ form.system|as_crispy_field }} {{ form.system|as_crispy_field }}
<hr/> <hr/>
{% include "dashboard/_ci-data-help.html" %}
{{ form.cloud_init|as_crispy_field }} {{ form.cloud_init|as_crispy_field }}
{{ form.ci_meta_data|as_crispy_field }} {{ form.ci_meta_data|as_crispy_field }}
{{ form.ci_user_data|as_crispy_field }} {{ form.ci_user_data|as_crispy_field }}
......
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
{{ form.system|as_crispy_field }} {{ form.system|as_crispy_field }}
{{ form.has_agent|as_crispy_field }} {{ form.has_agent|as_crispy_field }}
<hr/> <hr/>
{% include "dashboard/_ci-data-help.html" %}
{{ form.cloud_init|as_crispy_field }} {{ form.cloud_init|as_crispy_field }}
{{ form.ci_meta_data|as_crispy_field }} {{ form.ci_meta_data|as_crispy_field }}
{{ form.ci_user_data|as_crispy_field }} {{ form.ci_user_data|as_crispy_field }}
......
...@@ -86,8 +86,11 @@ ...@@ -86,8 +86,11 @@
<h3> <h3>
{% trans "Cloud-init" %} {% trans "Cloud-init" %}
</h3> </h3>
{% crispy ci_data %} {% crispy ci_data %}
<p>Keys (in "{{ ci_key }}") mapping for cloud-init:</p>
<small>
{{ci_data_mapping}}
</small>
</div> </div>
</div> </div>
{% endif %} {% endif %}
......
...@@ -56,7 +56,7 @@ def domain_validator(value): ...@@ -56,7 +56,7 @@ def domain_validator(value):
def meta_data_validator(value): def meta_data_validator(value):
try: try:
Instance.validate_meta_data(value) Instance.validate_ci_data(value)
except yaml.YAMLError as exc: except yaml.YAMLError as exc:
if hasattr(exc, 'problem_mark'): if hasattr(exc, 'problem_mark'):
if exc.context != None: if exc.context != None:
...@@ -72,7 +72,7 @@ def meta_data_validator(value): ...@@ -72,7 +72,7 @@ def meta_data_validator(value):
def user_data_validator(value): def user_data_validator(value):
try: try:
Instance.validate_user_data(value) Instance.validate_ci_data(value)
except yaml.YAMLError as exc: except yaml.YAMLError as exc:
if hasattr(exc, 'problem_mark'): if hasattr(exc, 'problem_mark'):
if exc.context != None: if exc.context != None:
......
...@@ -146,6 +146,8 @@ class VmDetailView(GraphMixin, CheckedDetailView): ...@@ -146,6 +146,8 @@ class VmDetailView(GraphMixin, CheckedDetailView):
'connect_commands': user.profile.get_connect_commands(instance), 'connect_commands': user.profile.get_connect_commands(instance),
'hide_tutorial': hide_tutorial, 'hide_tutorial': hide_tutorial,
'fav': instance.favourite_set.filter(user=user).exists(), 'fav': instance.favourite_set.filter(user=user).exists(),
'ci_data_mapping' : json.dumps(instance.get_ci_data_dict()),
'ci_key': '{{key}}'
}) })
# activity data # activity data
......
...@@ -15,11 +15,13 @@ ...@@ -15,11 +15,13 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import random, string
from contextlib import contextmanager from contextlib import contextmanager
from datetime import timedelta from datetime import timedelta
from dbm import dumb
from functools import partial from functools import partial
from importlib import import_module from importlib import import_module
from ipaddress import ip_interface
from logging import getLogger from logging import getLogger
from warnings import warn from warnings import warn
from xml.dom.minidom import Text from xml.dom.minidom import Text
...@@ -39,6 +41,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop ...@@ -39,6 +41,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop
from passlib.hash import sha512_crypt from passlib.hash import sha512_crypt
import yaml import yaml
import re
from model_utils import Choices from model_utils import Choices
from model_utils.managers import QueryManager from model_utils.managers import QueryManager
...@@ -46,7 +49,7 @@ from model_utils.models import TimeStampedModel, StatusModel ...@@ -46,7 +49,7 @@ from model_utils.models import TimeStampedModel, StatusModel
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from django.db import models from django.db import models
import json
from acl.models import AclBase from acl.models import AclBase
from django import forms from django import forms
from common.models import ( from common.models import (
...@@ -81,7 +84,7 @@ CI_USER_DATA_DEF = """ ...@@ -81,7 +84,7 @@ CI_USER_DATA_DEF = """
users: users:
- default - default
- name: {{user}} - name: {{sysuser}}
sudo: ['ALL=(ALL) NOPASSWD:ALL'] sudo: ['ALL=(ALL) NOPASSWD:ALL']
groups: sudo groups: sudo
shell: /bin/bash shell: /bin/bash
...@@ -139,9 +142,9 @@ class VirtualMachineDescModel(BaseResourceConfigModel): ...@@ -139,9 +142,9 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
cloud_init = BooleanField(verbose_name=_('Use Cloud-init'), default=False, cloud_init = BooleanField(verbose_name=_('Use Cloud-init'), default=False,
help_text=_( help_text=_(
'VM use cloud-init, set user- and meta-data below')) 'VM use cloud-init, set user- and meta-data below'))
ci_meta_data = TextField(verbose_name=_('ci_meta_data'), blank=True, help_text=_( ci_meta_data = TextField(verbose_name=_('CI Meta Data'), blank=True, help_text=_(
'When cloud-init is active, set meta-data (YAML format)'), default=CI_META_DATA_DEF) 'When cloud-init is active, set meta-data (YAML format)'), default=CI_META_DATA_DEF)
ci_user_data = TextField(verbose_name=_('ci_user_data'), blank=True, help_text=_( ci_user_data = TextField(verbose_name=_('CI User Data'), blank=True, help_text=_(
'When cloud-init is active, set user-data (YAML format)'), default=CI_USER_DATA_DEF) 'When cloud-init is active, set user-data (YAML format)'), default=CI_USER_DATA_DEF)
req_traits = ManyToManyField(Trait, blank=True, req_traits = ManyToManyField(Trait, blank=True,
help_text=_("A set of traits required for a " help_text=_("A set of traits required for a "
...@@ -364,33 +367,60 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -364,33 +367,60 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
DeprecationWarning) DeprecationWarning)
return self.status return self.status
def get_ci_data_dict(self):
datas = {
"sysuser": "cloud",
"hostname": self.short_hostname,
"password": sha512_crypt.hash(self.pw),
"net.ipv4": str(self.ipv4),
"net.ipv6": str(self.ipv6),
"owner": str(self.owner.username)
}
for net in self.interface_set.all():
if net.host:
datas["net.%s.ipv4" % net.vlan.name] = str(net.host.ipv4)
datas["net.%s.ipv6" % net.vlan.name] = str(net.host.ipv6)
user_levels = list({ 'username': u.username, 'level': l } for u, l in self.get_users_with_level())
datas['acl.allusers'] = yaml.dump(list(k['username'] for k in user_levels), default_flow_style=None)
datas['acl.operators'] = yaml.dump(list(k['username'] for k in user_levels if k['level'] == 'operator'), default_flow_style=None)
datas['acl.users'] = yaml.dump(list(k['username'] for k in user_levels if k['level'] == 'user'), default_flow_style=None)
return datas
@property @property
def get_user_data(self): def get_user_data(self):
passwd = sha512_crypt.hash(self.pw) data = str(self.ci_user_data)
user = 'cloud' ci_datas = self.get_ci_data_dict()
data = str(self.ci_user_data).replace('{{user}}', user) for k, v in ci_datas.items():
data = data.replace('{{password}}', passwd) repl = '{{%s}}' % k
data = data.replace(repl, v)
data = Instance.__replace_rndstr(data)
return data return data
@classmethod
def validate_user_data(cls, user_data):
data = user_data.replace('{{user}}', 'user')
data = data.replace('{{password}}', 'passwd')
yaml.dump(yaml.load(data, Loader=yaml.Loader))
return True
@property @property
def get_meta_data(self): def get_meta_data(self):
data = str(self.ci_meta_data).replace('{{hostname}}', self.short_hostname) data = str(self.ci_meta_data)
ci_datas = self.get_ci_data_dict()
for k, v in ci_datas.items():
repl = '{{%s}}' % k
data = data.replace(repl, v)
data = Instance.__replace_rndstr(data)
return data
@classmethod
def __replace_rndstr(cls, data):
search = re.search(r'\{\{rndstr%(\d+)\}\}', data)
while search:
randomstr = ''.join(random.choice(string.ascii_letters) for i in range(int(search.group(1))))
data = data.replace(search.group(0), randomstr, 1)
search = re.search(r'\{\{rndstr%(\d+)\}\}', data)
return data return data
@classmethod @classmethod
def validate_meta_data(cls, meta_data): def validate_ci_data(cls, meta_data):
data = meta_data.replace('{{hostname}}', 'hostname') data = re.sub(r'\{\{([a-zA-Z_.0-9%]+)\}\}', 'ci-data', meta_data)
yaml.dump(yaml.load(data, Loader=yaml.Loader)) yaml.dump(yaml.load(data, Loader=yaml.Loader))
return True return True
def _update_status(self): def _update_status(self):
"""Set the proper status of the instance to Instance.status. """Set the proper status of the instance to Instance.status.
""" """
......
...@@ -498,6 +498,7 @@ class DeployOperation(InstanceOperation): ...@@ -498,6 +498,7 @@ class DeployOperation(InstanceOperation):
description = _("create cloud init iso from user and meta-data") description = _("create cloud init iso from user and meta-data")
def _operation(self, user, activity, name=None): def _operation(self, user, activity, name=None):
import json
logger.info('create ci image') logger.info('create ci image')
disk = Disk.create_ci_disk(meta_data=self.instance.get_meta_data, disk = Disk.create_ci_disk(meta_data=self.instance.get_meta_data,
user_data=self.instance.get_user_data) user_data=self.instance.get_user_data)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment