Skip to content
Merged
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
30 changes: 30 additions & 0 deletions .tekton/build-service-prod-overlay-update.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: build-service-prod-overlay-update
namespace: rhtap-promotion-staging
annotations:
pipelinesascode.tekton.dev/on-cel-expression: |
event == "push" && target_branch == "main" && "components/build-service/staging/base/kustomization.yaml".pathChanged()
Comment thread
mmorhun marked this conversation as resolved.
pipelinesascode.tekton.dev/max-keep-runs: "5"
pipelinesascode.tekton.dev/task: "[.tekton/tasks/promote-component.yaml]"
spec:
pipelineSpec:
tasks:
- name: promote-component-from-stage-to-prod
params:
- name: BRANCH_NAME
value: stage-to-prod-build
- name: COMPONENT_NAME
value: build-service
- name: SCRIPT
value: |
STAGE_SHA=$(grep -oE '[0-9a-f]{40}' components/build-service/staging/base/kustomization.yaml -m1)
OLD_PROD_SHA=$(grep -oE '[0-9a-f]{40}' components/build-service/production/base/kustomization.yaml -m1)
echo "Changing prod sha from ${OLD_PROD_SHA} to ${STAGE_SHA}"
sed -i "s/$OLD_PROD_SHA/$STAGE_SHA/g" components/build-service/production/base/kustomization.yaml
sed -i "s/$OLD_PROD_SHA/$STAGE_SHA/g" components/monitoring/grafana/base/dashboards/build-service/kustomization.yaml
taskRef:
name: promote-component


29 changes: 29 additions & 0 deletions .tekton/image-controller-prod-overlay-update.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: image-controller-prod-overlay-update
namespace: rhtap-promotion-staging
annotations:
pipelinesascode.tekton.dev/on-cel-expression: |
event == "push" && target_branch == "main" && "components/image-controller/staging/kustomization.yaml".pathChanged()
pipelinesascode.tekton.dev/max-keep-runs: "5"
pipelinesascode.tekton.dev/task: "[.tekton/tasks/promote-component.yaml]"
spec:
pipelineSpec:
tasks:
- name: promote-component-from-stage-to-prod
params:
- name: BRANCH_NAME
value: stage-to-prod-image-controller
- name: COMPONENT_NAME
value: image-controller
- name: SCRIPT
value: |
STAGE_SHA=$(grep -oE '[0-9a-f]{40}' components/image-controller/staging/kustomization.yaml -m1)
OLD_PROD_SHA=$(grep -oE '[0-9a-f]{40}' components/image-controller/production/kustomization.yaml -m1)
echo "Changing prod sha from ${OLD_PROD_SHA} to ${STAGE_SHA}"
sed -i "s/$OLD_PROD_SHA/$STAGE_SHA/g" components/image-controller/production/kustomization.yaml
taskRef:
name: promote-component


263 changes: 263 additions & 0 deletions .tekton/tasks/promote-component.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
apiVersion: tekton.dev/v1
kind: Task
metadata:
labels:
app.kubernetes.io/version: "0.1"
annotations:
tekton.dev/pipelines.minVersion: "0.12.1"
tekton.dev/tags: "appstudio, hacbs"
name: promote-component
spec:
description: |
Clones redhat-appstudio/infra-deployments repository, runs script in 'SCRIPT' parameter, generates pull-request for redhat-appstudio/infra-deployments repository.
params:
- name: SCRIPT
description: Bash script for changing the infra-deployments
- name: BRANCH_NAME
description: Source branch in infra-deployment repo
- name: COMPONENT_NAME
description: Component to promote
- name: GH_REPO
description: GitHub repository of the infra-deployments code
default: redhat-appstudio/infra-deployments
- name: IMAGE
description: Image reference containing git and other utility required for executing the SCRIPT
# this image is built using https://github.com/redhat-appstudio/build-tasks-dockerfiles/blob/main/update-infra-deployments-task-scripts-image/Dockerfile
default: quay.io/redhat-appstudio/update-infra-deployments-task-script-image@sha256:2748f1a4f1af4e35214745aed4e56a9d06f6bdbd30572e7ade13729e67f23cc9
- name: shared-secret
default: infra-deployments-pr-creator
description: secret in the namespace which contains private key for the GitHub App
- name: GITHUB_APP_ID
description: ID of Github app used for updating PR
default: "305606"
- name: GITHUB_APP_INSTALLATION_ID
description: Installation ID of Github app in the organization
default: "35269675"
volumes:
- name: infra-deployments-pr-creator
secret:
# 'private-key' - private key for Github app
secretName: $(params.shared-secret)
- name: shared-dir
emptyDir: {}

