diff --git a/cicd/azure-devops-agent/README.md b/cicd/azure-devops-agent/README.md new file mode 100644 index 0000000..ebc4480 --- /dev/null +++ b/cicd/azure-devops-agent/README.md @@ -0,0 +1,74 @@ + +# TL;DR + +This builder image runs the Azure DevOps agent to connect to the Azure DevOps control plane for creating and managing builds and deployments. Out of the box, the following build tools are included: + +- .Net Core v2.2 +- OpenJDK v1.8.0 +- node.js v10.13 + +# Warning + +There are bugs in .net core 2.2 that cause authentication negotiation to fail when using Kerberos, NTLM, or Basic Auth. Basic Auth header will work though. Fixes are in .net core 3.0 which, at the time of writing this, is still in beta. Its recommended to use ADFS authentication which aligns with cloud native development. + +# Known Issues + +There is a permissions problem that prevents the agent from updating itself when a new version is available. + +# Builder Image + +Create a builder image. This will be the image that runs the agent and waits for work. The OpenShift Container Platform (OCP) manifest below will build the image and create an image stream to store the newly minted image. While you don't need to change any parameters to get this working you can explore the parameters for customizations. + +```console +oc process -f https://raw.githubusercontent.com/jleach/openshift-components/master/cicd/azure-devops-agent/openshift/build.yaml | oc apply -f - +``` + +# Run + +Its best to create an deployment config with the params you want so that the image will restart properly and be managed by OCP. To do a quick test run you can use the command below. Replace `blabla-tools` with your tools namespace; and use `oc project` to make sure you're in the correct namespace before you run the command. It will use the Jenkins service account to ensure that it has the proper permissions to to act as a builder. + +```console +oc run az-image --image=docker-registry.default.svc:5000/blabla-tools/azure-develop-agent:v2.153.2 --replicas=1 --restart=Never --env="AZ_DEVOPS_ORG_URL=$AZ_DEVOPS_ORG_URL" --env="AZ_DEVOPS_TOKEN=$AZ_DEVOPS_TOKEN" --requests="cpu=1,memory=1Gi" --limits="cpu=1,memory=1Gi" --serviceaccount=jenkins +``` + +Once run, the agent will come on-line and appear in the `default` pool with the name `az-image`. + +# Deploy + +Create your deployment script loosely based on the `Run` example above. There are other options you can pass as environment variables. See the [startup script](https://raw.githubusercontent.com/jleach/openshift-components/master/cicd/azure-devops-agent/scripts/start.sh) for more details. + +You'll need to deploy it using the Jenkens service account or create your own service account with similar privileges so that it can update image streams. + +To trigger an `s2i` build on OCP you can package up the artifacts and inject them into an image build. The `azure-pipelines.yml` example below checks-out the source code; installs the tools (npm); runs the automated tests; and finally packages up the source (excluding node_modules) and injects it into an s2i build that bakes the image: + +``` +# Node.js +# Build a general Node.js project with npm. +# Add steps that analyze code, save build artifacts, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript +# + +trigger: +- master + +# Specify your pool here if the agent is not part of `Default` +# pool: +# vmImage: 'ubuntu-latest' + +steps: +- task: NodeTool@0 + inputs: + versionSpec: '10.x' + displayName: 'Install Node.js' + +- script: | + npm ci + CI=true npm run test + displayName: 'npm install and test' + +- script: | + tar --exclude='./node_modules' -cf artifact.tar . + oc start-build mybuild-api-build --from-archive=artifact.tar --follow --wait + displayName: 'start s2i build' + condition: ne(variables['Build.Reason'], 'PullRequest') +``` \ No newline at end of file diff --git a/cicd/azure-devops-agent/docker/Dockerfile b/cicd/azure-devops-agent/docker/Dockerfile new file mode 100644 index 0000000..2787955 --- /dev/null +++ b/cicd/azure-devops-agent/docker/Dockerfile @@ -0,0 +1,90 @@ +FROM registry.access.redhat.com/openshift3/rhel7:latest + +ARG NODE_MAJOR_VERSION=10 +ARG NODE_VERSION=v10.13.0 +ARG DOTNET_MAJOR_VERSION=2 +ARG DOTNET_VERSION=2.2 +ARG DEVOPS_AGENT_VERSION=2.155.1 +ENV SUMMARY="Azure DevOps agent with .NET" \ + DESCRIPTION="Azure DevOps agent with .NET v${DOTNET_VERSION}, nodejs v${NODE_VERSION}, Agent v${DEVOPS_AGENT_VERSION}" + +LABEL summary="$SUMMARY" \ + description="$DESCRIPTION" \ + io.k8s.description="$DESCRIPTION" \ + io.k8s.display-name="azure-devlops-.net${NODE_MAJOR_VERSION}" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,azure,devops,agent-v${DEVOPS_AGENT_VERSION},.net-v${NODE_MAJOR_VERSION},nodejs-v${NODE_VERSION}" \ + release="1" + +ENV PATH=$HOME/.local/bin/:$PATH \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 + +# microdnf --enablerepo=rhel-7-server-rpms --enablerepo=rhel-server-rhscl-7-rpms --enablerepo=jenkins install which gpg java-1.8.0-openjdk-devel shadow-utils "jenkins-$JENKINS_VERSION" zip unzip bzip2 rsync elfutils rh-git29 --nodocs && \ + +# We need to call 2 (!) yum commands before being able to enable repositories properly +# This is a workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1479388 +RUN yum repolist > /dev/null && \ + yum install -y yum-utils && \ + yum-config-manager --disable \* &> /dev/null && \ + yum-config-manager --enable rhel-server-rhscl-7-rpms && \ + yum-config-manager --enable rhel-7-server-rpms && \ + yum-config-manager --enable rhel-7-server-optional-rpms && \ + yum-config-manager --enable rhel-7-server-dotnet-rpms && \ + yum-config-manager --enable rhel-7-server-ose-3.11-rpms && \ + INSTALL_PKGS="nss_wrapper libtool-ltdl httpd24-libcurl rh-git29 rh-dotnet22 \ + scl-utils atomic-openshift-clients java-1.8.0-openjdk-headless.x86_64" && \ + yum install -y --setopt=tsflags=nodocs $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + # Remove redhat-logos (httpd dependency) to keep image size smaller. + # rpm -e --nodeps redhat-logos && \ + yum clean all -y && \ + rm -rf /var/cache/yum + +# enable the rh-dotnet22 software collection environment +RUN scl enable rh-dotnet22 bash + +# Fetch stock azure client and install. The agent will update itself so +# we set the permission on the agent directory apropriatly. +RUN mkdir -p /opt/az/agent && \ + mkdir -p /opt/az/_work && \ + pushd /opt/az/agent && \ + curl -sL https://vstsagentpackage.azureedge.net/agent/${DEVOPS_AGENT_VERSION}/vsts-agent-rhel.6-x64-${DEVOPS_AGENT_VERSION}.tar.gz | tar -zx && \ + rm -f vsts-agent-rhel.6-x64-${DEVOPS_AGENT_VERSION}.tar.gz && \ + popd && \ + chmod -R 777 /opt/az && \ + chown -R root:root /opt/az/agent + +RUN curl -sL http://download.icu-project.org/files/icu4c/57.1/icu4c-57_1-RHEL6-x64.tgz | \ + tar -zx -C / + +RUN curl -sL https://raw.githubusercontent.com/jleach/openshift-components/master/cicd/azure-devops-agent/scripts/start.sh \ + -o /opt/az/start.sh && \ + chmod +x /opt/az/start.sh + +# Fetch stock nodejs and install +RUN mkdir -p /opt && \ + pushd /opt && \ + curl -sL https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.xz | tar -Jx && \ + rm -f node-${NODE_VERSION}-linux-x64.tar.xz + +# Update environment variables +ENV PATH=$PATH:/opt/rh/rh-dotnet22/root/bin:/opt/rh/rh-git29/root/usr/bin:/opt/az/agent/bin:/usr/local/git/bin:/opt/node-${NODE_VERSION}-linux-x64/bin +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/opt/rh/httpd24/root/usr/lib64 +ENV AZ_DEVOPS_ORG_URL=set_me_to_the_org_url +ENV AZ_DEVOPS_TOKEN=set_me_to_a_pat +ENV HOME=/tmp +ENV NODE_HOME=/opt/node-${NODE_VERSION}-linux-x64 + +# Update the version of `npm` that came with the packages above +# and install a few additional tools. +RUN npm i -g npm@latest yarn@latest nodemon@latest && \ + rm -rf ~/.npm && \ + node -v && \ + npm -v + +WORKDIR /opt/az/_work + +USER 1001 + +CMD ["bash", "-c", "/opt/az/start.sh", "-u", "$AZ_DEVOPS_ORG_URL", "-t", "$AZ_DEVOPS_TOKEN"] diff --git a/cicd/azure-devops-agent/openshift/account.yaml b/cicd/azure-devops-agent/openshift/account.yaml new file mode 100644 index 0000000..3666c80 --- /dev/null +++ b/cicd/azure-devops-agent/openshift/account.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +imagePullSecrets: + - name: jenkins-dockercfg-zww5x +kind: ServiceAccount +metadata: + annotations: + serviceaccounts.openshift.io/oauth-redirectreference.jenkins: >- + {"kind":"OAuthRedirectReference","apiVersion":"v1","reference":{"kind":"Route","name":"jenkins"}} + creationTimestamp: '2017-12-06T00:06:02Z' + labels: + app: jenkins-ephemeral + template: jenkins-ephemeral-template + name: jenkins + namespace: hello-sandbox diff --git a/cicd/azure-devops-agent/openshift/build.yaml b/cicd/azure-devops-agent/openshift/build.yaml new file mode 100644 index 0000000..9b3d30e --- /dev/null +++ b/cicd/azure-devops-agent/openshift/build.yaml @@ -0,0 +1,79 @@ +apiVersion: template.openshift.io/v1 +kind: Template +metadata: + creationTimestamp: null + name: azure-devops-agent +objects: +- apiVersion: v1 + kind: ImageStream + metadata: + creationTimestamp: null + name: ${NAME} + labels: + shared: 'true' + spec: + lookupPolicy: + local: false +- apiVersion: v1 + kind: BuildConfig + metadata: + creationTimestamp: null + name: ${NAME}${SUFFIX} + spec: + output: + to: + kind: ImageStreamTag + name: ${NAME}:${VERSION} + postCommit: {} + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: "1" + memory: 1Gi + runPolicy: SerialLatestOnly + source: + contextDir: ${SOURCE_CONTEXT_DIR} + git: + ref: ${SOURCE_REPOSITORY_REF} + uri: ${SOURCE_REPOSITORY_URL} + type: Git + strategy: + dockerStrategy: + from: + kind: ImageStreamTag + name: 'rhel7:latest' + namespace: openshift + type: Docker + successfulBuildsHistoryLimit: 2 + failedBuildsHistoryLimit: 2 + triggers: + - type: ConfigChange + - imageChange: {} + type: ImageChange +parameters: +- description: A name used for all objects + displayName: Name + name: NAME + required: true + value: azure-develop-agent +- description: A name suffix used for all objects + displayName: Suffix + name: SUFFIX + required: false + value: "" +- description: A version used for the image tags + displayName: version + name: VERSION + required: true + value: v2.153.2 +- name: SOURCE_REPOSITORY_URL + required: true + value: https://github.com/jleach/openshift-components.git +- name: SOURCE_CONTEXT_DIR + required: true + value: cicd/azure-devops-agent/docker +- name: SOURCE_REPOSITORY_REF + required: false + value: master diff --git a/cicd/azure-devops-agent/scripts/start.sh b/cicd/azure-devops-agent/scripts/start.sh new file mode 100755 index 0000000..f94a6d8 --- /dev/null +++ b/cicd/azure-devops-agent/scripts/start.sh @@ -0,0 +1,95 @@ +#! /bin/bash +# +# Copyright © 2019 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Created by Jason Leach on 2019-06-20 +# + +set -Eeo pipefail + +# ================================================================================================================= +# Usage: +# ----------------------------------------------------------------------------------------------------------------- +usage() { + cat <<-EOF + A helper script configure and start the Azure DevOps Agent. + + Usage: ${0} [ -h -x -r ] -i -n ] + + OPTIONS: + ======== + -u The Azure DevOps Organization URL. + Example `https://fullboar.visualstudio.com` + -t The Personal Access Token (PAT) for this Agent. + Example `kd2kdkj2ojldkajdf4jr938jf9edjsdkfjdfj20e` + -n Optional. The name of the Agent. + Defaults to hostname. + Example `d0e46b5cde65` + -p Optional. The name of the pool you would like this + agent to belong to. + Defaults to `default` + + -h prints the usage for the script + -x run the script in debug mode to see what's happening + +EOF +exit +} + +# ----------------------------------------------------------------------------------------------------------------- +# Initialization: +# ----------------------------------------------------------------------------------------------------------------- +while getopts u:t:n:p:hx FLAG; do + case $FLAG in + u ) export AZ_DEVOPS_ORG_URL=$OPTARG ;; + t ) export AZ_DEVOPS_TOKEN=$OPTARG ;; + n ) export AZ_DEVOPS_AGENT_NAME=$OPTARG ;; + p ) export AZ_DEVOPS_POOL=$OPTARG ;; + x ) export DEBUG=1 ;; + h ) usage ;; + \? ) #unrecognized option - show help + echo -e \\n"Invalid script option: -${OPTARG}"\\n + usage + ;; + esac +done + +# Shift the parameters in case there any more to be used +shift $((OPTIND-1)) + +if [ ! -z "${DEBUG}" ]; then + set -x +fi + +if [ -z "${AZ_DEVOPS_ORG_URL}" ] || [ -z "${AZ_DEVOPS_TOKEN}" ]; then + echo -e \\n"Missing parameters. Organization URL and Token are required."\\n + usage +fi + +pushd $(dirname $0)/agent + +source ./env.sh + +./bin/Agent.Listener configure --unattended \ + --agent "${AZ_DEVOPS_AGENT_NAME:-$(cat /proc/sys/kernel/hostname)}" \ + --url "${AZ_DEVOPS_ORG_URL}" \ + --auth PAT \ + --token $AZ_DEVOPS_TOKEN \ + --pool "${AZ_DEVOPS_POOL:-default}"\ + --work "$(dirname $0)/_work" \ + --replace & wait $! + +./bin/Agent.Listener run & wait $! +