-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create Tekton task and Pipeline for Tekton bundles
This adds `tkn-bundle` Tekton Task that simply finds all *.yaml or *.yml files in the current `params.CONTEXT` directory and creates a Tekton Bundle by pushing it to `params.IMAGE`. The result of the task are `IMAGE_URL` image reference with digest (no tag) and `IMAGE_DIGEST` containing the image digest. Also adds `tekton-bundle-builder` builder pipeline that uses the `tkn-bundle` task for the `build` step. Tests are provided. Ref. https://issues.redhat.com/browse/HACBS-1675
- Loading branch information
Showing
16 changed files
with
345 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
quay.io/zregvart_redhat/pipeline-docker-build:2023-01-27-105259@sha256:7cbf6db4ec0e9869a12c32dab7e44b3d5d2e24ad30dcf73edb4a0fec812cea78 | ||
quay.io/zregvart_redhat/pipeline-hacbs-docker-build:2023-01-27-105259@sha256:bc651ab32e9d82e72ef48313f67ea3a4c554b693127edef2be71e6c286dfc627 | ||
quay.io/zregvart_redhat/pipeline-enterprise-contract:2023-01-27-105259@sha256:8ab81616d92fadc65cec43fef6bcdb23dfdd2ccb68efea1d1a1e263bf4833cd1 | ||
quay.io/zregvart_redhat/pipeline-fbc-builder:2023-01-27-105259@sha256:c44ea2b5c2db24e0536aea2248641b9566abe6859ef617735337195592eb9395 | ||
quay.io/zregvart_redhat/pipeline-java-builder:2023-01-27-105259@sha256:b1c99b671377215aa8b0bea5669127b8bc29db5f628bf3b92d3fbca5046fe680 | ||
quay.io/zregvart_redhat/pipeline-nodejs-builder:2023-01-27-105259@sha256:9a70882f8ab7dc796bf2aebf05c2f6874fb8a216f61e3f5f1b8e9daba1c6f60f | ||
quay.io/zregvart_redhat/pipeline-tekton-bundle-builder:2023-01-27-105259@sha256:d1befd9a00e8d9bcd704e24acdf34643459f6cc472598469e3ab3c6b60626674 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ resources: | |
- nodejs-builder | ||
- enterprise-contract.yaml | ||
- fbc-builder | ||
- tekton-bundle-builder |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
apiVersion: kustomize.config.k8s.io/v1beta1 | ||
kind: Kustomization | ||
resources: | ||
- ../template-build | ||
|
||
patches: | ||
# Use the template-build as a template replacing the Pipeline name and the | ||
# `build-container` step's task reference | ||
- patch: |- | ||
- op: replace | ||
path: /metadata/name | ||
value: tekton-bundle-builder | ||
- op: replace | ||
path: /spec/tasks/4/taskRef | ||
value: | ||
name: tkn-bundle | ||
version: "0.1" | ||
target: | ||
kind: Pipeline | ||
name: template-build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
quay.io/zregvart_redhat/task-buildah:0.1@sha256:a3d239d08534b0a00232a19409f9840cfda9913f885d3e5bc46be7b085fe0924 | ||
quay.io/zregvart_redhat/task-clair-scan:0.1@sha256:1bdce0c26366d092d02b1049fc0a2d02ec0a8662b33851b426197baee3d6cfae | ||
quay.io/zregvart_redhat/task-clamav-scan:0.1@sha256:7dd9db4ad055fa3537290fd982599907744f282ee06a2a87445ce1d3a10f160e | ||
quay.io/zregvart_redhat/task-cleanup-build-directories:0.1@sha256:55862c6f2492ed771e0bbda4d408bf040beb5670659d41ae181f18194004866c | ||
quay.io/zregvart_redhat/task-configure-build:0.1@sha256:6a6626fa72e1ec615e28f43665770a06be3b5b2db819772680f2054f7372a83f | ||
quay.io/zregvart_redhat/task-deprecated-image-check:0.1@sha256:f0e0a4af62ba639767a8fcf76f86554247cb98ad86c10270b7629ab13fe63478 | ||
quay.io/zregvart_redhat/task-fbc-validation:0.1@sha256:89894035aa4221402ac31b006f198e31f191c495057bdded23547746e257ccf2 | ||
quay.io/zregvart_redhat/task-git-clone:0.1@sha256:01aaab92ba115b45460de5c09cf91b3acbd53cbb3d5b58a77d6ec5b97188c45e | ||
quay.io/zregvart_redhat/task-init:0.1@sha256:34947be27fd8b83e2fd829e40da1f85504d85213f4ec6d94a40e25973b313650 | ||
quay.io/zregvart_redhat/task-prefetch-dependencies:0.1@sha256:58eee789cfe5e015164c5ce1988b9006e2dd40f13315986eb8ae0dd0bed51b7f | ||
quay.io/zregvart_redhat/task-s2i-java:0.1@sha256:87df051e8d704637522b665d3f222b01fef624b50642fa3d59ce56c1464607c4 | ||
quay.io/zregvart_redhat/task-s2i-nodejs:0.1@sha256:34c9a798a2e8094b1fc02b13e36fd8f71a5d10fd300ec2ef0acc77dc126cc423 | ||
quay.io/zregvart_redhat/task-sanity-inspect-image:0.1@sha256:dee638b481dd4825685c5d05b1d007a08d36c119cc15b62e7ba7125ba19886b4 | ||
quay.io/zregvart_redhat/task-sanity-label-check:0.1@sha256:2f5fa96ec08d1623bc6404c67738e1764c6e5c1c5f79f0a3705d6ba6879f7b02 | ||
quay.io/zregvart_redhat/task-sast-go:0.1@sha256:3e5e11ac564b81159af4a5e32bcc48c300346c0725056bba3246aadf5fa07383 | ||
quay.io/zregvart_redhat/task-sast-java-sec-check:0.1@sha256:e302565b411f04d31ff4e112d28d16ba6fb9fe7fce4d0ce9dfcbeb5178e8dc93 | ||
quay.io/zregvart_redhat/task-sast-snyk-check:0.1@sha256:5ddb96aac456948eda3f87fd74554390ead6bff4cf6e2f6840957ea991dcf98b | ||
quay.io/zregvart_redhat/task-sbom-json-check:0.1@sha256:ca7011f6bc3f11f638742bd455af64b7c1eeeb748710b196673e26a0cc0d94a2 | ||
quay.io/zregvart_redhat/task-summary:0.1@sha256:a1ffa3d26220ac3c9c491b813f0df0c4f19ae09e173f430cbbcd9e77cfaabe8f | ||
quay.io/zregvart_redhat/task-tkn-bundle:0.1@sha256:b6b563492d27df10da2936b5e1bab793b5e5df8f0be56bcad9143acc9de988c2 | ||
quay.io/zregvart_redhat/task-update-infra-deployments:0.1@sha256:f96509ced66af8f04f85fa819602366aef09ba55cffbc4f4f1f1bed618f30daa | ||
quay.io/zregvart_redhat/task-utils-task:0.1@sha256:e4a5795d3f39222ded6f9158b620a6d5f73073368cd3fa2b4db3491970a8ac88 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
--require spec_helper |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# tkn-bundle - Tekton Task to push a Tekton Bundle to an image registry | ||
|
||
Tekton Task to build and push Tekton Bundles (OCI images) which contain | ||
definitions of Tekton objects, most commonly Task and Pipeline objects. | ||
|
||
Task finds all `*.yaml` or `*.yml` files within `CONTEXT`, packages and pushes | ||
them as a Tekton Bundle to the image repository, name and tag specified by the | ||
`IMAGE` parameter. | ||
|
||
## Input Parameters | ||
|
||
The task supports the following input parameters. | ||
|
||
| Name | Example | Description | | ||
|---------|-------------------------|------------------------------------------| | ||
| IMAGE | registry.io/my-task:tag | Reference of the image task will produce | | ||
| CONTEXT | my-task/0.1 | Path to the directory to use as context | | ||
|
||
## Results | ||
|
||
The task emits the following results. | ||
|
||
| Name | Example | Description | | ||
|--------------|-----------------------------------|---------------------------------------------------| | ||
| IMAGE_URL | registry.io/my-task@sha256-abc... | Image repository where the built image was pushed | | ||
| IMAGE_DIGEST | abc... | Digest of the image just built | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Testing tkn-bundle Tekton Task | ||
|
||
Make sure you have shellspec installed[1]. The test setup script will bring up a | ||
kind[2] cluster and installs Tekton Pipeline. The source is provided via the | ||
`source-pvc` PersistantVolumeClaim and prepopulated with the test?.y*ml files in | ||
order to not necesate the need for source checkout. | ||
|
||
For second and subsequent invocations the setup is quicker as it only applies | ||
any changes to already started and setup cluster. To delete the cluster and | ||
start afresh run: `kind delete cluster --name=test-tkn-bundle`. | ||
|
||
To run the tests run `shellspec` from this directory. | ||
|
||
## Example of a testing setup and session | ||
|
||
```shell | ||
$ pwd | ||
.../build-definitions/task/tkn-bundle/0.1 | ||
$ shellspec | ||
Running: /bin/sh [bash 5.2.15(1)-release] | ||
namespace/tekton-pipelines created | ||
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-controller-cluster-access created | ||
... | ||
pod "setup-1674815473" deleted | ||
deployment.apps/registry created | ||
service/registry created | ||
deployment.apps/registry condition met | ||
deployment.apps/tekton-pipelines-controller condition met | ||
deployment.apps/tekton-pipelines-webhook condition met | ||
.. | ||
|
||
Finished in 119.59 seconds (user 7.37 seconds, sys 4.03 seconds) | ||
2 examples, 0 failures | ||
``` | ||
|
||
[1] https://shellspec.info/ | ||
[2] https://kind.sigs.k8s.io/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#!/bin/env bash | ||
|
||
set -o errexit | ||
set -o pipefail | ||
set -o nounset | ||
|
||
spec_helper_configure() { | ||
import 'support/task_run_subject' | ||
import 'support/jq_matcher' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
#!/bin/env bash | ||
|
||
set -o errexit | ||
set -o pipefail | ||
set -o nounset | ||
|
||
shellspec_syntax 'shellspec_matcher_jq' | ||
|
||
shellspec_matcher_jq() { | ||
shellspec_matcher__match() { | ||
SHELLSPEC_EXPECT="$1" | ||
[ "${SHELLSPEC_SUBJECT+x}" ] || return 1 | ||
echo "${SHELLSPEC_SUBJECT}" | jq --exit-status "${SHELLSPEC_EXPECT}" > /dev/null || return 1 | ||
return 0 | ||
} | ||
|
||
# Message when the matcher fails with "should" | ||
shellspec_matcher__failure_message() { | ||
shellspec_putsn "expected: JSON $1 should evaluate with success against jq expression: $2" | ||
} | ||
|
||
# Message when the matcher fails with "should not" | ||
shellspec_matcher__failure_message_when_negated() { | ||
shellspec_putsn "expected: JSON $1 should not evaluate with success against jq expression: $2" | ||
} | ||
|
||
# checking for parameter count | ||
shellspec_syntax_param count [ $# -eq 1 ] || return 0 | ||
shellspec_matcher_do_match "$@" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#!/bin/env bash | ||
|
||
set -o errexit | ||
set -o pipefail | ||
set -o nounset | ||
|
||
shellspec_syntax 'shellspec_subject_taskrun' | ||
|
||
shellspec_subject_taskrun() { | ||
# shellcheck disable=SC2034 | ||
SHELLSPEC_META='text' | ||
shellspec_readfile_once SHELLSPEC_STDOUT "$SHELLSPEC_STDOUT_FILE" | ||
if [ ${SHELLSPEC_STDOUT+x} ]; then | ||
# shellcheck disable=SC2034 | ||
LINES=(${SHELLSPEC_STDOUT[@]}) | ||
TASK_RUN_NAME="${LINES[2]}" # "TaskRun(0) started:(1) tkn-bundle-run-ndjfb(2) | ||
SHELLSPEC_SUBJECT="$(tkn tr describe "${TASK_RUN_NAME}" -o json)" | ||
shellspec_chomp SHELLSPEC_SUBJECT | ||
else | ||
unset SHELLSPEC_SUBJECT ||: | ||
fi | ||
|
||
shellspec_off UNHANDLED_STDOUT | ||
|
||
eval shellspec_syntax_dispatch modifier ${1+'"$@"'} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
apiVersion: tekton.dev/v1beta1 | ||
kind: Task | ||
metadata: | ||
name: test1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
apiVersion: tekton.dev/v1beta1 | ||
kind: Task | ||
metadata: | ||
name: test2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
apiVersion: tekton.dev/v1beta1 | ||
kind: Task | ||
metadata: | ||
name: test3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
#!/bin/env bash | ||
|
||
set -o errexit | ||
set -o pipefail | ||
set -o nounset | ||
|
||
eval "$(shellspec - -c) exit 1" | ||
|
||
Describe "tkn-bundle task" | ||
setup() { | ||
# Create Kind cluster if it doesn't exist already | ||
CLUSTER_NAME="test-tkn-bundle" | ||
kind get clusters -q | grep -q "${CLUSTER_NAME}" || kind create cluster -q --name="${CLUSTER_NAME}" | ||
|
||
# Install Tekton Pipeline, proceed with the rest of the test of the setup | ||
kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/previous/v0.42.0/release.yaml | ||
|
||
# Create the test namespace | ||
kubectl create namespace test --dry-run=client -o yaml | kubectl apply -f - | ||
kubectl config set-context --current --namespace=test | ||
while ! kubectl get serviceaccount default 2> /dev/null | ||
do | ||
sleep 1 | ||
done | ||
|
||
# Create the tkn-bundle Task | ||
kubectl apply -f tkn-bundle.yaml | ||
|
||
# Copy the task's YAML file to the persistent volume mounted as source for | ||
# running the task via a setup Pod | ||
|
||
# Create the PersistentVolumeClaim | ||
echo 'apiVersion: v1 | ||
kind: PersistentVolumeClaim | ||
metadata: | ||
name: source-pvc | ||
spec: | ||
storageClassName: standard | ||
accessModes: | ||
- ReadWriteOnce | ||
resources: | ||
requests: | ||
storage: 1Mi | ||
' | kubectl apply -f - | ||
|
||
# Image to run the setup pod with, taken from the Task definition | ||
IMAGE="$(kubectl get task tkn-bundle -o=jsonpath='{.spec.steps[0].image}')" | ||
# Semi-random name for the setup Pod | ||
SETUP_POD="setup-$(date +%s)" | ||
# Run the pod with the volume mounted at /source, the container is blocking | ||
# until "/stop" file is created | ||
kubectl run "${SETUP_POD}" \ | ||
--restart=Never \ | ||
--image="${IMAGE}" \ | ||
--override-type=json \ | ||
--overrides='[ | ||
{"op":"add","path":"/spec/containers/0/volumeMounts","value":[{"name":"source","mountPath":"/source"}]}, | ||
{"op":"add","path":"/spec/volumes","value":[{"name":"source","persistentVolumeClaim":{"claimName":"source-pvc"}}]}]' \ | ||
--command -- bash -c 'while [ ! -f /stop ]; do sleep 1; done' | ||
# Wait for the Pod to be ready | ||
kubectl wait --for=condition=Ready "pod/${SETUP_POD}" --timeout=3m | ||
# Clean the volume before proceeding | ||
kubectl exec "${SETUP_POD}" -- sh -c 'rm -rf /source/*' | ||
|
||
# Copy the files over | ||
kubectl cp spec/test1.yaml "${SETUP_POD}:/source/" | ||
kubectl cp spec/test2.yml "${SETUP_POD}:/source/" | ||
kubectl exec "${SETUP_POD}" -- mkdir -p /source/sub | ||
kubectl cp spec/test3.yaml "${SETUP_POD}:/source/sub/" | ||
|
||
# Trigger the termination and delete the Pod | ||
kubectl exec "${SETUP_POD}" -- touch /stop | ||
kubectl delete pod "${SETUP_POD}" | ||
|
||
# Deploy an image registry and expose it via a Service | ||
kubectl create deployment registry --image=docker.io/registry:2.8.1 --port=5000 --dry-run=client -o yaml | kubectl apply -f - | ||
kubectl create service clusterip registry --tcp=5000:5000 --dry-run=client -o yaml | kubectl apply -f - | ||
kubectl wait deployment registry --for=condition=Available --timeout=3m | ||
|
||
# Finally wait for Tekton Pipeline to be available | ||
kubectl -n tekton-pipelines wait deployment -l "app.kubernetes.io/part-of=tekton-pipelines" --for=condition=Available --timeout=3m | ||
} | ||
BeforeAll 'setup' | ||
|
||
It 'creates Tekton bundles' | ||
When call tkn task start tkn-bundle -p IMAGE=registry:5000/bundle:tag --use-param-defaults --timeout 1m --showlog -w name=source,claimName=source-pvc | ||
The output should include 'Added Task: test1 to image' | ||
The output should include 'Added Task: test2 to image' | ||
The output should include 'Added Task: test3 to image' | ||
The output should include 'Pushed Tekton Bundle to registry:5000/bundle' | ||
The taskrun should jq '.status.taskResults[] | select(.name=="IMAGE_DIGEST").value | test("sha256:[a-z0-9]+")' | ||
The taskrun should jq '.status.taskResults[] | select(.name=="IMAGE_URL").value | test("registry:5000/bundle@sha256:[a-z0-9]+")' | ||
End | ||
|
||
It 'creates Tekton bundles from specific context' | ||
When call tkn task start tkn-bundle -p IMAGE=registry:5000/sub:tag -p CONTEXT=sub --timeout 1m --showlog -w name=source,claimName=source-pvc | ||
The output should not include 'Added Task: test1 to image' | ||
The output should not include 'Added Task: test2 to image' | ||
The output should include 'Added Task: test3 to image' | ||
The output should include 'Pushed Tekton Bundle to registry:5000/sub' | ||
The taskrun should jq '.status.taskResults[] | select(.name=="IMAGE_DIGEST").value | test("sha256:[a-z0-9]+")' | ||
The taskrun should jq '.status.taskResults[] | select(.name=="IMAGE_URL").value | test("registry:5000/sub@sha256:[a-z0-9]+")' | ||
End | ||
End |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
apiVersion: tekton.dev/v1beta1 | ||
kind: Task | ||
metadata: | ||
labels: | ||
app.kubernetes.io/version: "0.1" | ||
annotations: | ||
tekton.dev/pipelines.minVersion: "0.12.1" | ||
tekton.dev/tags: "image-build, appstudio, hacbs" | ||
name: tkn-bundle | ||
spec: | ||
description: |- | ||
Creates and pushes a Tekton bundle containing the specified Tekton YAML files. | ||
params: | ||
- description: Reference of the image task will produce. | ||
name: IMAGE | ||
type: string | ||
- default: . | ||
description: Path to the directory to use as context. | ||
name: CONTEXT | ||
type: string | ||
results: | ||
- description: Digest of the image just built | ||
name: IMAGE_DIGEST | ||
- description: Image repository where the built image was pushed | ||
name: IMAGE_URL | ||
steps: | ||
- image: quay.io/redhat-appstudio/appstudio-utils:4580b3ba3012095ff3981e50b6bbf753d4afd4c3 | ||
name: build | ||
script: | | ||
#!/bin/env bash | ||
set -o errexit | ||
set -o pipefail | ||
set -o nounset | ||
readarray FILES < <(find $(params.CONTEXT) \( -name '*.yaml' -o -name '*.yml' \)) | ||
[[ ${#FILES[@]} -eq 0 ]] \ | ||
&& echo 'No YAML files found in "$(params.CONTEXT)", aborting the build' \ | ||
&& exit 1 | ||
exec 3>&1; | ||
OUT="$(tkn bundle push "$(params.IMAGE)" \ | ||
$(printf ' -f %s' "${FILES[@]}") \ | ||
|tee /proc/self/fd/3)" | ||
echo "${OUT#*Pushed Tekton Bundle to }" > $(results.IMAGE_URL.path) | ||
echo "${OUT#*Pushed Tekton Bundle to *@}" > $(results.IMAGE_DIGEST.path) | ||
workingDir: $(workspaces.source.path) | ||
workspaces: | ||
- name: source |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
AppStudio/HACBS Team |