steps:
- name: git-clone-infra-deployments
image: $(params.IMAGE)
volumeMounts:
- name: shared-dir
mountPath: /shared
workingDir: /shared
env:
- name: GH_REPO
value: "$(params.GH_REPO)"
script: |
git clone "https://github.com/${GH_REPO}.git" cloned
- name: run-update-script
image: $(params.IMAGE)
volumeMounts:
- name: shared-dir
mountPath: /shared
workingDir: /shared
env:
- name: SCRIPT
value: $(params.SCRIPT)
script: |
cd cloned
echo "$SCRIPT" | sh
- name: get-diff-files
image: $(params.IMAGE)
volumeMounts:
- name: shared-dir
mountPath: /shared
workingDir: /shared
script: |
cd cloned
git status -s --porcelain | cut -c4- > ../updated_files.txt
# Based on https://github.com/tektoncd/catalog/tree/main/task/github-app-token/0.2/
- name: create-mr
image: quay.io/redhat-appstudio/github-app-token@sha256:b4f2af12e9beea68055995ccdbdb86cfe1be97688c618117e5da2243dc1da18e
# per https://kubernetes.io/docs/concepts/containers/images/#imagepullpolicy-defaulting
# the cluster will set imagePullPolicy to IfNotPresent
# also per direction from Ralph Bean, we want to use image digest based tags to use a cue to automation like dependabot or renovatebot to periodially submit pull requests that update the digest as new images are released.
volumeMounts:
- name: infra-deployments-pr-creator
mountPath: /secrets/deploy-key
- name: shared-dir
mountPath: /shared
workingDir: /shared
env:
- name: GITHUBAPP_KEY_PATH
value: /secrets/deploy-key/private-key
- name: GITHUBAPP_APP_ID
value: "$(params.GITHUB_APP_ID)"
- name: GITHUBAPP_INSTALLATION_ID
value: "$(params.GITHUB_APP_INSTALLATION_ID)"
- name: GITHUB_API_URL
value: https://api.github.com
- name: BRANCH_NAME
value: $(params.BRANCH_NAME)
- name: GH_REPO
value: "$(params.GH_REPO)"
- name: COMPONENT_NAME
value: "$(params.COMPONENT_NAME)"
script: |
#!/usr/bin/env python3
import json
import logging
import os
import time
import base64

import requests
from jwcrypto import jwk, jwt

logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(name)s:%(levelname)s:%(message)s")
logger = logging.getLogger("updater")

EXPIRE_MINUTES_AS_SECONDS = int(os.environ.get('GITHUBAPP_TOKEN_EXPIRATION_MINUTES', 10)) * 60
# TODO support github enteprise
GITHUB_API_URL = os.environ["GITHUB_API_URL"]

BRANCH_NAME = os.environ["BRANCH_NAME"]
GH_REPO = os.environ["GH_REPO"]
COMPONENT_NAME = os.environ["COMPONENT_NAME"]


class GitHub():
token = None

def __init__(self, private_key, app_id=None, installation_id=None):
if not isinstance(private_key, bytes):
raise ValueError(f'"{private_key}" parameter must be byte-string')
self._private_key = private_key
self.app_id = app_id
self.token = self._get_token(installation_id)

def _load_private_key(self, pem_key_bytes):
return jwk.JWK.from_pem(pem_key_bytes)

def _app_token(self, expire_in=EXPIRE_MINUTES_AS_SECONDS):
key = self._load_private_key(self._private_key)
now = int(time.time())
token = jwt.JWT(
header={"alg": "RS256"},
claims={
"iat": now,
"exp": now + expire_in,
"iss": self.app_id
},
algs=["RS256"],
)
token.make_signed_token(key)
return token.serialize()

def _get_token(self, installation_id=None):
app_token = self._app_token()
if not installation_id:
return app_token

req = self._request(
"POST",
f"/app/installations/{installation_id}/access_tokens",
headers={
"Authorization": f"Bearer {app_token}",
"Accept": "application/vnd.github.machine-man-preview+json"
})

ret = req.json()
if 'token' not in ret:
raise Exception(f"Authentication errors: {ret}")

return ret['token']

def _request(self, method, url, headers={}, data={}):
if self.token and 'Authorization' not in headers:
headers.update({"Authorization": "Bearer " + self.token})
if not url.startswith("http"):
url = f"{GITHUB_API_URL}{url}"
return requests.request(method,
url,
headers=headers,
data=json.dumps(data))

def create_mr(self, title, description):
logger.info("Create/update pull request, head: %s, base: main", BRANCH_NAME)
req = self._request(
"POST",
f"/repos/{GH_REPO}/pulls",
headers={
"Authorization": f"Bearer {self.token}",
"Accept": "application/vnd.github.v3+json"
},
data={
"head": BRANCH_NAME,
"base": "main",
"title": title,
"body": description,
"maintainer_can_modify": False
})
json_output = req.json()
print(json_output)
return json_output

