Skip to content
This repository was archived by the owner on Nov 8, 2022. It is now read-only.

Create a builder image for Azure DevOps #41

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions cicd/azure-devops-agent/README.md
Original file line number Diff line number Diff line change
@@ -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')
```
90 changes: 90 additions & 0 deletions cicd/azure-devops-agent/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
14 changes: 14 additions & 0 deletions cicd/azure-devops-agent/openshift/account.yaml
Original file line number Diff line number Diff line change
@@ -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
79 changes: 79 additions & 0 deletions cicd/azure-devops-agent/openshift/build.yaml
Original file line number Diff line number Diff line change
@@ -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
95 changes: 95 additions & 0 deletions cicd/azure-devops-agent/scripts/start.sh
Original file line number Diff line number Diff line change
@@ -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 <OpenShiftRegistryAddress>] -i <ImageName> -n <OpenShiftProjectNamespace> ]

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 $!