Smarter upgrades of node type definitions
- No changes are applied unless the new --go CLI arg is used. - Differences to node types are actually shown. - Dynamic form definitions are kept.
This commit is contained in:
@@ -158,7 +158,12 @@ class MetaFalsey(type):
|
||||
return False
|
||||
|
||||
|
||||
class DoesNotExist(object, metaclass=MetaFalsey):
|
||||
class DoesNotExistMeta(MetaFalsey):
|
||||
def __repr__(cls) -> str:
|
||||
return 'DoesNotExist'
|
||||
|
||||
|
||||
class DoesNotExist(object, metaclass=DoesNotExistMeta):
|
||||
"""Returned as value by doc_diff if a value does not exist."""
|
||||
|
||||
|
||||
|
@@ -412,13 +412,17 @@ def expire_all_project_links(project_uuid):
|
||||
@manager_maintenance.option('-m', '--missing', dest='missing',
|
||||
action='store_true', default=False,
|
||||
help='Add missing node types. Note that this may add unwanted ones.')
|
||||
def replace_pillar_node_type_schemas(proj_url=None, all_projects=False, missing=False):
|
||||
@manager_maintenance.option('-g', '--go', dest='gogogo',
|
||||
action='store_true', default=False,
|
||||
help='Actually go and perform the changes, without this just '
|
||||
'shows differences.')
|
||||
def replace_pillar_node_type_schemas(project_url=None, all_projects=False, missing=False, go=False):
|
||||
"""Replaces the project's node type schemas with the standard Pillar ones.
|
||||
|
||||
Non-standard node types are left alone.
|
||||
"""
|
||||
|
||||
if bool(proj_url) == all_projects:
|
||||
if bool(project_url) == all_projects:
|
||||
log.error('Use either --project or --all.')
|
||||
return 1
|
||||
|
||||
@@ -426,57 +430,73 @@ def replace_pillar_node_type_schemas(proj_url=None, all_projects=False, missing=
|
||||
force_cli_user()
|
||||
|
||||
from pillar.api.node_types import PILLAR_NAMED_NODE_TYPES
|
||||
from pillar.api.utils import remove_private_keys
|
||||
from pillar.api.utils import remove_private_keys, doc_diff
|
||||
|
||||
projects_collection = current_app.db()['projects']
|
||||
will_would = 'Will' if go else 'Would'
|
||||
|
||||
def handle_project(project):
|
||||
log.info('Handling project %s', project['url'])
|
||||
is_public_proj = not project.get('is_private', True)
|
||||
def handle_project(proj):
|
||||
orig_proj = copy.deepcopy(proj)
|
||||
proj_id = proj['_id']
|
||||
if 'url' not in proj:
|
||||
log.warning('Project %s has no URL!', proj_id)
|
||||
proj_url = proj.get('url', f'-no URL id {proj_id}')
|
||||
log.debug('Handling project %s', proj_url)
|
||||
|
||||
for proj_nt in project['node_types']:
|
||||
for proj_nt in proj['node_types']:
|
||||
nt_name = proj_nt['name']
|
||||
try:
|
||||
pillar_nt = PILLAR_NAMED_NODE_TYPES[nt_name]
|
||||
except KeyError:
|
||||
log.info(' - skipping non-standard node type "%s"', nt_name)
|
||||
log.debug(' - skipping non-standard node type "%s"', nt_name)
|
||||
continue
|
||||
|
||||
log.info(' - replacing schema on node type "%s"', nt_name)
|
||||
log.debug(' - replacing schema on node type "%s"', nt_name)
|
||||
|
||||
# This leaves node type keys intact that aren't in Pillar's node_type_xxx definitions,
|
||||
# such as permissions.
|
||||
# such as permissions. It also keeps form schemas as-is.
|
||||
pillar_nt.pop('form_schema', None)
|
||||
proj_nt.update(copy.deepcopy(pillar_nt))
|
||||
|
||||
# On our own public projects we want to be able to set license stuff.
|
||||
if is_public_proj:
|
||||
proj_nt['form_schema'].pop('license_type', None)
|
||||
proj_nt['form_schema'].pop('license_notes', None)
|
||||
|
||||
# Find new node types that aren't in the project yet.
|
||||
if missing:
|
||||
project_ntnames = set(nt['name'] for nt in project['node_types'])
|
||||
project_ntnames = set(nt['name'] for nt in proj['node_types'])
|
||||
for nt_name in set(PILLAR_NAMED_NODE_TYPES.keys()) - project_ntnames:
|
||||
log.info(' - Adding node type "%s"', nt_name)
|
||||
pillar_nt = PILLAR_NAMED_NODE_TYPES[nt_name]
|
||||
project['node_types'].append(copy.deepcopy(pillar_nt))
|
||||
proj['node_types'].append(copy.deepcopy(pillar_nt))
|
||||
|
||||
# Use Eve to PUT, so we have schema checking.
|
||||
db_proj = remove_private_keys(project)
|
||||
r, _, _, status = current_app.put_internal('projects', db_proj, _id=project['_id'])
|
||||
if status != 200:
|
||||
log.error('Error %i storing altered project %s %s', status, project['_id'], r)
|
||||
raise SystemExit('Error storing project, see log.')
|
||||
log.info('Project saved succesfully.')
|
||||
proj_header_shown = False
|
||||
for key, val1, val2 in doc_diff(orig_proj, proj, falsey_is_equal=False):
|
||||
if not proj_header_shown:
|
||||
if proj.get('_deleted', False):
|
||||
deleted = ' (deleted)'
|
||||
else:
|
||||
deleted = ''
|
||||
log.info('%s change project %s%s', will_would, proj_url, deleted)
|
||||
proj_header_shown = True
|
||||
log.info(' %30r: %r → %r', key, val1, val2)
|
||||
|
||||
if go:
|
||||
# Use Eve to PUT, so we have schema checking.
|
||||
db_proj = remove_private_keys(proj)
|
||||
r, _, _, status = current_app.put_internal('projects', db_proj, _id=proj['_id'])
|
||||
if status != 200:
|
||||
log.error('Error %i storing altered project %s %s', status, proj['_id'], r)
|
||||
raise SystemExit('Error storing project, see log.')
|
||||
log.info('Project saved succesfully.')
|
||||
|
||||
if not go:
|
||||
log.info('Not changing anything, use --go to actually go and change things.')
|
||||
|
||||
if all_projects:
|
||||
for project in projects_collection.find():
|
||||
handle_project(project)
|
||||
return
|
||||
|
||||
project = projects_collection.find_one({'url': proj_url})
|
||||
project = projects_collection.find_one({'url': project_url})
|
||||
if not project:
|
||||
log.error('Project url=%s not found', proj_url)
|
||||
log.error('Project url=%s not found', project_url)
|
||||
return 3
|
||||
|
||||
handle_project(project)
|
||||
|
@@ -293,7 +293,7 @@ class ReplaceNodeTypesTest(AbstractNodeReplacementTest):
|
||||
|
||||
# Run the CLI command
|
||||
with self.app.test_request_context():
|
||||
replace_pillar_node_type_schemas(proj_url=self.proj['url'])
|
||||
replace_pillar_node_type_schemas(project_url=self.proj['url'])
|
||||
|
||||
# Fetch the project again from MongoDB
|
||||
dbproj = self.fetch_project_from_db()
|
||||
|
Reference in New Issue
Block a user