def create_reset_branch(self):
target_branch = self._request("GET", f"/repos/{GH_REPO}/git/refs/heads/{BRANCH_NAME}").json()
main_branch_sha = self._request("GET", f"/repos/{GH_REPO}/git/refs/heads/main").json()['object']['sha']
if "ref" in target_branch:
logger.info("Update branch %s", BRANCH_NAME)
self._request(
"PATCH",
f"/repos/{GH_REPO}/git/refs/heads/{BRANCH_NAME}",
data={"sha": main_branch_sha, "force": True}
)
else:
logger.info("Create branch %s", BRANCH_NAME)
self._request(
"POST",
f"/repos/{GH_REPO}/git/refs",
data={"sha": main_branch_sha, "ref": f"refs/heads/{BRANCH_NAME}"}
)

def upload_content(self):
for file in open('updated_files.txt').readlines():
file = file.strip()
with open(f"cloned/{file}", "rb") as fd:
encoded_string = base64.b64encode(fd.read()).decode("utf-8")
old_sha = self._request("GET", f'/repos/{GH_REPO}/contents/{file}').json().get("sha")
if old_sha is None:
logger.info("Upload a new file %s", file)
self._request("PUT", f'/repos/{GH_REPO}/contents/{file}', data={"message": f"update {file}", "branch": BRANCH_NAME, "content": encoded_string})
else:
logger.info("Update file %s", file)
self._request("PUT", f'/repos/{GH_REPO}/contents/{file}', data={"message": f"update {file}", "branch": BRANCH_NAME, "content": encoded_string, "sha": old_sha})


def main():
with open(os.environ['GITHUBAPP_KEY_PATH'], 'rb') as key_file:
key = key_file.read()

if os.environ.get('GITHUBAPP_APP_ID'):
app_id = os.environ['GITHUBAPP_APP_ID']
else:
raise Exception("application id is not set")

print(f"Getting user token for application_id: {app_id}")
github_app = GitHub(
key,
app_id=app_id,
installation_id=os.environ.get('GITHUBAPP_INSTALLATION_ID'))

github_app.create_reset_branch()
github_app.upload_content()
title = f"Promoting component {COMPONENT_NAME} from stage to prod"
description = f"Promote from stage to production"
github_app.create_mr(title, description)

if __name__ == '__main__':
main()

workspaces:
- name: artifacts
description: Workspace containing arbitrary artifacts used during the task run.
optional: true
8 changes: 0 additions & 8 deletions components/build-service/base/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
resources:
- allow-argocd-to-manage.yaml
- https://github.com/redhat-appstudio/build-service/config/default?ref=45e8fe7c3b036d1df3eee11c1d83e0633e451c07
- build-pipeline-selectors/build-pipeline-selector.yaml
- monitoring.yaml
- rbac
Expand All @@ -11,12 +10,5 @@ resources:
commonAnnotations:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true

namespace: build-service

images:
- name: quay.io/redhat-appstudio/build-service
newName: quay.io/redhat-appstudio/build-service
newTag: 45e8fe7c3b036d1df3eee11c1d83e0633e451c07

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
11 changes: 11 additions & 0 deletions components/build-service/development/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base
- https://github.com/redhat-appstudio/build-service/config/default?ref=45e8fe7c3b036d1df3eee11c1d83e0633e451c07

namespace: build-service
Comment thread
chmeliik marked this conversation as resolved.
Outdated

images:
- name: quay.io/redhat-appstudio/build-service
newName: quay.io/redhat-appstudio/build-service
newTag: 45e8fe7c3b036d1df3eee11c1d83e0633e451c07

commonAnnotations:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
Comment thread
chmeliik marked this conversation as resolved.
Outdated

patches:
- target:
Expand Down
12 changes: 12 additions & 0 deletions components/build-service/production/base/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ kind: Kustomization
resources:
- ../../base
- ../../base/external-secrets
- https://github.com/redhat-appstudio/build-service/config/default?ref=45e8fe7c3b036d1df3eee11c1d83e0633e451c07

namespace: build-service

images:
- name: quay.io/redhat-appstudio/build-service
newName: quay.io/redhat-appstudio/build-service
newTag: 45e8fe7c3b036d1df3eee11c1d83e0633e451c07

commonAnnotations:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true

patches:
- path: pipelines-as-code-secret-patch.yaml
target:
Expand Down
11 changes: 11 additions & 0 deletions components/build-service/staging/base/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,14 @@ kind: Kustomization
resources:
Comment thread
chmeliik marked this conversation as resolved.
Outdated
- ../../base
- ../../base/external-secrets
- https://github.com/redhat-appstudio/build-service/config/default?ref=45e8fe7c3b036d1df3eee11c1d83e0633e451c07

namespace: build-service

images:
- name: quay.io/redhat-appstudio/build-service
newName: quay.io/redhat-appstudio/build-service
newTag: 45e8fe7c3b036d1df3eee11c1d83e0633e451c07

commonAnnotations:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing new line

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the pr got merged now.. will address this in a future PR

Loading