Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions gitopsconfig.yaml

This file was deleted.

4 changes: 2 additions & 2 deletions src/clients/azdo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import logging
from configuration.gitops_config import GitOpsConfig


class AzdoClient:

def __init__(self, gitops_config: GitOpsConfig):
# https://dev.azure.com/csedevops/GitOps
self.org_url = gitops_config.azdo_org_url # utils.getenv("AZDO_ORG_URL")
self.org_url = gitops_config.azdo_org_url
# token is supposed to be stored in a secret without any transformations
token = base64.b64encode(f':{utils.getenv("PAT")}'.encode("ascii")).decode("ascii")

Expand Down
2 changes: 1 addition & 1 deletion src/clients/github_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class GitHubClient:

def __init__(self, gitops_config: GitOpsConfig):
self.org_url = gitops_config.github_org_url # utils.getenv("GITHUB_ORG_URL") # https://api.github.com/repos/kaizentm
self.org_url = gitops_config.github_org_url
# token is supposed to be stored in a secret without any transformations
self.token = utils.getenv("PAT")
self.headers = {'Authorization': f'token {self.token}'}
Expand Down
17 changes: 10 additions & 7 deletions src/configuration/gitops_config.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

class GitOpsConfig:
def __init__(self,
name,
git_repository_type,
cicd_orchestrator_type,
gitops_operator_type,
gitops_app_url,
azdo_gitops_repo_name=None,
azdo_pr_repo_name=None,
name,
git_repository_type,
cicd_orchestrator_type,
gitops_operator_type,
gitops_app_url,
azdo_gitops_repo_name=None,
azdo_pr_repo_name=None,
azdo_org_url=None,
github_gitops_repo_name=None,
github_gitops_manifests_repo_name=None,
Expand Down
12 changes: 5 additions & 7 deletions src/configuration/gitops_config_operator.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

import kopf
import logging
from kubernetes import config as k8s_config
from threading import Thread
from configuration.gitops_config import GitOpsConfig
from typing import Callable, Optional, List
from configuration.gitops_connector_manager import GitOpsConnectorManager


class GitOpsConfigOperator:
def __init__(self, connector_manager: GitOpsConnectorManager):
self.configurations = {} # Store configuration objects indexed by resource name
Expand Down Expand Up @@ -61,10 +63,6 @@ def parse_config(self, spec, name):
def get_configuration(self, name):
"""Get the configuration object by name."""
return self.configurations.get(name)

# def get_gitops_connector(self, name):
# """Get the gitops_connector object by name."""
# return self.connector_manager.connectors.get(name)

def stop_all(self):
self.connector_manager.stop_all()
self.connector_manager.stop_all()
6 changes: 3 additions & 3 deletions src/configuration/gitops_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
PR_CLEANUP_INTERVAL = 1 * 30
DISABLE_POLLING_PR_TASK = False


# Instance is shared across threads.
class GitopsConnector:

Expand All @@ -29,7 +30,7 @@ def __init__(self, gitops_config: GitOpsConfig):

self.status_thread = None
self.status_thread_running = False

self.cleanup_task = Timeloop()
self.cleanup_task_running = False

Expand All @@ -47,7 +48,7 @@ def pr_polling_thread_worker():

def is_supported_message(self, payload):
return self._gitops_operator.is_supported_message(payload)

def start_background_work(self):
self._start_status_thread()
self._start_cleanup_task()
Expand Down Expand Up @@ -142,4 +143,3 @@ def drain_commit_status_queue(self):

except Exception as e:
logging.error(f'Unexpected exception in the message queue draining thread: {e}')

4 changes: 4 additions & 0 deletions src/configuration/gitops_connector_manager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

import logging
from configuration.gitops_connector import GitopsConnector
from configuration.gitops_config import GitOpsConfig


class GitOpsConnectorManager:
"""Manages configurations and the lifecycle of GitOpsConnector instances."""
def __init__(self):
Expand Down
9 changes: 4 additions & 5 deletions src/gitops_event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import logging
import kopf
from kubernetes import client, config
from kubernetes.client.rest import ApiException
import atexit
import time
import utils
Expand Down Expand Up @@ -42,11 +41,11 @@
else:
logging.debug('Detected no ENV configuration data. Running in multiple instance configuration mode via gitopsconfig resources.')
try:
cluster_domain=utils.getenv('CLUSTER_DOMAIN')
cluster_domain = utils.getenv('CLUSTER_DOMAIN')
logging.debug(f"cluster domain: '{cluster_domain}'")
config.load_incluster_config() # In-cluster Kubernetes config
api_instance = client.CustomObjectsApi()
instances = api_instance.list_cluster_custom_object(cluster_domain, "v1", "gitopsconfigs")
instances = api_instance.list_cluster_custom_object(cluster_domain, "v1", "gitopsconfigs")
for instance in instances.get("items"):
config_name = instance.get("metadata").get("name")
config_namespace = instance.get("metadata").get("namespace")
Expand Down Expand Up @@ -77,7 +76,6 @@ def run_kopf_operator():
kopf_thread.start()



@application.route("/gitopsphase", methods=['POST'])
def gitopsphase():
# Use per process timer to stash the time we got the request
Expand All @@ -99,7 +97,7 @@ def gitopsphase():
logging.debug(f'GitOps phase: {payload}')

gitops_connector = connector_manager.get_supported_gitops_connector(payload)
if gitops_connector != None:
if gitops_connector is not None:
gitops_connector.process_gitops_phase(payload, req_time)

return f'GitOps phase: {payload}', 200
Expand All @@ -108,6 +106,7 @@ def gitopsphase():
def interrupt():
connector_manager.stop_all()


atexit.register(interrupt)


Expand Down
12 changes: 4 additions & 8 deletions src/operators/argo_gitops_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from operators.git_commit_status import GitCommitStatus
from configuration.gitops_config import GitOpsConfig


class ArgoGitopsOperator(GitopsOperatorInterface):

def __init__(self, gitops_config: GitOpsConfig):
Expand Down Expand Up @@ -48,7 +49,7 @@ def is_finished(self, phase_data):
logging.debug(f'is_finished called. phase_data: {json.dumps(phase_data, indent=2)}')
phase_status, _, health_status = self._get_statuses(phase_data)
logging.debug(f'is_finished: phase_status: {phase_status}, health_status: {health_status}')

is_finished = \
phase_status != 'Inconclusive' \
and phase_status != 'Running' \
Expand All @@ -75,15 +76,10 @@ def _new_git_commit_status(self, commit_id, status_name, state, message: str):
genre='ArgoCD')

