Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 5 additions & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,23 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate Image Tag
run: |
# IMAGE_TAG=${{ secrets.MAJOR_VERSION }}.${{ secrets.MINOR_VERSION }}.${{ secrets.HF_VERSION }}-${{ github.run_number }}
IMAGE_TAG=${{ secrets.MAJOR_VERSION }}.${{ secrets.MINOR_VERSION }}.${{ secrets.HF_VERSION }}
# IMAGE_TAG=${{ secrets.MAJOR_VERSION }}.${{ secrets.MINOR_VERSION }}.${{ secrets.HF_VERSION }}
IMAGE_TAG=${{ secrets.MAJOR_VERSION }}.${{ secrets.MINOR_VERSION }}.${{ secrets.HF_VERSION }}-${{ github.run_number }}
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
echo $IMAGE_TAG > $GITHUB_WORKSPACE/IMAGE_TAG
- name: Build and Push to Docker Hub
uses: docker/build-push-action@v2
with:
push: true
context: ./src
tags: ghcr.io/azure/gitops-connector:${{ env.IMAGE_TAG }}, ghcr.io/azure/gitops-connector:latest
tags: ghcr.io/${{ github.repository_owner }}/gitops-connector:${{ env.IMAGE_TAG }}, ghcr.io/${{ github.repository_owner }}/gitops-connector:latest
- name: Upload Image Tags
uses: actions/upload-artifact@v2.2.2
uses: actions/upload-artifact@v4
with:
name: image_tags
path: ${{ github.workspace }}/IMAGE_TAG
- name: Upload Manifests Templates
uses: actions/upload-artifact@v2.2.2
uses: actions/upload-artifact@v4
with:
name: manifests
path: ${{ github.workspace }}/manifests
12 changes: 0 additions & 12 deletions gitopsconfig.yaml

This file was deleted.

4 changes: 2 additions & 2 deletions manifests/helm/templates/crds/gitops-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: gitopsconfigs.example.com # Replace 'example.com' with your domain
name: gitopsconfigs.{{ .Values.multipleInstances.clusterDomain }}
spec:
group: example.com # Replace 'example.com' with your domain
group: {{ .Values.multipleInstances.clusterDomain }}
names:
kind: GitOpsConfig
plural: gitopsconfigs
Expand Down
6 changes: 5 additions & 1 deletion manifests/helm/templates/service-account.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ metadata:
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}
---
{{- if not .Values.singleInstance }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitops-connector-config-reader
rules:
- apiGroups: ["example.com"] # "" indicates the core API group
- apiGroups: [{{ .Values.multipleInstances.clusterDomain | quote }}] # "" indicates the core API group
resources: ["gitopsconfigs"]
verbs: ["get", "watch", "list", "patch"]
- apiGroups: [""] # "" indicates the core API group
Expand All @@ -28,7 +29,9 @@ rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["watch", "list"]
{{- end }}
---
{{- if not .Values.singleInstance }}
apiVersion: rbac.authorization.k8s.io/v1
# This role binding allows "jane" to read pods in the "default" namespace.
# You need to already have a Role named "pod-reader" in that namespace.
Expand All @@ -45,3 +48,4 @@ roleRef:
kind: ClusterRole #this must be Role or ClusterRole
name: gitops-connector-config-reader # this must match the name of the Role or ClusterRole you wish to bind to
apiGroup: rbac.authorization.k8s.io
{{- end }}
2 changes: 1 addition & 1 deletion manifests/helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ singleInstance:

# specify values for a multiple instances configuration
multipleInstances:
clusterDomain: apps-crc.testing
clusterDomain: example.com

# -- Partially override resource names (adds suffix)
# @section -- Common
Expand Down
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
8 changes: 4 additions & 4 deletions src/orchestrators/azdo_cicd_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def _update_pr_task(self, is_successful, pr_num, is_alive=True):
planurl = pr_task['planurl']
projectid = pr_task['projectid']
planid = pr_task['planid']
url = f'{planurl}{projectid}/_apis/distributedtask/hubs/build/plans/{planid}/events?api-version=2.0-preview.1'
url = f'{planurl}{projectid}/_apis/distributedtask/hubs/build/plans/{planid}/events?api-version=7.1'
data = {
'name': "TaskCompleted",
'taskId': pr_task['taskid'],
Expand All @@ -77,14 +77,14 @@ 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):
planurl = pr_task['planurl']
projectid = pr_task['projectid']
planid = pr_task['planid']
url = f'{planurl}{projectid}/_apis/distributedtask/hubs/build/plans/{planid}'
url = f'{planurl}{projectid}/_apis/distributedtask/hubs/build/plans/{planid}?api-version=7.1'

response = requests.get(url=url, headers=self.headers)
# Throw appropriate exception if request failed
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
Loading