shipyard/src/bin/shipyard_airflow/shipyard_airflow/common/notes/notes_helper.py

367 lines
13 KiB
Python

# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
#
# 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 enum import Enum
import logging
from .notes import MAX_VERBOSITY
from .notes import MIN_VERBOSITY
from .notes import Query
LOG = logging.getLogger(__name__)
class _NoteType:
"""Define the patterns and pertinent positions of information for a note
:param root: the string used as the initial identifier for the type
:param key_pattern_count: the number of variable positions in the key.
E.g.: a value of 3, and a root of "tacos" would generate a key_pattern
of "tacos/{}/{}/{}", while a value of 0 would generate "tacos".
The lookup_pattern for a type is also drived from the key_pattern_count
by subtracting 1 (minimum 0), such that a value of 3 and a root of
"tacos" would generate a lookup_pattern of "tacos/{}/{}/
:param id_start: the start location in the assoc_id of a note of this type
where the id of the item it is associated with appears.
:param id_end: the optional end location in the assoc_id for locating the
id from the assoc_id of a note of this type. This is only valid for
items that have a fixed length key.
:param default_subtype: the default sub_type specified upon creation of a
note of this type.
"""
def __init__(self, root, key_pattern_count, id_start,
id_end=None, default_subtype="metadata"):
self.root = root
self.base_pattern = "{}/".format(root)
self.kp_count = key_pattern_count
self.key_pattern = "{}{}".format(root, "/{}" * self.kp_count)
self.lp_count = self.kp_count - 1 if self.kp_count > 0 else 0
self.lookup_pattern = "{}/{}".format(root, "{}/" * self.lp_count)
self.id_start = id_start
self.id_end = id_end
self.default_subtype = default_subtype
class NoteType(Enum):
# Action
#
# Text: action/12345678901234567890123456
# | |
# Position: 0....5.|..1....1....2....2....3..|.3
# | 0 5 0 5 0 | 5
# | |
# (7) ACTION_ID_START |
# (33) ACTION_ID_END
ACTION = _NoteType(
root="action",
key_pattern_count=1,
id_start=7,
id_end=33,
default_subtype="action metadata")
# Step
#
# Text: step/12345678901234567890123456/my_step
# | ||
# Position: 0....5....1....1....2....2....3||..3....4
# | 0 5 0 5 0|| 5 0
# | |\
# (5) STEP_ACTION_ID_START | \
# | (32) STEP_ID_START
# (31) STEP_ACTION_ID_END
STEP = _NoteType(
root="step",
key_pattern_count=2,
id_start=32,
default_subtype="step metadata")
OTHER = _NoteType(root="", key_pattern_count=0, id_start=0)
@property
def base_pattern(self):
return self.value.base_pattern
@property
def key_pattern(self):
return self.value.key_pattern
@property
def lookup_pattern(self):
return self.value.lookup_pattern
@property
def id_start(self):
return self.value.id_start
@property
def id_end(self):
return self.value.id_end
@property
def default_subtype(self):
return self.value.default_subtype
@classmethod
def get_type(cls, note):
for tp in cls:
if note.assoc_id.startswith(tp.value.base_pattern):
return tp
return cls.OTHER
class NotesHelper:
"""Notes Helper
Provides helper methods for the common use cases for notes
:param notes_manager: the NotesManager object to use
"""
def __init__(self, notes_manager):
self.nm = notes_manager
def _failsafe_make_note(self, assoc_id, subject, sub_type, note_val,
verbosity=MIN_VERBOSITY, link_url=None,
is_auth_link=None, note_timestamp=None):
"""LOG and continue on any note creation failure"""
try:
self.nm.create(
assoc_id=assoc_id,
subject=subject,
sub_type=sub_type,
note_val=note_val,
verbosity=verbosity,
link_url=link_url,
is_auth_link=is_auth_link,
note_timestamp=note_timestamp
)
except Exception as ex:
LOG.warn(
"Creating note for {} encountered a problem, exception info "
"follows, but processing is not halted for notes.",
assoc_id
)
LOG.exception(ex)
def _failsafe_get_notes(self, assoc_id_pattern, verbosity,
exact_match):
"""LOG and continue on any note retrieval failure"""
try:
if verbosity < MIN_VERBOSITY:
return []
q = Query(assoc_id_pattern, verbosity, exact_match)
return self.nm.retrieve(q)
except Exception as ex:
LOG.warn(
"Note retrieval for {} encountered a problem, exception "
"info follows, but processing is not halted for notes.",
assoc_id_pattern
)
LOG.exception(ex)
return []
#
# Retrieve notes by note ID
#
def get_note(self, note_id):
"""Return a single note looked up by the specified note_id
:param note_id: the ULID of the note to retrieve.
:raises NoteNotFoundError: if there is no note matching the requested
note_id
"""
return self.nm.retrieve_by_id(note_id)
def get_note_details(self, note):
"""Return the note details for the specified note
:param note: the Note object with additional details to retrieve.
"""
return self.nm.get_note_url_info(note)
def get_note_assoc_id_type(self, note):
"""Return the type of note based on the assoc_id
:param note: The note to examine
The purpose of this method is to use the standard formats (e.g.:
action and step) supported by this helper to get the type of note.
This value can be used by a client to enforce access rules to the note
based on the item it is related to.
"""
return NoteType.get_type(note)
#
# Action notes helper methods
#
def make_action_note(self, action_id, note_val, subject=None,
sub_type=None, verbosity=MIN_VERBOSITY, link_url=None,
is_auth_link=None, note_timestamp=None):
"""Creates an action note using a convention for the note's assoc_id
:param action_id: the ULID id of an action
:param note_val: the text for the note
:param subject: optional subject for the note. Defaults to the
action_id
:param sub_type: optional subject type for the note, defaults to
"action metadata"
:param verbosity: optional verbosity for the note, defaults to 1,
i.e.: summary level
:param link_url: optional link URL if there's additional information
to retreive from another source.
:param is_auth_link: optional, defaults to False, indicating if there
is a need to send a Shipyard service account token with the
request to the optional URL
:param note_timestamp: the parseable string timestamp to associate with
this note. Optional, defaults to the creation time of the note.
"""
assoc_id = NoteType.ACTION.key_pattern.format(action_id)
if subject is None:
subject = action_id
if sub_type is None:
sub_type = NoteType.ACTION.default_subtype
self._failsafe_make_note(
assoc_id=assoc_id,
subject=subject,
sub_type=sub_type,
note_val=note_val,
verbosity=verbosity,
link_url=link_url,
is_auth_link=is_auth_link,
note_timestamp=note_timestamp
)
def get_all_action_notes(self, verbosity=MIN_VERBOSITY):
"""Retrieve notes for all actions, in a dictionary keyed by action id.
:param verbosity: optional integer, 0-5, the maximum verbosity level
to retrieve, defaults to 1 (most summary level)
if set to less than 1, returns {}, skipping any retrieval
"""
notes = self._failsafe_get_notes(
assoc_id_pattern=NoteType.ACTION.lookup_pattern,
verbosity=verbosity,
exact_match=False
)
note_dict = {}
id_s = NoteType.ACTION.id_start
id_e = NoteType.ACTION.id_end
for n in notes:
action_id = n.assoc_id[id_s:id_e]
if action_id not in note_dict:
note_dict[action_id] = []
note_dict[action_id].append(n)
return note_dict
def get_action_notes(self, action_id, verbosity=MAX_VERBOSITY):
"""Retrive notes related to a particular action
:param action_id: the action for which to retrieve notes.
:param verbosity: optional integer, 0-5, the maximum verbosity level
to retrieve, defaults to 5 (most detailed level)
if set to less than 1, returns [], skipping any retrieval
"""
return self._failsafe_get_notes(
assoc_id_pattern=NoteType.ACTION.key_pattern.format(action_id),
verbosity=verbosity,
exact_match=True
)
#
# Step notes helper methods
#
def make_step_note(self, action_id, step_id, note_val, subject=None,
sub_type=None, verbosity=MIN_VERBOSITY, link_url=None,
is_auth_link=None, note_timestamp=None):
"""Creates an action note using a convention for the note's assoc_id
:param action_id: the ULID id of the action containing the note
:param step_id: the step for this note
:param note_val: the text for the note
:param subject: optional subject for the note. Defaults to the
step_id
:param sub_type: optional subject type for the note, defaults to
"step metadata"
:param verbosity: optional verbosity for the note, defaults to 1,
i.e.: summary level
:param link_url: optional link URL if there's additional information
to retreive from another source.
:param is_auth_link: optional, defaults to False, indicating if there
is a need to send a Shipyard service account token with the
request to the optional URL
:param note_timestamp: the parseable string timestamp to associate with
this note. Optional, defaults to the creation time of the note.
"""
assoc_id = NoteType.STEP.key_pattern.format(action_id, step_id)
if subject is None:
subject = step_id
if sub_type is None:
sub_type = NoteType.STEP.default_subtype
self._failsafe_make_note(
assoc_id=assoc_id,
subject=subject,
sub_type=sub_type,
note_val=note_val,
verbosity=verbosity,
link_url=link_url,
is_auth_link=is_auth_link,
note_timestamp=note_timestamp
)
def get_all_step_notes_for_action(self, action_id,
verbosity=MIN_VERBOSITY):
"""Retrieve a dict keyed by step names for the action_id
:param action_id: the action that contains the target steps
:param verbosity: optional integer, 0-5, the maximum verbosity level
to retrieve, defaults to 1 (most summary level)
if set to less than 1, returns {}, skipping any retrieval
"""
notes = self._failsafe_get_notes(
assoc_id_pattern=NoteType.STEP.lookup_pattern.format(action_id),
verbosity=verbosity,
exact_match=False
)
note_dict = {}
id_s = NoteType.STEP.id_start
for n in notes:
step_id = n.assoc_id[id_s:]
if step_id not in note_dict:
note_dict[step_id] = []
note_dict[step_id].append(n)
return note_dict
def get_step_notes(self, action_id, step_id, verbosity=MAX_VERBOSITY):
"""Retrive notes related to a particular step
:param action_id: the action containing the step
:param step_id: the id of the step
:param verbosity: optional integer, 0-5, the maximum verbosity level
to retrieve, defaults to 5 (most detailed level)
if set to less than 1, returns [], skipping any retrieval
"""
return self._failsafe_get_notes(
assoc_id_pattern=NoteType.STEP.key_pattern.format(
action_id, step_id),
verbosity=verbosity,
exact_match=True
)