177 lines
5.1 KiB
Python
177 lines
5.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2015 Mirantis, Inc.
|
|
#
|
|
# 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.
|
|
|
|
from copy import deepcopy
|
|
import json
|
|
from multipledispatch import dispatch
|
|
import os
|
|
import uuid
|
|
|
|
from solar.interfaces.db import get_db
|
|
from solar import utils
|
|
|
|
|
|
db = get_db()
|
|
|
|
|
|
# TODO: cycle detection?
|
|
# TODO: write this as a Cypher query? Move to DB?
|
|
def _read_input_value(input_node):
|
|
rel = db.get_relations(dest=input_node,
|
|
type_=db.RELATION_TYPES.input_to_input)
|
|
|
|
if not rel:
|
|
v = input_node.properties['value'] or 'null'
|
|
return json.loads(v)
|
|
|
|
if input_node.properties['is_list']:
|
|
return [_read_input_value(r.start_node) for r in rel]
|
|
|
|
return _read_input_value(rel[0].start_node)
|
|
|
|
|
|
def prepare_meta(meta):
|
|
actions_path = os.path.join(meta['base_path'], 'actions')
|
|
meta['actions_path'] = actions_path
|
|
meta['base_name'] = os.path.split(meta['base_path'])[-1]
|
|
|
|
meta['actions'] = {}
|
|
if os.path.exists(meta['actions_path']):
|
|
for f in os.listdir(meta['actions_path']):
|
|
meta['actions'][os.path.splitext(f)[0]] = f
|
|
|
|
|
|
def read_meta(base_path):
|
|
base_meta_file = os.path.join(base_path, 'meta.yaml')
|
|
|
|
metadata = utils.yaml_load(base_meta_file)
|
|
metadata['version'] = '1.0.0'
|
|
metadata['base_path'] = os.path.abspath(base_path)
|
|
|
|
return metadata
|
|
|
|
|
|
class Resource(object):
|
|
_metadata = {}
|
|
|
|
# Create
|
|
@dispatch(str, str, dict)
|
|
def __init__(self, name, base_path, args, tags=None, virtual_resource=None):
|
|
self.name = name
|
|
if base_path:
|
|
self.metadata = read_meta(base_path)
|
|
else:
|
|
self.metadata = deepcopy(self._metadata)
|
|
|
|
self.metadata['id'] = name
|
|
|
|
self.tags = tags or []
|
|
self.virtual_resource = virtual_resource
|
|
|
|
self.node = db.get_or_create(
|
|
name,
|
|
properties={
|
|
'actions_path': self.metadata.get('actions_path', ''),
|
|
'base_name': self.metadata.get('base_name', ''),
|
|
'base_path': self.metadata.get('base_path', ''),
|
|
'handler': self.metadata.get('handler', ''),
|
|
'id': self.metadata['id'],
|
|
'version': self.metadata.get('version', ''),
|
|
},
|
|
collection=db.COLLECTIONS.resource
|
|
)
|
|
|
|
self.set_args_from_dict(args)
|
|
|
|
# Load
|
|
@dispatch(object)
|
|
def __init__(self, resource_node):
|
|
self.node = resource_node
|
|
self.name = resource_node.uid
|
|
self.metadata = read_meta(resource_node.properties['base_path'])
|
|
self.metadata.update(resource_node.properties)
|
|
self.tags = []
|
|
self.virtual_resource = None
|
|
|
|
@property
|
|
def actions(self):
|
|
return self.metadata.get('actions') or []
|
|
|
|
# TODO: json.dumps/loads should be probably moved to neo4j.py
|
|
def set_args_from_dict(self, args):
|
|
self.node.pull()
|
|
|
|
for k, v in self.metadata['input'].items():
|
|
value = args.get(k, v.get('value'))
|
|
|
|
uid = '{}-{}'.format(k, uuid.uuid4())
|
|
|
|
i = db.get_or_create(
|
|
uid,
|
|
properties={
|
|
'is_list': isinstance(v.get('schema'), list),
|
|
'input_name': k,
|
|
'value': json.dumps(value),
|
|
},
|
|
collection=db.COLLECTIONS.input
|
|
)
|
|
db.get_or_create_relation(
|
|
self.node,
|
|
i,
|
|
properties={},
|
|
type_=db.RELATION_TYPES.resource_input
|
|
)
|
|
|
|
@property
|
|
def args(self):
|
|
ret = {}
|
|
for k, n in self.resource_inputs().items():
|
|
ret[k] = _read_input_value(n)
|
|
return ret
|
|
|
|
def update(self, args):
|
|
# TODO: disconnect input when it is updated and and end_node
|
|
# for some input_to_input relation
|
|
resource_inputs = self.resource_inputs()
|
|
|
|
for k, v in args.items():
|
|
i = resource_inputs[k]
|
|
i.properties['value'] = json.dumps(v)
|
|
i.push()
|
|
|
|
def resource_inputs(self):
|
|
resource_inputs = [
|
|
r.end_node for r in
|
|
db.get_relations(source=self.node,
|
|
type_=db.RELATION_TYPES.resource_input)
|
|
]
|
|
|
|
return {
|
|
i.properties['input_name']: i for i in resource_inputs
|
|
}
|
|
|
|
|
|
def load(name):
|
|
r = db.get(name, collection=db.COLLECTIONS.resource)
|
|
|
|
if not r:
|
|
raise Exception('Resource {} does not exist in DB'.format(name))
|
|
|
|
return wrap_resource(r)
|
|
|
|
|
|
def wrap_resource(resource_node):
|
|
return Resource(resource_node)
|