Skip to content

Commit

Permalink
fix: state of same object name/kind pais not tracked correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
davidgiga1993 committed Jun 13, 2023
1 parent 6fb31ae commit c71b816
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 115 deletions.
2 changes: 1 addition & 1 deletion octoploy/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.2.1'
__version__ = '1.2.2'
5 changes: 2 additions & 3 deletions octoploy/api/Model.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ class ItemDescription:
def __init__(self, data):
self.data = data

def get_annotation(self, key: str) -> str:
item = self.data.get('metadata', {}).get('annotations', {}).get(key)
return item
def get_annotation(self, key: str) -> Optional[str]:
return self.data.get('metadata', {}).get('annotations', {}).get(key)


class PodData:
Expand Down
11 changes: 8 additions & 3 deletions octoploy/api/Oc.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ def exec(self, pod_name: str, cmd: str, args: List[str]):
def switch_context(self, context: str):
"""
Changes the configuration context
:param context: Context
:param context: Context which should be used
"""
raise NotImplemented

@abstractmethod
def annotate(self, name: str, key: str, value: str, namespace: Optional[str] = None):
def annotate(self, name: str, key: str, value: Optional[str], namespace: Optional[str] = None):
"""
Add / updates the annotation at the given item
:param name: Name
Expand Down Expand Up @@ -193,7 +193,12 @@ def exec(self, pod_name: str, cmd: str, args: List[str], namespace: Optional[str
def switch_context(self, context: str):
raise NotImplemented('Not available for openshift')

def annotate(self, name: str, key: str, value: str, namespace: Optional[str] = None):
def annotate(self, name: str, key: str, value: Optional[str], namespace: Optional[str] = None):
if value is None:
# Remove the annotation
self._exec(['annotate', name, key + '-'], namespace=namespace)
return

self._exec(['annotate', '--overwrite=true', name, key + '=' + value], namespace=namespace)

def delete(self, name: str, namespace: str):
Expand Down
23 changes: 13 additions & 10 deletions octoploy/deploy/K8sObjectDeployer.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,23 @@ def deploy_object(self, k8s_object: BaseObj):
if current_object is None:
self._log_create(item_path)

current_hash = None
state_hash = None
old_state_hash = None
if current_object is not None:
old_state_hash = current_object.get_annotation(self.HASH_ANNOTATION)
obj_state = self._state.get_state(self._app_config.get_name(), k8s_object)
if obj_state is not None and obj_state.hash != '':
current_hash = obj_state.hash
state_hash = obj_state.hash
else: # Fallback to old hash location
current_hash = current_object.get_annotation(self.HASH_ANNOTATION)
state_hash = old_state_hash

if current_object is not None and current_hash is None:
if current_object is not None and state_hash is None:
# Item has not been deployed with octoploy, but it does already exist
self.log.warning(f'{item_path} has no state annotation, assuming no change required')
self._state.visit(self._app_config.get_name(), k8s_object, hash_val)
return

if current_hash == hash_val:
if state_hash == hash_val:
self._state.visit(self._app_config.get_name(), k8s_object, hash_val)
self.log.debug(f"{item_path} hasn't changed")
return
Expand All @@ -74,6 +76,9 @@ def deploy_object(self, k8s_object: BaseObj):
self._state.visit(self._app_config.get_name(), k8s_object, hash_val)
return

if old_state_hash is not None:
# Migrate to new state format by removing the old one
self._api.annotate(k8s_object.get_fqn(), self.HASH_ANNOTATION, None, namespace=k8s_object.namespace)
self._api.apply(k8s_object.as_string(), namespace=namespace)
self._state.visit(self._app_config.get_name(), k8s_object, hash_val)

Expand All @@ -88,14 +93,12 @@ def delete_abandoned_objects(self):
abandoned = self._state.get_not_visited(self._app_config.get_name())
for item in abandoned:
namespace = item.namespace
if namespace is None:
namespace = self._root_config.get_namespace_name()
item_path = item.kind + '/' + item.name
self._log_delete(item_path)

self._log_delete(item.fqn)
if self._mode.plan:
continue

self._api.delete(item_path, namespace=namespace)
self._api.delete(item.fqn, namespace=namespace)
self._state.remove(item)

def _reload_config(self):
Expand Down
10 changes: 10 additions & 0 deletions octoploy/processing/DataDiff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import Dict


class DataDiff:
"""
Creates a diff between two objects
"""

def diff(self, a: Dict[str, any], b: Dict[str, any]):
pass
68 changes: 35 additions & 33 deletions octoploy/state/StateTracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,55 @@


class ObjectState:
fqn: str
"""
Fully qualified name of the object in the format
kind.group/name
"""

visited: bool
"""
Transient flag to indicate if the object has been visited by octoploy
"""

def __init__(self):
self.context = ''
self.name = ''
self.kind = ''
self.context: str = ''
self.namespace: str = ''

self.fqn = ''

self.hash = ''
self.namespace: Optional[str] = None
self.visited = False
"""
Transient flag to indicate if the object has been visited by octoploy
"""

def update_from_key(self, key: str):
segments = key.split('/')
count = len(segments)
if count > 0:
self.context = segments[0]
if count > 1:
self.namespace = segments[1]
if count > 2:
self.kind = segments[2]
if count > 3:
self.name = segments[3]
segments = key.split('/', 2)
if len(segments) < 3:
raise ValueError(f'Invalid key, must be 3 segments long {key}')

self.context = segments[0]
self.namespace = segments[1]
self.fqn = segments[2]

def parse(self, data: Dict[str, str]) -> ObjectState:
self.context = data['context']
self.namespace = data['namespace']
self.kind = data['kind']
self.name = data['name']
self.context = data.get('context')
self.namespace = data.get('namespace')
self.fqn = data.get('fqn')
if self.context is None or self.namespace is None or self.fqn is None:
raise ValueError(f'Corrupt octoploy state, could not parse {data}')

self.hash = data.get('hash', '')
return self

def to_dict(self) -> Dict[str, str]:
return {
'name': self.name,
'hash': self.hash,
'kind': self.kind,
'context': self.context,
'namespace': self.namespace,
'fqn': self.fqn,
'hash': self.hash,
}

def get_key(self) -> str:
return f'{self.context}/{self.namespace}/{self.kind}/{self.name}'
return f'{self.context}/{self.namespace}/{self.fqn}'


class StateTracking(Log):
Expand Down Expand Up @@ -158,15 +165,10 @@ def remove(self, object_state: ObjectState):

@staticmethod
def _k8s_to_state(context_name: str, k8s_object: BaseObj) -> ObjectState:
api_version = k8s_object.api_version
kind = k8s_object.kind
name = k8s_object.name

# At this point we always have a namespace set for the object
state = ObjectState()
state.namespace = k8s_object.namespace
state.context = context_name
state.api_version = api_version
state.kind = kind
state.name = name
state.namespace = k8s_object.namespace
state.fqn = k8s_object.get_fqn()
state.visited = True
return state
Loading

0 comments on commit c71b816

Please sign in to comment.