From 8cc6e06072b9830421b80f1679285534fe2863f6 Mon Sep 17 00:00:00 2001 From: Xiaofeng Wang Date: Sat, 14 May 2022 21:17:25 +0800 Subject: [PATCH] Move to kite-action v2 --- .gitignore | 3 +- Makefile | 33 --- README.md | 3 +- controller/Dockerfile | 53 ----- controller/controller.py | 213 ------------------ controller/entrypoint.sh | 14 -- controller/openshift/controller-template.yaml | 180 --------------- deploy/aws_deploy.yaml | 62 +++++ dummy/Dockerfile | 54 ----- dummy/dummy.py | 126 ----------- dummy/entrypoint.sh | 14 -- dummy/openshift/dummy-buildconfig.yaml | 56 ----- dummy/openshift/dummy-cronjob.yaml | 32 --- dummy/openshift/dummy-pod.yaml | 24 -- kite-action.png | Bin 81759 -> 0 bytes proxy/Dockerfile | 4 +- proxy/ansible.cfg | 8 + proxy/group_vars/all | 49 ++++ proxy/install_runner.yaml | 161 +++++++++++++ proxy/inventory | 21 ++ proxy/key/ostree_key | 38 ++++ proxy/key/ostree_key.pub | 1 + proxy/proxy.py | 46 ++-- sweeper/Dockerfile | 51 ----- sweeper/entrypoint.sh | 14 -- sweeper/openshift/sweeper-buildconfig.yaml | 56 ----- sweeper/openshift/sweeper-cronjob.yaml | 32 --- sweeper/openshift/sweeper-pod.yaml | 24 -- sweeper/sweeper.py | 89 -------- upload/upload_runner_binary.yaml | 74 ++++++ webhook/webhook.py | 129 ++++------- 31 files changed, 484 insertions(+), 1180 deletions(-) delete mode 100644 Makefile delete mode 100644 controller/Dockerfile delete mode 100755 controller/controller.py delete mode 100644 controller/entrypoint.sh delete mode 100644 controller/openshift/controller-template.yaml create mode 100644 deploy/aws_deploy.yaml delete mode 100644 dummy/Dockerfile delete mode 100755 dummy/dummy.py delete mode 100644 dummy/entrypoint.sh delete mode 100644 dummy/openshift/dummy-buildconfig.yaml delete mode 100644 dummy/openshift/dummy-cronjob.yaml delete mode 100644 dummy/openshift/dummy-pod.yaml delete mode 100644 kite-action.png create mode 100644 proxy/ansible.cfg create mode 100644 proxy/group_vars/all create mode 100644 proxy/install_runner.yaml create mode 100644 proxy/inventory create mode 100644 proxy/key/ostree_key create mode 100644 proxy/key/ostree_key.pub delete mode 100644 sweeper/Dockerfile delete mode 100644 sweeper/entrypoint.sh delete mode 100644 sweeper/openshift/sweeper-buildconfig.yaml delete mode 100644 sweeper/openshift/sweeper-cronjob.yaml delete mode 100644 sweeper/openshift/sweeper-pod.yaml delete mode 100755 sweeper/sweeper.py create mode 100644 upload/upload_runner_binary.yaml diff --git a/.gitignore b/.gitignore index 264f5d7..8df3377 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -kite-secret.yaml kite-secret-template.yaml .pyc webhook.zip +aws_policy/ +venv diff --git a/Makefile b/Makefile deleted file mode 100644 index cfbe4d6..0000000 --- a/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -PROJECT = webhook -FUNCTION_NAME = kite-webhook -FUNCTION_HANDLER = kite_webhook_handler -FUNCTION_DESCRIPTION = "Dealing with github app webhook check_run POST event" - -# run python linting test -flake8: - flake8 **/*.py - -# zip function file -zip: - cd webhook && zip ../$(PROJECT).zip webhook.py - -lambda_delete: - aws lambda delete-function \ - --function-name $(FUNCTION_NAME) - -lambda_create: - aws lambda create-function \ - --region $(AWS_REGION) \ - --function-name $(FUNCTION_NAME) \ - --description $(FUNCTION_DESCRIPTION) \ - --zip-file fileb://./$(PROJECT).zip \ - --role $(SERVICE_ROLE) \ - --handler $(PROJECT).$(FUNCTION_HANDLER) \ - --runtime python3.8 \ - --timeout 15 \ - --memory-size 128 \ - --environment "Variables={GITHUB_ACCESS_TOKEN=AQICAHj7RSzG4JqNNnM3dbZox06sEvand3JdQiiNJGYlB80iiwGeA0+E/4Y5gvbpSek4WSw2AAAAhzCBhAYJKoZIhvcNAQcGoHcwdQIBADBwBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDDm4YNDaQCBgNMTpmQIBEIBDUYkHB86UaXLD+G/C2kfPlThw6m5vXwVLdKPGw7Ubgxz0PiIvWiq6RlmxPcVmVmaO0q6+D9MuRjWSqsAuRYdbRAewHg==, GITHUB_APP_WEBHOOK_SECRET=AQICAHj7RSzG4JqNNnM3dbZox06sEvand3JdQiiNJGYlB80iiwEbTQ5FKyvyVk/CdfIi5V44AAAAhzCBhAYJKoZIhvcNAQcGoHcwdQIBADBwBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHCU546tiN7NWoCVoAIBEIBDlblVRV1Xq4hhmk0s3nMmmMBkmjMo7gJIXMOrPR3mHUlDo3+lYNiorphnBfiBQZkjSTVU7N2Fh7hRsh1BbkZlOD1Y0Q==, SQS_REGION=$(AWS_REGION), SQS_QUEUE=$(SQS_QUEUE)}" \ - --kms-key-arn $(KMS_KEY_ARN) \ - --tags name=kite-webhook - -.PHONY: flake8 lambda_delete lambda_delete diff --git a/README.md b/README.md index b1c322e..3146856 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ -# kite-action +# kite-action v2 **kite project CI and self-hosted runner auto-scaling** ## Github self-hosted action runner auto-scaling diagram -![kite project CI diagram](./kite-action.png) diff --git a/controller/Dockerfile b/controller/Dockerfile deleted file mode 100644 index e024f4c..0000000 --- a/controller/Dockerfile +++ /dev/null @@ -1,53 +0,0 @@ -### Github action controller on OpenShift -FROM fedora:32 - -LABEL name="kite-controller" \ - maintainer="xiaofwan@redhat.com" \ - vendor="Red Hat QE Section 1" \ - version="1.0" \ - release="1" \ - summary="Github self-host action controller" \ - description="Github action controller for kite project on OpenShift" \ - io.k8s.description="Github action controller for kite project on OpenShift" \ - io.k8s.display-name="kite controller" \ - io.openshift.tags="kite-controller,github-action-controller" - -ENV CONTROLLER_ROOT=/home/controller -ENV KUBECONFIG=${CONTROLLER_ROOT}/.kube/config - -USER root - -# install red hat root CA to access red hat internal https service -ADD https://password.corp.redhat.com/RH-IT-Root-CA.crt \ - /etc/pki/ca-trust/source/anchors/ -RUN update-ca-trust extract - -RUN dnf -y update && \ - dnf -y install \ - net-tools \ - procps-ng \ - curl \ - gcc \ - libev-devel \ - python3 \ - python3-devel \ - python3-pip && \ - dnf clean all && \ - pip install bottle requests bjoern pyyaml && \ - curl -o /usr/bin/oc http://file-server-virt-qe-3rd.cloud.paas.psi.redhat.com/oc && \ - chmod 755 /usr/bin/oc && \ - mkdir -p ${CONTROLLER_ROOT} && \ - chmod -R g=u ${CONTROLLER_ROOT} /etc/passwd /etc/group && \ - chgrp -R 0 ${CONTROLLER_ROOT} - -COPY controller.py entrypoint.sh /home/controller/ -RUN chmod 755 ${CONTROLLER_ROOT}/{controller.py,entrypoint.sh} - -EXPOSE 8080 - -WORKDIR ${CONTROLLER_ROOT} - -USER 1001 - -ENTRYPOINT ["/home/controller/entrypoint.sh"] -CMD ["/home/controller/controller.py"] diff --git a/controller/controller.py b/controller/controller.py deleted file mode 100755 index cb31936..0000000 --- a/controller/controller.py +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/python3 - -import os -import time -import subprocess -import yaml -import random -import string - -from bottle import route, run -import requests -from requests.adapters import HTTPAdapter -from requests.packages.urllib3.util.retry import Retry - - -@route("/runner/delete/", method="PUT") -def shut_runner(name): - token = os.environ.get("GITHUB_ACCESS_TOKEN") - api = "https://api.github.com/orgs/virt-s1/actions/" - headers = { - "User-Agent": "kite-action", - "Accept": "application/vnd.github.v3+json", - "Authorization": "token " + token - } - # runner_name: github action runner name (Github->Actions) - # pod_name: openshift pod name (openshift->pod->name) - runner_name = pod_name = name - - # Wordaround issue - Max retries exceeded with URL in requests - # From: https://stackoverflow.com/questions/23013220 - session = requests.Session() - retry = Retry(connect=5, backoff_factor=1) - adapter = HTTPAdapter(max_retries=retry) - session.mount("https://", adapter) - session.keep_alive = False - - # 3 minutes timeout (18*10 seconds) - # wait until action runner is not busy to remove runner and delete pod - retry_times = 18 - while (retry_times > 0): - result = session.get(api + "runners", headers=headers).json() - for x in result["runners"]: - if (x["name"] == runner_name): - this_runner = x - if not this_runner["busy"]: - runner_id = this_runner["id"] - break - time.sleep(10) - retry_times -= 1 - - # remove action runner from org - session.delete(api + "runners/" + str(runner_id), headers=headers) - # delete pod - subprocess.call(["oc", "delete", "--grace-period=0", "--force", "pods/" + pod_name]) - - -@route("/runner/create/", method="PUT") -def create_runner(repo): - # random string for pod name - letters_and_digits = string.ascii_lowercase + string.digits - surfix = ''.join((random.choice(letters_and_digits) for i in range(5))) - # pod object - pod_obj = { - 'kind': 'Pod', - 'apiVersion': 'v1', - 'metadata': { - 'name': repo + "-runner-" + surfix, - 'labels': { - 'kite': 'self-hosted-runner' - } - }, - 'spec': { - 'containers': [ - { - 'name': repo + "-runner-" + surfix, - 'image': 'docker-registry.default.svc:5000/virt-qe-3rd/github-runner:latest', - 'imagePullPolicy': 'IfNotPresent', - 'resources': { - 'requests': { - 'memory': '500Mi', - 'cpu': '500m' - }, - 'limits': { - 'memory': '2Gi', - 'cpu': '1000m' - } - }, - 'env': [ - { - 'name': 'KITE_CONTROLLER_API_NETLOC', - 'valueFrom': { - 'secretKeyRef': { - 'name': 'kite-controller-api-netloc', - 'key': 'controller_api_netloc' - } - } - }, - { - 'name': 'RUNNER_ORGANIZATION_URL', - 'value': 'https://github.com/virt-s1' - }, - { - 'name': 'RUNNER_LABELS', - 'value': 'kite-runner' - }, - { - 'name': 'GITHUB_ACCESS_TOKEN', - 'valueFrom': { - 'secretKeyRef': { - 'name': 'github-access-token', - 'key': 'token' - } - } - }, - { - 'name': 'AWS_REGION', - 'value': 'us-east-1' - }, - { - 'name': 'AWS_ACCESS_KEY', - 'valueFrom': { - 'secretKeyRef': { - 'name': 'aws-access-key-id', - 'key': 'secrettext' - } - } - }, - { - 'name': 'AWS_SECRET_KEY', - 'valueFrom': { - 'secretKeyRef': { - 'name': 'aws-secret-access-key', - 'key': 'secrettext' - } - } - }, - { - 'name': 'VSPHERE_USERNAME', - 'valueFrom': { - 'secretKeyRef': { - 'name': 'vsphere-service-account-username', - 'key': 'secrettext' - } - } - }, - { - 'name': 'VSPHERE_PASSWORD', - 'valueFrom': { - 'secretKeyRef': { - 'name': 'vsphere-service-account-password', - 'key': 'secrettext' - } - } - }, - { - 'name': 'VSPHERE_SERVER', - 'value': '10.73.73.245' - }, - { - 'name': 'ESXI_HOST', - 'value': '10.73.196.33' - }, - { - 'name': 'ESXI_DATACENTER', - 'value': 'Datacenter7.0-AMD' - }, - { - 'name': 'ESXI_DATASTORE', - 'value': 'datastore-33' - }, - { - 'name': 'TEST_OS', - 'value': 'rhel-8-3' - }, - { - 'name': 'VAULT_PASSWORD', - 'valueFrom': { - 'secretKeyRef': { - 'name': 'vsphere-service-account-password', - 'key': 'secrettext' - } - } - }, - { - 'name': 'RUNNER_POD_NAME', - 'valueFrom': { - 'fieldRef': { - 'fieldPath': 'metadata.name' - } - } - } - ] - } - ] - } - } - # save pod object to yaml file - with open("runner-pod.yaml", "w") as f: - yaml.dump(pod_obj, f) - # create pod - subprocess.call(["oc", "create", "-f", "runner-pod.yaml"]) - - -# used by readinessProbe and livenessProbe on openshift -@route("/probe", method="GET") -def oc_probe(): - return("Controller is running") - - -# dev -# run(reloader=True, debug=True) -# prod -run(host="0.0.0.0", port=8080, server="bjoern") diff --git a/controller/entrypoint.sh b/controller/entrypoint.sh deleted file mode 100644 index 844cc4c..0000000 --- a/controller/entrypoint.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -if ! whoami &> /dev/null; then - if [ -w /etc/passwd ]; then - echo "controller:x:$(id -u):$(id -g):,,,:${HOME}:/bin/bash" >> /etc/passwd - echo "controller:x:$(id -G | cut -d' ' -f 2)" >> /etc/group - fi -fi - -TOKEN=$(cat "/var/run/secrets/kubernetes.io/serviceaccount/token") -oc login https://paas.psi.redhat.com:443 --token=${TOKEN} -oc project virt-qe-3rd - -exec "$@" diff --git a/controller/openshift/controller-template.yaml b/controller/openshift/controller-template.yaml deleted file mode 100644 index 6fcdddb..0000000 --- a/controller/openshift/controller-template.yaml +++ /dev/null @@ -1,180 +0,0 @@ ---- -kind: Template -apiVersion: v1 -labels: - app: kite-controller - template: kite-controller-template -metadata: - name: kite-controller - annotations: - openshift.io/display-name: kite action controller - tags: kite,controller,kite-controller - -objects: - - kind: ImageStream - apiVersion: v1 - metadata: - labels: - app: ${KITE_CONTROLLER_NAME} - name: ${KITE_CONTROLLER_NAME} - - kind: BuildConfig - apiVersion: v1 - metadata: - labels: - app: ${KITE_CONTROLLER_NAME} - name: ${KITE_CONTROLLER_NAME} - spec: - output: - to: - kind: ImageStreamTag - name: ${KITE_CONTROLLER_IMAGE_STREAM_TAG} - resources: {} - source: - type: Git - contextDir: ${CONTEXTDIR} - git: - uri: ${REPO_URL} - ref: ${REPO_REF} - strategy: - type: Docker - dockerStrategy: - noCache: true - forcePull: true - triggers: - - type: ConfigChange - successfulBuildsHistoryLimit: 2 - failedBuildsHistoryLimit: 2 - - kind: Route - apiVersion: v1 - metadata: - name: ${KITE_CONTROLLER_NAME} - annotations: - template.openshift.io/expose-uri: http://{.spec.host}{.spec.path} - spec: - tls: - termination: edge - insecureEdgeTerminationPolicy: Redirect - to: - kind: Service - name: ${KITE_CONTROLLER_NAME} - - kind: DeploymentConfig - apiVersion: v1 - metadata: - name: ${KITE_CONTROLLER_NAME} - spec: - replicas: 1 - selector: - name: ${KITE_CONTROLLER_NAME} - strategy: - type: Recreate - template: - metadata: - labels: - name: ${KITE_CONTROLLER_NAME} - spec: - containers: - - name: ${KITE_CONTROLLER_NAME} - image: ${KITE_CONTROLLER_IMAGE_STREAM_TAG} - imagePullPolicy: IfNotPresent - readinessProbe: - httpGet: - path: /probe - port: 8080 - initialDelaySeconds: 3 - timeoutSeconds: 240 - livenessProbe: - failureThreshold: 2 - httpGet: - path: /probe - port: 8080 - initialDelaySeconds: 420 - periodSeconds: 360 - timeoutSeconds: 240 - resources: - limits: - memory: "1Gi" - cpu: "1000m" - requests: - memory: "500Mi" - cpu: "512m" - securityContext: - capabilities: {} - privileged: false - terminationMessagePath: /dev/termination-log - env: - - name: GITHUB_ACCESS_TOKEN - valueFrom: - secretKeyRef: - name: github-access-token - key: token - dnsPolicy: ClusterFirst - restartPolicy: Always - serviceAccountName: ${KITE_CONTROLLER_NAME} - triggers: - - type: "ConfigChange" - - type: "ImageChange" - imageChangeParams: - automatic: true - containerNames: - - ${KITE_CONTROLLER_NAME} - from: - kind: "ImageStreamTag" - name: ${KITE_CONTROLLER_IMAGE_STREAM_TAG} - - kind: ServiceAccount - apiVersion: v1 - metadata: - name: ${KITE_CONTROLLER_NAME} - - kind: RoleBinding - apiVersion: v1 - metadata: - name: "${KITE_CONTROLLER_NAME}_edit" - groupNames: - subjects: - - kind: ServiceAccount - name: "${KITE_CONTROLLER_NAME}" - roleRef: - name: edit - # TCP port mapping 8080-80 - - kind: Service - apiVersion: v1 - metadata: - name: "${KITE_CONTROLLER_NAME}" - annotations: - service.alpha.openshift.io/dependencies: '[{"name": "${KITE_CONTROLLER_NAME}", - "namespace": "", "kind": "Service"}]' - service.openshift.io/infrastructure: "true" - creationTimestamp: - spec: - ports: - - name: web - protocol: TCP - port: 80 - targetPort: 8080 - nodePort: 0 - selector: - name: "${KITE_CONTROLLER_NAME}" - type: ClusterIP - sessionAffinity: None -# global parameters -parameters: - - description: Git repository with Dockerfile and master entrypoint. - displayName: Repository URL - name: REPO_URL - value: https://github.com/virt-s1/kite-action.git - required: true - - description: The sub-directory inside the repository. - displayName: Context Directory - name: CONTEXTDIR - value: controller - - description: The git ref or tag to use for customization. - displayName: Git Reference - name: REPO_REF - value: master - - name: KITE_CONTROLLER_NAME - displayName: kite controller APP name - description: The name of the kite controller application. - value: kite-controller - - name: KITE_CONTROLLER_IMAGE_STREAM_TAG - displayName: kite controller ImageStreamTag - description: Name of the ImageStreamTag to be used for the kite controller image. - value: kite-controller:latest diff --git a/deploy/aws_deploy.yaml b/deploy/aws_deploy.yaml new file mode 100644 index 0000000..94c0eb0 --- /dev/null +++ b/deploy/aws_deploy.yaml @@ -0,0 +1,62 @@ +--- +- hosts: localhost + become: no + vars: + aws_profile: "{{ lookup('env', 'AWS_PROFILE') }}" + aws_region: "{{ lookup('env', 'AWS_REGION') }}" + sqs_name: "kite-webhook" + sqs_policy_file: "{{ lookup('env', 'SQS_POLICY_FILE') }}" + kms_alias: "kite-webhook" + kms_policy_file: "{{ lookup('env', 'KMS_POLICY_FILE') }}" + s3_bucket_name: "kite-storage" + + + tasks: + - name: deploy SQS + community.aws.sqs_queue: + profile: "{{ aws_profile }}" + region: "{{ aws_region }}" + name: "{{ sqs_name }}" + visibility_timeout: 30 + message_retention_period: 86400 + maximum_message_size: 262144 + policy: "{{ lookup('file', sqs_policy_file) | from_json }}" + tags: + kite: webhook + name: "{{ sqs_name }}" + + - name: get SQS queue URL + community.aws.sqs_queue: + profile: "{{ aws_profile }}" + region: "{{ aws_region }}" + name: "{{ sqs_name }}" + register: sqs_result + retries: 5 + until: sqs_result.queue_url != "null" + + - set_fact: + sqs_queue_url: "{{ sqs_result.queue_url }}" + + - name: deploy KMS + community.aws.aws_kms: + profile: "{{ aws_profile }}" + region: "{{ aws_region }}" + alias: "{{ kms_alias }}" + description: "KMS for kite-edge, used by lambda env encrypt" + enable_key_rotation: no + enabled: yes + key_spec: SYMMETRIC_DEFAULT + key_usage: ENCRYPT_DECRYPT + policy: "{{ lookup('file', kms_policy_file) | from_json }}" + tags: + kite: webhook + name: "{{ kms_alias }}" + register: kms_result + + - name: deploy S3 bucket + amazon.aws.aws_s3: + profile: "{{ aws_profile }}" + region: "{{ aws_region }}" + bucket: "{{ s3_bucket_name }}" + mode: create + permission: public-read diff --git a/dummy/Dockerfile b/dummy/Dockerfile deleted file mode 100644 index af6ac23..0000000 --- a/dummy/Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -### dummy Github action runner -# create pod and register as runner -# delete pod to keep runner registed and make it offline -# all test tasks will not fail due to dummy runner -FROM fedora:32 - -LABEL name="kite-dummy" \ - maintainer="xiaofwan@redhat.com" \ - vendor="Red Hat QE Section 1" \ - version="1.0" \ - release="1" \ - summary="A dummy Github action runner" \ - description="dummy runner means an offline Github action runner" \ - io.k8s.description="dummy runner means an offline Github action runner" \ - io.k8s.display-name="kite dummy runner" \ - io.openshift.tags="kite-dummy,dummy-runner,dummy,kite" - -ENV DUMMY_ROOT=/home/dummy -ENV KUBECONFIG=${DUMMY_ROOT}/.kube/config - -USER root - -# install red hat root CA to access red hat internal https service -ADD https://password.corp.redhat.com/RH-IT-Root-CA.crt \ - /etc/pki/ca-trust/source/anchors/ -RUN update-ca-trust extract - -RUN dnf -y update && \ - dnf -y install \ - net-tools \ - procps-ng \ - curl \ - gcc \ - libev-devel \ - python3 \ - python3-devel \ - python3-pip && \ - dnf clean all && \ - pip install requests pyyaml && \ - curl -o /usr/bin/oc http://file-server-virt-qe-3rd.cloud.paas.psi.redhat.com/oc && \ - chmod 755 /usr/bin/oc && \ - mkdir -p ${DUMMY_ROOT} && \ - chmod -R g=u ${DUMMY_ROOT} /etc/passwd /etc/group && \ - chgrp -R 0 ${DUMMY_ROOT} - -COPY dummy.py entrypoint.sh /home/dummy/ -RUN chmod 755 ${DUMMY_ROOT}/{dummy.py,entrypoint.sh} - -WORKDIR ${DUMMY_ROOT} - -USER 1001 - -ENTRYPOINT ["/home/dummy/entrypoint.sh"] -CMD ["python3", "-u", "/home/dummy/dummy.py"] diff --git a/dummy/dummy.py b/dummy/dummy.py deleted file mode 100755 index ce285fa..0000000 --- a/dummy/dummy.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/python3 - -import os -import json -import time -import subprocess -import yaml -from datetime import date - -import requests -from requests.adapters import HTTPAdapter -from requests.packages.urllib3.util.retry import Retry - - -# make date(month+day) as pod name surfix -surfix = date.today().strftime("%m%d") -runner_name = "dummy-runner-" + surfix -# pod object -pod_obj = { - 'kind': 'Pod', - 'apiVersion': 'v1', - 'metadata': { - 'name': runner_name - }, - 'spec': { - 'containers': [ - { - 'name': runner_name, - 'image': 'docker-registry.default.svc:5000/virt-qe-3rd/github-runner:latest', - 'imagePullPolicy': 'IfNotPresent', - 'resources': { - 'requests': { - 'memory': '250Mi', - 'cpu': '100m' - }, - 'limits': { - 'memory': '500Mi', - 'cpu': '500m' - } - }, - 'env': [ - { - 'name': 'RUNNER_ORGANIZATION_URL', - 'value': 'https://github.com/virt-s1' - }, - { - 'name': 'RUNNER_LABELS', - 'value': 'kite-runner' - }, - { - 'name': 'GITHUB_ACCESS_TOKEN', - 'valueFrom': { - 'secretKeyRef': { - 'name': 'github-access-token', - 'key': 'token' - } - } - } - ] - } - ] - } -} -# save pod object to yaml file -with open("runner-pod.yaml", "w") as f: - yaml.dump(pod_obj, f) -# create pod -subprocess.run(["oc", "create", "-f", "runner-pod.yaml"]) - -# wait 360*10 seonds until pod's running -retry_times = 360 -while (retry_times > 0): - raw_output = subprocess.run( - ["oc", "get", "pod", runner_name, "-o", "json"], stdout=subprocess.PIPE) - output = json.loads(raw_output.stdout) - print(f'current status: {output["status"]["phase"]}') - - if output["status"]["phase"] == "Running": - break - - time.sleep(10) - retry_times -= 1 - -# wait for new runner running -token = os.environ.get("GITHUB_ACCESS_TOKEN") -api = "https://api.github.com/orgs/virt-s1/actions/" -headers = { - "User-Agent": "kite-action", - "Accept": "application/vnd.github.v3+json", - "Authorization": "token " + token -} - -# Wordaround issue - Max retries exceeded with URL in requests -# From: https://stackoverflow.com/questions/23013220 -session = requests.Session() -retry = Retry(connect=5, backoff_factor=1) -adapter = HTTPAdapter(max_retries=retry) -session.mount("https://", adapter) -session.keep_alive = False - -# 10 minutes timeout (60*10 seconds) -# wait until action runner is not busy to remove runner and delete pod -retry_times = 60 -while (retry_times > 0): - result = session.get(api + "runners", headers=headers).json() - for x in result["runners"]: - if x["name"] == runner_name and x["status"] == "online": - print(f"{runner_name} is online") - break - else: - time.sleep(10) - retry_times -= 1 - continue - break - -# delete pod -subprocess.run(["oc", "delete", "--grace-period=0", "--force", "pods/" + runner_name]) - -# un-register old dummy runner because we have a new one already -# do not wait for Github audo-delete every 30 days -for x in result["runners"]: - if x["name"].startswith("dummy-runner") and x["name"] != runner_name: - runner_id = x["id"] - # un-register old dummy runner from org - session.delete(api + "runners/" + str(runner_id), headers=headers) - print(f"delete old dummy runner {x['name']}") diff --git a/dummy/entrypoint.sh b/dummy/entrypoint.sh deleted file mode 100644 index 346eb38..0000000 --- a/dummy/entrypoint.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -if ! whoami &> /dev/null; then - if [ -w /etc/passwd ]; then - echo "dummy:x:$(id -u):$(id -g):,,,:${HOME}:/bin/bash" >> /etc/passwd - echo "dummy:x:$(id -G | cut -d' ' -f 2)" >> /etc/group - fi -fi - -TOKEN=$(cat "/var/run/secrets/kubernetes.io/serviceaccount/token") -oc login https://paas.psi.redhat.com:443 --token=${TOKEN} -oc project virt-qe-3rd - -exec "$@" diff --git a/dummy/openshift/dummy-buildconfig.yaml b/dummy/openshift/dummy-buildconfig.yaml deleted file mode 100644 index 78db241..0000000 --- a/dummy/openshift/dummy-buildconfig.yaml +++ /dev/null @@ -1,56 +0,0 @@ ---- -apiVersion: v1 -kind: Template -labels: - template: kite-dummy -metadata: - name: kite-dummy - -objects: - - apiVersion: v1 - kind: ImageStream - metadata: - labels: - app: ${KITE_DUMMY_NAME} - name: ${KITE_DUMMY_NAME} - - apiVersion: v1 - kind: BuildConfig - metadata: - name: ${KITE_DUMMY_NAME} - spec: - output: - to: - kind: ImageStreamTag - name: ${KITE_DUMMY_NAME}:latest - resources: {} - source: - contextDir: ${CONTEXTDIR} - git: - ref: ${REPO_REF} - uri: ${REPO_URL} - type: Git - strategy: - dockerStrategy: - forcePull: true - noCache: true - type: Docker - triggers: - - type: ConfigChange - successfulBuildsHistoryLimit: 2 - failedBuildsHistoryLimit: 2 - -parameters: - - description: Git repository with Dockerfile and entrypoint. - displayName: Repository URL - name: REPO_URL - value: https://github.com/virt-s1/kite-action.git - - description: The sub-directory inside the repository. - displayName: Context Directory - name: CONTEXTDIR - value: dummy - - description: The git ref or tag to use for customization. - displayName: Git Reference - name: REPO_REF - value: master - - name: KITE_DUMMY_NAME - value: kite-dummy diff --git a/dummy/openshift/dummy-cronjob.yaml b/dummy/openshift/dummy-cronjob.yaml deleted file mode 100644 index fce01e8..0000000 --- a/dummy/openshift/dummy-cronjob.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: kite-dummy -spec: - schedule: "0 23 */29 * *" - jobTemplate: - spec: - template: - metadata: - labels: - parent: "cronjobdummy" - spec: - containers: - - name: kite-dummy - image: docker-registry.default.svc:5000/virt-qe-3rd/kite-dummy:latest - imagePullPolicy: IfNotPresent - resources: - limits: - cpu: 500m - memory: 500Mi - requests: - cpu: 500m - memory: 250Mi - env: - - name: GITHUB_ACCESS_TOKEN - valueFrom: - secretKeyRef: - key: token - name: github-access-token - restartPolicy: Never diff --git a/dummy/openshift/dummy-pod.yaml b/dummy/openshift/dummy-pod.yaml deleted file mode 100644 index 0a91ae9..0000000 --- a/dummy/openshift/dummy-pod.yaml +++ /dev/null @@ -1,24 +0,0 @@ ---- -apiVersion: v1 -kind: Pod -metadata: - name: kite-dummy -spec: - containers: - - env: - - name: GITHUB_ACCESS_TOKEN - valueFrom: - secretKeyRef: - key: token - name: github-access-token - image: docker-registry.default.svc:5000/virt-qe-3rd/kite-dummy:latest - imagePullPolicy: IfNotPresent - name: kite-dummy - resources: - limits: - cpu: 500m - memory: 500Mi - requests: - cpu: 100m - memory: 250Mi - restartPolicy: Never diff --git a/kite-action.png b/kite-action.png deleted file mode 100644 index a9bae899ecd52e53b0476c895e955bc6569fc780..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81759 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 diff --git a/proxy/Dockerfile b/proxy/Dockerfile index 008a7c8..bf2ca71 100644 --- a/proxy/Dockerfile +++ b/proxy/Dockerfile @@ -1,10 +1,10 @@ ### Proxy server for kite project on OpenShift -FROM fedora:32 +FROM quay.io/fedora/fedora:36-x86_64 LABEL name="kite-proxy" \ maintainer="xiaofwan@redhat.com" \ vendor="Red Hat QE Section 1" \ - version="1.0" \ + version="2.0" \ release="1" \ summary="kite proxy server" \ description="A proxy server to fetch AWS SQS message repeatly" \ diff --git a/proxy/ansible.cfg b/proxy/ansible.cfg new file mode 100644 index 0000000..2ffc410 --- /dev/null +++ b/proxy/ansible.cfg @@ -0,0 +1,8 @@ +[defaults] +timeout = 30 +# human-readable stdout/stderr results display +stdout_callback = yaml + +[ssh_connection] +scp_if_ssh=True +pipelining=False diff --git a/proxy/group_vars/all b/proxy/group_vars/all new file mode 100644 index 0000000..5f1db1d --- /dev/null +++ b/proxy/group_vars/all @@ -0,0 +1,49 @@ +os: "{{ lookup('env', 'TEST_OS') }}" +arch: "{{ lookup('env', 'ARCH') | default('x86_64', true) }}" + +repos: + rhel-8-6: + baseos: http://download-node-02.eng.bos.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.6.0/compose/BaseOS/x86_64/os/ + appstream: http://download-node-02.eng.bos.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.6.0/compose/AppStream/x86_64/os/ + rhel-8-7: + baseos: http://download-node-02.eng.bos.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.7.0/compose/BaseOS/x86_64/os/ + appstream: http://download-node-02.eng.bos.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.7.0/compose/AppStream/x86_64/os/ + rhel-9-0: + baseos: http://download-node-02.eng.bos.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.0.0/compose/BaseOS/x86_64/os/ + appstream: http://download-node-02.eng.bos.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.0.0/compose/AppStream/x86_64/os/ + rhel-9-1: + baseos: http://download-node-02.eng.bos.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.1.0/compose/BaseOS/x86_64/os/ + appstream: http://download-node-02.eng.bos.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.1.0/compose/AppStream/x86_64/os/ + +images: + centos-stream-8: edge-centos-stream-8 + centos-stream-9: edge-centos-stream-9 + rhel-8-6: RHEL-8.6.0-x86_64-nightly-latest + rhel-8-7: RHEL-8.7.0-x86_64-nightly-latest + rhel-9-0: RHEL-9.0.0-x86_64-nightly-latest + rhel-9-1: RHEL-9.1.0-x86_64-nightly-latest + fedora-35: Fedora-Cloud-Base-35 + +# Runner user - user under which is the local runner service running +runner_user: admin + +# Directory where the local runner will be installed +runner_dir: /opt/actions-runner + +# Directory where the runner package will be dowloaded +runner_pkg_tempdir: /tmp/gh_actions_runner + +# Do not show Ansible logs which may contain sensitive data (registration token) +hide_sensitive_logs: yes + +# GitHub address +github_url: "https://github.com" + +# GitHub API +github_api_url: "https://api.github.com" + +# Personal Access Token for your GitHub account +access_token: "{{ lookup('env', 'PERSONAL_ACCESS_TOKEN') }}" + +# GitHub Actions Runner repository (change it if you want to use custom Actions Runner fork) +runner_download_repository: "actions/runner" diff --git a/proxy/install_runner.yaml b/proxy/install_runner.yaml new file mode 100644 index 0000000..9f77e85 --- /dev/null +++ b/proxy/install_runner.yaml @@ -0,0 +1,161 @@ +--- +- hosts: openstack + gather_facts: no + become: no + vars: + cloud_profile: "rhos-01" + date_label: "{{ lookup('pipe', 'date +%y%m%d') }}" + + tasks: + - name: create keypair for rhel-edge test + openstack.cloud.keypair: + cloud: "{{ cloud_profile }}" + state: present + name: "rhel-edge" + public_key_file: key/ostree_key.pub + + - name: "deploy RHEL instance" + openstack.cloud.server: + cloud: "{{ cloud_profile }}" + name: "{{ os }}-{{ date_label }}" + image: "{{ images[os] }}" + flavor: "ci.standard.large" + network: "shared_net_9" + key_name: "rhel-edge" + security_groups: ssh_only + auto_ip: yes + config_drive: yes + wait: yes + timeout: 600 + userdata: | + #cloud-config + users: + - default + - name: admin + gecos: Administrator + groups: users,wheel + sudo: ALL=(ALL) NOPASSWD:ALL + ssh_authorized_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzxo5dEcS+LDK/OFAfHo6740EyoDM8aYaCkBala0FnWfMMTOq7PQe04ahB0eFLS3IlQtK5bpgzxBdFGVqF6uT5z4hhaPjQec0G3+BD5Pxo6V+SxShKZo+ZNGU3HVrF9p2V7QH0YFQj5B8F6AicA3fYh2BVUFECTPuMpy5A52ufWu0r4xOFmbU7SIhRQRAQz2u4yjXqBsrpYptAvyzzoN4gjUhNnwOHSPsvFpWoBFkWmqn0ytgHg3Vv9DlHW+45P02QH1UFedXR2MqLnwRI30qqtaOkVS+9rE/dhnR+XPpHHG+hv2TgMDAuQ3IK7Ab5m/yCbN73cxFifH4LST0vVG3Jx45xn+GTeHHhfkAfBSCtya6191jixbqyovpRunCBKexI5cfRPtWOitM3m7Mq26r7LpobMM+oOLUm4p0KKNIthWcmK9tYwXWSuGGfUQ+Y8gt7E0G06ZGbCPHOrxJ8lYQqXsif04piONPA/c9Hq43O99KPNGShONCS9oPFdOLRT3U= ostree-image-test + yum_repos: + baseos: + name: rhel-baseos + baseurl: "{{ repos[os]['baseos'] }}" + enabled: true + gpgcheck: false + sslverify: false + appstream: + name: rhel-appstream + baseurl: "{{ repos[os]['appstream'] }}" + enabled: true + gpgcheck: false + sslverify: false + packages: + - python3 + - python3-dnf + register: tmp_instance_result + when: "'rhel' in os" + + # avoid registering a variable when a “when” condition is *not* met + - set_fact: + instance_result: "{{ tmp_instance_result }}" + when: tmp_instance_result.changed + + - name: "deploy CentOS Stream instance" + openstack.cloud.server: + cloud: "{{ cloud_profile }}" + name: "{{ os }}-{{ date_label }}" + image: "{{ images[os] }}" + flavor: "ci.standard.large" + network: "shared_net_9" + key_name: "rhel-edge" + security_groups: ssh_only + auto_ip: yes + config_drive: yes + wait: yes + timeout: 600 + userdata: | + #cloud-config + packages: + - python3 + - python3-dnf + users: + - default + - name: admin + gecos: Administrator + groups: users,wheel + sudo: ALL=(ALL) NOPASSWD:ALL + ssh_authorized_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzxo5dEcS+LDK/OFAfHo6740EyoDM8aYaCkBala0FnWfMMTOq7PQe04ahB0eFLS3IlQtK5bpgzxBdFGVqF6uT5z4hhaPjQec0G3+BD5Pxo6V+SxShKZo+ZNGU3HVrF9p2V7QH0YFQj5B8F6AicA3fYh2BVUFECTPuMpy5A52ufWu0r4xOFmbU7SIhRQRAQz2u4yjXqBsrpYptAvyzzoN4gjUhNnwOHSPsvFpWoBFkWmqn0ytgHg3Vv9DlHW+45P02QH1UFedXR2MqLnwRI30qqtaOkVS+9rE/dhnR+XPpHHG+hv2TgMDAuQ3IK7Ab5m/yCbN73cxFifH4LST0vVG3Jx45xn+GTeHHhfkAfBSCtya6191jixbqyovpRunCBKexI5cfRPtWOitM3m7Mq26r7LpobMM+oOLUm4p0KKNIthWcmK9tYwXWSuGGfUQ+Y8gt7E0G06ZGbCPHOrxJ8lYQqXsif04piONPA/c9Hq43O99KPNGShONCS9oPFdOLRT3U= ostree-image-test + register: tmp_instance_result + when: "'centos' in os" + + # avoid registering a variable when a “when” condition is *not* met + - set_fact: + instance_result: "{{ tmp_instance_result }}" + when: tmp_instance_result.changed + + - name: "deploy Fedora instance" + openstack.cloud.server: + cloud: "{{ cloud_profile }}" + name: "{{ os }}-{{ date_label }}" + image: "{{ images[os] }}" + flavor: "ci.standard.large" + network: "shared_net_9" + key_name: "rhel-edge" + security_groups: ssh_only + auto_ip: yes + config_drive: yes + wait: yes + timeout: 600 + userdata: | + #cloud-config + packages: + - python3 + - python3-dnf + users: + - default + - name: admin + gecos: Administrator + groups: users,wheel + sudo: ALL=(ALL) NOPASSWD:ALL + ssh_authorized_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzxo5dEcS+LDK/OFAfHo6740EyoDM8aYaCkBala0FnWfMMTOq7PQe04ahB0eFLS3IlQtK5bpgzxBdFGVqF6uT5z4hhaPjQec0G3+BD5Pxo6V+SxShKZo+ZNGU3HVrF9p2V7QH0YFQj5B8F6AicA3fYh2BVUFECTPuMpy5A52ufWu0r4xOFmbU7SIhRQRAQz2u4yjXqBsrpYptAvyzzoN4gjUhNnwOHSPsvFpWoBFkWmqn0ytgHg3Vv9DlHW+45P02QH1UFedXR2MqLnwRI30qqtaOkVS+9rE/dhnR+XPpHHG+hv2TgMDAuQ3IK7Ab5m/yCbN73cxFifH4LST0vVG3Jx45xn+GTeHHhfkAfBSCtya6191jixbqyovpRunCBKexI5cfRPtWOitM3m7Mq26r7LpobMM+oOLUm4p0KKNIthWcmK9tYwXWSuGGfUQ+Y8gt7E0G06ZGbCPHOrxJ8lYQqXsif04piONPA/c9Hq43O99KPNGShONCS9oPFdOLRT3U= ostree-image-test + register: tmp_instance_result + when: "'fedora' in os" + + # avoid registering a variable when a “when” condition is *not* met + - set_fact: + instance_result: "{{ tmp_instance_result }}" + when: tmp_instance_result.changed + + - name: waits until instance is reachable + wait_for: + host: "{{ instance_result.openstack.public_v4 }}" + port: 22 + search_regex: OpenSSH + delay: 10 + + - name: add instance ip into host group guest + add_host: + name: "{{ instance_result.openstack.public_v4 }}" + groups: guest + + - name: keep private key permission to 600 + file: + path: "key/ostree_key" + mode: "0600" + + - name: ensure cloud-init has finished + raw: test -f /var/lib/cloud/instance/boot-finished + retries: 60 + register: cloud_init_check + changed_when: false + until: cloud_init_check is success + delegate_to: "{{ instance_result.openstack.public_v4 }}" + +- hosts: guest + gather_facts: yes + become: no + + tasks: diff --git a/proxy/inventory b/proxy/inventory new file mode 100644 index 0000000..b00212f --- /dev/null +++ b/proxy/inventory @@ -0,0 +1,21 @@ +[openstack] +localhost + +[guest] + +[cloud:children] +openstack + +[remote:children] +guest + +[cloud:vars] +ansible_connection=local + +[remote:vars] +ansible_user=admin +ansible_private_key_file="{{ playbook_dir }}/key/ostree_key" +ansible_ssh_common_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" + +[all:vars] +ansible_python_interpreter=/usr/bin/python3 diff --git a/proxy/key/ostree_key b/proxy/key/ostree_key new file mode 100644 index 0000000..716fd87 --- /dev/null +++ b/proxy/key/ostree_key @@ -0,0 +1,38 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/proxy/key/ostree_key.pub b/proxy/key/ostree_key.pub new file mode 100644 index 0000000..184fb50 --- /dev/null +++ b/proxy/key/ostree_key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzxo5dEcS+LDK/OFAfHo6740EyoDM8aYaCkBala0FnWfMMTOq7PQe04ahB0eFLS3IlQtK5bpgzxBdFGVqF6uT5z4hhaPjQec0G3+BD5Pxo6V+SxShKZo+ZNGU3HVrF9p2V7QH0YFQj5B8F6AicA3fYh2BVUFECTPuMpy5A52ufWu0r4xOFmbU7SIhRQRAQz2u4yjXqBsrpYptAvyzzoN4gjUhNnwOHSPsvFpWoBFkWmqn0ytgHg3Vv9DlHW+45P02QH1UFedXR2MqLnwRI30qqtaOkVS+9rE/dhnR+XPpHHG+hv2TgMDAuQ3IK7Ab5m/yCbN73cxFifH4LST0vVG3Jx45xn+GTeHHhfkAfBSCtya6191jixbqyovpRunCBKexI5cfRPtWOitM3m7Mq26r7LpobMM+oOLUm4p0KKNIthWcmK9tYwXWSuGGfUQ+Y8gt7E0G06ZGbCPHOrxJ8lYQqXsif04piONPA/c9Hq43O99KPNGShONCS9oPFdOLRT3U= ostree-image-test diff --git a/proxy/proxy.py b/proxy/proxy.py index 2ad039e..d644b68 100755 --- a/proxy/proxy.py +++ b/proxy/proxy.py @@ -18,32 +18,40 @@ print("Fetching message from queue") +# Wordaround issue - Max retries exceeded with URL in requests +# From: https://stackoverflow.com/questions/23013220 +session = requests.Session() +retry = Retry(connect=5, backoff_factor=1) +adapter = HTTPAdapter(max_retries=retry) +session.mount("https://", adapter) +session.keep_alive = False +# Do not need verify certificate because it's internal +session.verify = False + +# All messages in SQS are allowed and valid +# Do not check/verify message again +# Do not check x-github-event here because it's filtered by webhook already while True: for message in queue.receive_messages( WaitTimeSeconds=20, MaxNumberOfMessages=10): print(f"Got message: {message.message_id}") parsed = json.loads(message.body) - headers = parsed["headers"] payload = parsed["payload"] - # Check message - if headers["x-github-event"] == "check_run" and payload["action"] == "created": - # Get repo name - repo_name = payload["repository"]["name"] - - # Wordaround issue - Max retries exceeded with URL in requests - # From: https://stackoverflow.com/questions/23013220 - session = requests.Session() - retry = Retry(connect=5, backoff_factor=1) - adapter = HTTPAdapter(max_retries=retry) - session.mount("https://", adapter) - session.keep_alive = False - # Do not need verify certificate because it's internal - session.verify = False - - # Call API to create runner pod - session.put(CONTROLLER_API_NETLOC + "/runner/create/" + repo_name) - print(f"Github runner - {repo_name} deploy starting") + # Create runner for queued action + if payload["action"] == "queued": + labels = payload["workflow_job"]["labels"] + label_str = ",".join(labels) + # Call API to create runner + session.put(CONTROLLER_API_NETLOC + "/runner/create/" + label_str) + print(f"Github runner - {label_str} deploy starting") + + # Remove runner instance for completed action + if payload["action"] == "completed": + runner_name = payload["workflow_job"]["runner_name"] + # Call API to delete runner instance + session.put(CONTROLLER_API_NETLOC + "/runner/delete/" + runner_name) + print(f"Github runner - {runner_name} destroy starting") # Delete the message if we made it this far. message.delete() diff --git a/sweeper/Dockerfile b/sweeper/Dockerfile deleted file mode 100644 index 755a288..0000000 --- a/sweeper/Dockerfile +++ /dev/null @@ -1,51 +0,0 @@ -### Sweep un-used github action runner -FROM fedora:32 - -LABEL name="kite-sweeper" \ - maintainer="xiaofwan@redhat.com" \ - vendor="Red Hat QE Section 1" \ - version="1.0" \ - release="1" \ - summary="Remove un-used github action runner" \ - description="Clean un-used github action runner every day" \ - io.k8s.description="Clean un-used github action runner every day" \ - io.k8s.display-name="runner sweeper" \ - io.openshift.tags="kite-sweeper,sweeper,kite" - -ENV SWEEPER_ROOT=/home/sweeper -ENV KUBECONFIG=${SWEEPER_ROOT}/.kube/config - -USER root - -# install red hat root CA to access red hat internal https service -ADD https://password.corp.redhat.com/RH-IT-Root-CA.crt \ - /etc/pki/ca-trust/source/anchors/ -RUN update-ca-trust extract - -RUN dnf -y update && \ - dnf -y install \ - net-tools \ - procps-ng \ - curl \ - gcc \ - libev-devel \ - python3 \ - python3-devel \ - python3-pip && \ - dnf clean all && \ - pip install requests pyyaml && \ - curl -o /usr/bin/oc http://file-server-virt-qe-3rd.cloud.paas.psi.redhat.com/oc && \ - chmod 755 /usr/bin/oc && \ - mkdir -p ${SWEEPER_ROOT} && \ - chmod -R g=u ${SWEEPER_ROOT} /etc/passwd /etc/group && \ - chgrp -R 0 ${SWEEPER_ROOT} - -COPY sweeper.py entrypoint.sh /home/sweeper/ -RUN chmod 755 ${SWEEPER_ROOT}/{sweeper.py,entrypoint.sh} - -WORKDIR ${SWEEPER_ROOT} - -USER 1001 - -ENTRYPOINT ["/home/sweeper/entrypoint.sh"] -CMD ["python3", "-u", "/home/sweeper/sweeper.py"] diff --git a/sweeper/entrypoint.sh b/sweeper/entrypoint.sh deleted file mode 100644 index 3b86c16..0000000 --- a/sweeper/entrypoint.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -if ! whoami &> /dev/null; then - if [ -w /etc/passwd ]; then - echo "sweeper:x:$(id -u):$(id -g):,,,:${HOME}:/bin/bash" >> /etc/passwd - echo "sweeper:x:$(id -G | cut -d' ' -f 2)" >> /etc/group - fi -fi - -TOKEN=$(cat "/var/run/secrets/kubernetes.io/serviceaccount/token") -oc login https://paas.psi.redhat.com:443 --token=${TOKEN} -oc project virt-qe-3rd - -exec "$@" diff --git a/sweeper/openshift/sweeper-buildconfig.yaml b/sweeper/openshift/sweeper-buildconfig.yaml deleted file mode 100644 index 9ad6f32..0000000 --- a/sweeper/openshift/sweeper-buildconfig.yaml +++ /dev/null @@ -1,56 +0,0 @@ ---- -apiVersion: v1 -kind: Template -labels: - template: kite-sweeper -metadata: - name: kite-sweeper - -objects: - - apiVersion: v1 - kind: ImageStream - metadata: - labels: - app: ${KITE_SWEEPER_NAME} - name: ${KITE_SWEEPER_NAME} - - apiVersion: v1 - kind: BuildConfig - metadata: - name: ${KITE_SWEEPER_NAME} - spec: - output: - to: - kind: ImageStreamTag - name: ${KITE_SWEEPER_NAME}:latest - resources: {} - source: - contextDir: ${CONTEXTDIR} - git: - ref: ${REPO_REF} - uri: ${REPO_URL} - type: Git - strategy: - dockerStrategy: - forcePull: true - noCache: true - type: Docker - triggers: - - type: ConfigChange - successfulBuildsHistoryLimit: 2 - failedBuildsHistoryLimit: 2 - -parameters: - - description: Git repository with Dockerfile and entrypoint. - displayName: Repository URL - name: REPO_URL - value: https://github.com/virt-s1/kite-action.git - - description: The sub-directory inside the repository. - displayName: Context Directory - name: CONTEXTDIR - value: sweeper - - description: The git ref or tag to use for customization. - displayName: Git Reference - name: REPO_REF - value: master - - name: KITE_SWEEPER_NAME - value: kite-sweeper diff --git a/sweeper/openshift/sweeper-cronjob.yaml b/sweeper/openshift/sweeper-cronjob.yaml deleted file mode 100644 index 8dfc115..0000000 --- a/sweeper/openshift/sweeper-cronjob.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: kite-sweeper -spec: - schedule: "5 20 * * *" - jobTemplate: - spec: - template: - metadata: - labels: - parent: "cronjobsweeper" - spec: - containers: - - name: kite-sweeper - image: docker-registry.default.svc:5000/virt-qe-3rd/kite-sweeper:latest - imagePullPolicy: IfNotPresent - resources: - limits: - cpu: 500m - memory: 500Mi - requests: - cpu: 500m - memory: 250Mi - env: - - name: GITHUB_ACCESS_TOKEN - valueFrom: - secretKeyRef: - key: token - name: github-access-token - restartPolicy: Never diff --git a/sweeper/openshift/sweeper-pod.yaml b/sweeper/openshift/sweeper-pod.yaml deleted file mode 100644 index de4395f..0000000 --- a/sweeper/openshift/sweeper-pod.yaml +++ /dev/null @@ -1,24 +0,0 @@ ---- -apiVersion: v1 -kind: Pod -metadata: - name: kite-sweeper -spec: - containers: - - env: - - name: GITHUB_ACCESS_TOKEN - valueFrom: - secretKeyRef: - key: token - name: github-access-token - image: docker-registry.default.svc:5000/virt-qe-3rd/kite-sweeper:latest - imagePullPolicy: IfNotPresent - name: kite-sweeper - resources: - limits: - cpu: 500m - memory: 500Mi - requests: - cpu: 100m - memory: 250Mi - restartPolicy: Never diff --git a/sweeper/sweeper.py b/sweeper/sweeper.py deleted file mode 100755 index 6f394b7..0000000 --- a/sweeper/sweeper.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/python3 - -import os -import json -import time -import subprocess -import yaml -from datetime import datetime - - -import requests -from requests.adapters import HTTPAdapter -from requests.packages.urllib3.util.retry import Retry - - -# list all pods running for more than 5 minutes -raw_output = subprocess.run( - ["oc", "get", "pod", "-l", "kite=self-hosted-runner", "-o", "json"], - stdout=subprocess.PIPE) -output = json.loads(raw_output.stdout) - -if output["items"]: - containers = [x["status"]["containerStatuses"][0] for x in output["items"]] - - # Python's datetime does not support the military timezone suffixes like 'Z' suffix for UTC. - # filter function - def filter_removing(x): - allowed_delta = 300 - delta = datetime.utcnow() - datetime.strptime( - x["state"]["running"]["startedAt"], '%Y-%m-%dT%H:%M:%SZ') - if delta.total_seconds() >= allowed_delta: - return True - else: - return False - - # containers in running status and started for more than 5 minutes - removing_pods = list(filter(filter_removing, containers)) - print(removing_pods) - - # list all online and not busy runners - token = os.environ.get("GITHUB_ACCESS_TOKEN") - api = "https://api.github.com/orgs/virt-s1/actions/" - headers = { - "User-Agent": "kite-action", - "Accept": "application/vnd.github.v3+json", - "Authorization": "token " + token - } - - # Wordaround issue - Max retries exceeded with URL in requests - # From: https://stackoverflow.com/questions/23013220 - session = requests.Session() - retry = Retry(connect=5, backoff_factor=1) - adapter = HTTPAdapter(max_retries=retry) - session.mount("https://", adapter) - session.keep_alive = False - - result = session.get(api + "runners", headers=headers).json() - print(result) - if result["runners"]: - runners = [x for x in result["runners"] if x["status"] == "online" and not x["busy"]] - runners_obj = {} - for x in runners: - runners_obj[x["name"]] = x["id"] - print(runners_obj) - - delete_pods = [] - removed_runners = [] - for runner in removing_pods: - if runner["name"] in runners_obj.keys(): - # remove action runner from org - session.delete(api + "runners/" + str(runners_obj[runner["name"]]), headers=headers) - # delete pod - subprocess.call( - ["oc", "delete", "--grace-period=0", "--force", "pods/" + runner["name"]]) - removed_runners.append(runner["name"]) - else: - delete_pods.append(runner["name"]) - - # delete pod if pod is not registed as runner - if delete_pods: - print(delete_pods) - for pod in delete_pods: - subprocess.call(["oc", "delete", "--grace-period=0", "--force", "pods/" + pod]) - else: # pods not registered as runner - for runner in removing_pods: - subprocess.call( - ["oc", "delete", "--grace-period=0", "--force", "pods/" + runner["name"]]) -else: - print("Github runner pod not found") diff --git a/upload/upload_runner_binary.yaml b/upload/upload_runner_binary.yaml new file mode 100644 index 0000000..1e36cd7 --- /dev/null +++ b/upload/upload_runner_binary.yaml @@ -0,0 +1,74 @@ +--- +- hosts: localhost + gather_facts: no + become: no + vars: + runner_download_repository: "actions/runner" + runner_pkg_tempdir: /tmp + github_actions_architecture: x64 + access_token: "{{ lookup('env', 'PERSONAL_ACCESS_TOKEN') }}" + aws_profile: "{{ lookup('env', 'AWS_PROFILE') }}" + aws_region: "{{ lookup('env', 'AWS_REGION') }}" + s3_bucket_name: "kite-storage" + + tasks: + - name: Get the current actions runner version on S3 + amazon.aws.aws_s3: + profile: "{{ aws_profile }}" + region: "{{ aws_region }}" + bucket: "{{ s3_bucket_name }}" + mode: list + prefix: actions-runner-linux-x64 + register: runner_result + + - set_fact: + current_version: "{{ runner_result.s3_keys[0] | regex_replace('actions-runner-linux-x64-(.*).tar.gz', '\\1') }}" + when: runner_result.s3_keys | length > 0 + + - name: Find the latest runner version + uri: + url: "https://api.github.com/repos/{{ runner_download_repository }}/releases/latest" + headers: + Authorization: "token {{ access_token }}" + Content-Type: "application/json" + method: GET + return_content: yes + status_code: 200 + body_format: json + register: api_response + + - name: Set runner_version variable + set_fact: + runner_version: "{{ api_response.json.tag_name | regex_replace('^v', '') }}" + + - block: + - name: Download runner package version - "{{ runner_version }}" + get_url: + url: + "https://github.com/{{ runner_download_repository }}/releases/download/v{{ runner_version }}/\ + actions-runner-linux-{{ github_actions_architecture }}-{{ runner_version }}.tar.gz" + dest: "{{ runner_pkg_tempdir }}/actions-runner-linux-{{ github_actions_architecture }}-{{ runner_version }}.tar.gz" + force: no + + - name: Upload action runner "{{ runner_version }}" to S3 bucket + amazon.aws.aws_s3: + profile: "{{ aws_profile }}" + region: "{{ aws_region }}" + bucket: "{{ s3_bucket_name }}" + mode: put + src: "{{ runner_pkg_tempdir }}/actions-runner-linux-{{ github_actions_architecture }}-{{ runner_version }}.tar.gz" + object: "actions-runner-linux-{{ github_actions_architecture }}-{{ runner_version }}.tar.gz" + permission: public-read + tags: + name: actions-runner + version: "{{ runner_version }}" + + - name: "Remove actions-runner-linux-{{ github_actions_architecture }}-{{ current_version }}.tar.gz" + amazon.aws.aws_s3: + profile: "{{ aws_profile }}" + region: "{{ aws_region }}" + bucket: "{{ s3_bucket_name }}" + mode: delobj + object: "actions-runner-linux-{{ github_actions_architecture }}-{{ current_version }}.tar.gz" + when: runner_result.s3_keys | length > 0 + when: current_version is defined and (runner_version is version(current_version, '>')) or (runner_result.s3_keys | length == 0) diff --git a/webhook/webhook.py b/webhook/webhook.py index 06ec25a..0b6d256 100755 --- a/webhook/webhook.py +++ b/webhook/webhook.py @@ -5,14 +5,10 @@ import hashlib import os from base64 import b64decode -import time -import http.client -from http import HTTPStatus -import urllib.parse -import errno -import socket +import logging import boto3 +from botocore.exceptions import ClientError ENCRYPTED_GITHUB_APP_WEBHOOK_SECRET = os.environ['GITHUB_APP_WEBHOOK_SECRET'] # Decrypt code should run once and variables stored outside of the function @@ -22,125 +18,76 @@ EncryptionContext={'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']} )['Plaintext'] -# Github access token -ENCRYPTED_GITHUB_ACCESS_TOKEN = os.environ['GITHUB_ACCESS_TOKEN'] -GITHUB_ACCESS_TOKEN = boto3.client('kms').decrypt( - CiphertextBlob=b64decode(ENCRYPTED_GITHUB_ACCESS_TOKEN), - EncryptionContext={'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']} -)['Plaintext'].decode('utf-8') - - -def request(url, method, data="", headers=None): - if headers is None: - headers = {} - headers["User-Agent"] = "kite-action" - headers["Authorization"] = "token " + GITHUB_ACCESS_TOKEN - url_obj = urllib.parse.urlparse(url) - - connected = False - bad_gateway_errors = 0 - while not connected and bad_gateway_errors < 5: - if url_obj.scheme == 'http': - conn = http.client.HTTPConnection(url_obj.netloc) - else: - conn = http.client.HTTPSConnection(url_obj.netloc) - connected = True - - try: - conn.request(method, url_obj.path, data, headers) - response = conn.getresponse() - if response.status == HTTPStatus.BAD_GATEWAY: - bad_gateway_errors += 1 - conn = None - connected = False - time.sleep(bad_gateway_errors * 2) - continue - break - # This happens when GitHub disconnects in python3 - except ConnectionResetError: - if connected: - raise - conn = None - # This happens when GitHub disconnects a keep-alive connection - except http.client.BadStatusLine: - if connected: - raise - conn = None - # This happens when TLS is the source of a disconnection - except socket.error as ex: - if connected or ex.errno != errno.EPIPE: - raise - conn = None - heads = {} - for (header, value) in response.getheaders(): - heads[header.lower()] = value - - return { - "status": response.status, - "reason": response.reason, - "headers": heads, - "data": response.read().decode('utf-8') - } - +ALLOWED_REPOS = ["virt-s1/kite-demo"] def kite_webhook_handler(event, context): headers = event["headers"] body = json.loads(event["body"]) - if headers["x-github-event"] != "check_run": + if body["repository"]["full_name"] not in ALLOWED_REPOS: return { "statusCode": 400, - "body": json.dumps("check_run event only") + "body": json.dumps("This repo is not allowed") } - if body["action"] != "created": + if headers["x-github-event"] != "workflow_job": return { "statusCode": 400, - "body": json.dumps("created action only") + "body": json.dumps("workflow_job event only") } - signature = headers["x-hub-signature"] - if not signature or not signature.startswith("sha1="): + # Totally 3 actions, but only need queue and completed actions in this case + if body["action"] == "in_progress": return { "statusCode": 400, - "body": json.dumps("X-Hub-Signature required") + "body": json.dumps("queued and completed actions only") + } + + signature = headers["x-hub-signature-256"] + if not signature or not signature.startswith("sha256="): + return { + "statusCode": 400, + "body": json.dumps("X-Hub-Signature-256 required") } # Create local hash of payload digest = hmac.new( GITHUB_APP_WEBHOOK_SECRET, event["body"].encode(), - hashlib.sha1 + hashlib.sha256 ).hexdigest() print(digest) - if not hmac.compare_digest(signature, "sha1=" + digest): + if not hmac.compare_digest(signature, "sha256=" + digest): return { "statusCode": 400, "body": json.dumps("Invalid signature") } - response = request("https://api.github.com/orgs/virt-s1/members", "GET") - allowed_users = [x["login"] for x in json.loads(response["data"])] - print(allowed_users) - pr_sender = body["sender"]["login"] - if pr_sender not in allowed_users: - return { - "statusCode": 400, - "body": json.dumps("PR sender is not members of virt-s1 org") - } - message = { 'headers': dict(headers), 'payload': body } - sqs = boto3.resource('sqs', region_name=os.environ['SQS_REGION']) - queue = sqs.get_queue_by_name(QueueName=os.environ['SQS_QUEUE']) - sqs_response = queue.send_message( - MessageBody=json.dumps(message) - ) + # Set up logging + logging.basicConfig(level=logging.DEBUG, + format='%(levelname)s: %(asctime)s: %(message)s') + # Send the SQS message + sqs_client = boto3.client('sqs') + sqs_queue_url = sqs_client.get_queue_url( + QueueName=os.environ['SQS_QUEUE'])['QueueUrl'] + + try: + msg = sqs_client.send_message(QueueUrl=sqs_queue_url, + MessageBody=json.dumps(message)) + except ClientError as e: + logging.error(e) + return { + 'statusCode': 400, + 'body': json.dumps("Send to SQS failed") + } + logging.info(f'Sent SQS message ID: {msg["MessageId"]}') return { 'statusCode': 200, - 'body': json.dumps(f"SQS Message ID: {sqs_response.get('MessageId')}") + 'body': json.dumps(f'Sent SQS message ID: {msg["MessageId"]}') }