def is_supported_operator(self, phase_data) -> bool:
return (self.gitops_config.name == 'singleInstance' or
self.gitops_config.name != 'singleInstance' and phase_data.get('gitops_connector_config_name') == self.gitops_config.name)
return self.gitops_config.name == 'singleInstance' or phase_data.get('gitops_connector_config_name') == self.gitops_config.name

def is_supported_message(self, phase_data) -> bool:
if ((not self.is_supported_operator(phase_data)) or
phase_data['commitid'] == "<no value>" or
phase_data['resources'] == None):
return False
return True
return self.is_supported_operator(phase_data) and phase_data.get('commitid') != "<no value>" and phase_data.get('resources') is not None

def _get_deployment_status_summary(self, resources):
total = len(resources)
Expand Down
6 changes: 3 additions & 3 deletions src/operators/flux_gitops_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,11 @@ def get_commit_id(self, phase_data) -> str:
revision = phase_data['metadata']['source.toolkit.fluxcd.io/revision']

return parse_commit_id(revision)

def is_supported_operator(self, phase_data) -> bool:
return (self.gitops_config.name == 'singleInstance' or
(self.gitops_config.name != 'singleInstance' and
'gitops_connector_config_name' in phase_data['metadata'] and
(self.gitops_config.name != 'singleInstance' and
'gitops_connector_config_name' in phase_data['metadata'] and
phase_data['metadata']['gitops_connector_config_name'] == self.gitops_config.name))

def is_supported_message(self, phase_data) -> bool:
Expand Down
4 changes: 2 additions & 2 deletions src/operators/gitops_operator.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

import utils
from abc import ABC, abstractmethod
from configuration.gitops_config import GitOpsConfig


class GitopsOperatorInterface(ABC):

def __init__(self, gitops_config: GitOpsConfig):
self.gitops_config = gitops_config
self.callback_url = gitops_config.gitops_app_url # utils.getenv("GITOPS_APP_URL")
self.callback_url = gitops_config.gitops_app_url

@abstractmethod
def extract_commit_statuses(self, phase_data):
Expand Down
3 changes: 1 addition & 2 deletions src/operators/gitops_operator_factory.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

import utils
from operators.argo_gitops_operator import ArgoGitopsOperator
from operators.flux_gitops_operator import FluxGitopsOperator
from operators.gitops_operator import GitopsOperatorInterface
Expand All @@ -16,7 +15,7 @@ class GitopsOperatorFactory:

@staticmethod
def new_gitops_operator(gitops_config: GitOpsConfig) -> GitopsOperatorInterface:
gitops_operator_type = gitops_config.gitops_operator_type # utils.getenv("GITOPS_OPERATOR_TYPE", FLUX_TYPE)
gitops_operator_type = gitops_config.gitops_operator_type

if gitops_operator_type == FLUX_TYPE:
return FluxGitopsOperator(gitops_config)
Expand Down
4 changes: 2 additions & 2 deletions src/orchestrators/azdo_cicd_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def _update_pr_task(self, is_successful, pr_num, is_alive=True):
def _get_pr_task_data(self, pr_num, is_alive=True):
logging.debug(f'_get_pr_task_data called. pr_num: {pr_num}, is_alive: {is_alive}')
return self.git_repository.get_pr_metadata(pr_num)

# Given a PR task, check if it's parent plan has already completed.
# Note: Completed does not necessarily mean it succeeded.
def _plan_already_completed(self, pr_task):
Expand Down Expand Up @@ -120,7 +120,7 @@ def _build_job_already_completed(self, pr_task, plan_info):
logging.debug(f'Check if job {job_id} already completed: state = {job_state}')
job_state_completed = job_state == 'completed'
return job_state_completed

def notify_abandoned_pr_tasks(self):
logging.debug('notify_abandoned_pr_tasks called')
update_count = 0
Expand Down
3 changes: 1 addition & 2 deletions src/orchestrators/cicd_orchestrator_factory.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

import utils
from orchestrators.cicd_orchestrator import CicdOrchestratorInterface
from repositories.git_repository import GitRepositoryInterface
from orchestrators.azdo_cicd_orchestrator import AzdoCicdOrchestrator
Expand All @@ -17,7 +16,7 @@ class CicdOrchestratorFactory:

@staticmethod
def new_cicd_orchestrator(git_repository: GitRepositoryInterface, gitops_config: GitOpsConfig) -> CicdOrchestratorInterface:
cicd_orchestrator_type = gitops_config.cicd_orchestrator_type # utils.getenv("CICD_ORCHESTRATOR_TYPE", AZDO_TYPE)
cicd_orchestrator_type = gitops_config.cicd_orchestrator_type

if cicd_orchestrator_type == AZDO_TYPE:
return AzdoCicdOrchestrator(git_repository, gitops_config)
Expand Down
3 changes: 1 addition & 2 deletions src/orchestrators/github_cicd_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# Licensed under the MIT License.

import logging
import utils
import requests
from orchestrators.cicd_orchestrator import CicdOrchestratorInterface
from repositories.git_repository import GitRepositoryInterface
Expand All @@ -14,7 +13,7 @@ class GitHubCicdOrchestrator(CicdOrchestratorInterface):

def __init__(self, git_repository: GitRepositoryInterface, gitops_config: GitOpsConfig):
super().__init__(git_repository)
self.gitops_repo_name = gitops_config.github_gitops_repo_name # utils.getenv("GITHUB_GITOPS_REPO_NAME") # cloud-native-ops
self.gitops_repo_name = gitops_config.github_gitops_repo_name
self.github_client = GitHubClient(gitops_config)
self.headers = self.github_client.get_rest_api_headers()
self.rest_api_url = self.github_client.get_rest_api_url()
Expand Down
6 changes: 2 additions & 4 deletions src/repositories/azdo_git_repository.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

import os
import logging
import json
import requests
import utils
from clients.azdo_client import AzdoClient
from repositories.git_repository import GitRepositoryInterface
from configuration.gitops_config import GitOpsConfig
Expand All @@ -16,8 +14,8 @@
class AzdoGitRepository(GitRepositoryInterface):

def __init__(self, gitops_config: GitOpsConfig):
self.gitops_repo_name = gitops_config.azdo_gitops_repo_name # utils.getenv("AZDO_GITOPS_REPO_NAME")
self.pr_repo_name = gitops_config.azdo_pr_repo_name # os.getenv("AZDO_PR_REPO_NAME", self.gitops_repo_name)
self.gitops_repo_name = gitops_config.azdo_gitops_repo_name
self.pr_repo_name = gitops_config.azdo_pr_repo_name
self.azdo_client = AzdoClient(gitops_config)
self.repository_api = f'{self.azdo_client.get_rest_api_url()}/_apis/git/repositories/{self.gitops_repo_name}'
self.pr_repository_api = f'{self.azdo_client.get_rest_api_url()}/_apis/git/repositories/{self.pr_repo_name}'
Expand Down
5 changes: 2 additions & 3 deletions src/repositories/git_repository_factory.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

import os
from repositories.git_repository import GitRepositoryInterface
from repositories.azdo_git_repository import AzdoGitRepository
from repositories.github_git_repository import GitHubGitRepository
Expand All @@ -15,8 +14,8 @@
class GitRepositoryFactory:

@staticmethod
def new_git_repository(gitops_config:GitOpsConfig) -> GitRepositoryInterface:
git_repository_type = gitops_config.git_repository_type # os.getenv("GIT_REPOSITORY_TYPE", AZDO_TYPE)
def new_git_repository(gitops_config: GitOpsConfig) -> GitRepositoryInterface:
git_repository_type = gitops_config.git_repository_type

if git_repository_type == AZDO_TYPE:
return AzdoGitRepository(gitops_config)
Expand Down
4 changes: 2 additions & 2 deletions src/repositories/github_git_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
# Licensed under the MIT License.

import requests
import utils
import logging
from clients.github_client import GitHubClient
from repositories.git_repository import GitRepositoryInterface
from configuration.gitops_config import GitOpsConfig


class GitHubGitRepository(GitRepositoryInterface):

MAX_DESCR_LENGTH = 140

def __init__(self, gitops_config: GitOpsConfig):
self.gitops_repo_name = gitops_config.github_gitops_manifests_repo_name # utils.getenv("GITHUB_GITOPS_MANIFEST_REPO_NAME") # gitops-manifests
self.gitops_repo_name = gitops_config.github_gitops_manifests_repo_name
self.github_client = GitHubClient(gitops_config)
self.headers = self.github_client.get_rest_api_headers()
self.rest_api_url = self.github_client.get_rest_api_url()
Expand Down