In deze blogpost ga ik het hebben over een technische oplossing waarbij je software onderdelen kunt delen tussen verschillende applicaties. Dit is alleen relevant als je meerdere applicaties hebt en hier onderdelen tussen wil delen.
There is an English version of this blog post
TL;DR; (Samenvatting)
In deze blog koppelen we een private NPM-feed (Node Package Manager) om deze te gebruiken tijdens het ontwikkelen, in een container en in de Continuous Integration (CI) pipelines. Dit doe ik om versies te hebben van componenten. Ik gebruik hier de tools Azure DevOps, NPM, Nodejs en Docker. Om er voor te zorgen dat de authenticatie gebruiken we een Oath2 flow waarbij vlak voor de installatie (preinstall) tokens ophalen. Er is een Github repository aanwezig waar een werkend voorbeeld wordt gegeven
Project wensen
Voor een project waar ik aan werk bestaat de wens naar losse NPM registries. NPM staat voor Node Package Manager. NPM wordt gebruikt om javascript gebaseerde projecten online te her gebruiken. Hiervoor wordt de publieke npmjs.com-feed gebruikt. Als je code wil delen tussen een beperkte set gebruikers of teams kun je private user (project) of organisation-scoped packages publiceren naar het npm registry. User (project) scoped betekent dat deze alleen binnen hetzelfde Azure DevOps project wordt gedeeld en als je organisation-scoped kiest wordt deze binnen de hele organisatie gedeeld. Deze private NPM registries hebben als voordeel dat front-end applicaties eigen ontwikkelde software onderdelen kunnen uitwisselen.
Wat is Azure DevOps
Azure DevOps is een tool voor ontwikkelaars services waarmee teams werk kunnen plannen, samenwerken aan code-ontwikkeling en toepassingen kunnen bouwen en implementeren. Azure DevOps ondersteunt een samenwerkingscultuur en een reeks processen die ontwikkelaars, projectmanagers en bijdragers samenbrengen om software te ontwikkelen. Wij gebruiken de pipeline functionaliteiten het meeste. Dat is het centraal compileren van software en het releasen naar omgevingen.
Azure DevOps Artifacts
Naast de pipelines bestaan er ook Artifacts. Met Azure Artifacts kunnen ontwikkelaars pakketten uit verschillende feeds en openbare registers delen en gebruiken. Pakketten kunnen worden gedeeld binnen hetzelfde team, dezelfde organisatie en zelfs openbaar. Azure Artifacts ondersteunt meerdere pakkettypen, zoals NuGet, npm, Python, Maven en Azure Universal Packages.
Zo ziet een feed er bijvoorbeeld uit:
Docker containers
Voor het project gebruiken we Docker containers om de software te draaien.
Een container is een manier om software en alle dependencies van software bij elkaar te houden en makkelijk uit te wisselen tussen verschillende omgevingen. Een Docker-containerimage is een lichtgewicht, op zichzelf staand, uitvoerbaar softwarepakket dat alles bevat wat nodig is om een applicatie uit te voeren: code, runtime, systeemtools, systeembibliotheken en instellingen.
Azure’s Windows only oplossing
De officiële tool om de authenticatie naar je private NPM feed te regelen is vsts-npm-auth alleen deze werkt alleen op Windows. Aangezien we in ons project een combinatie van Mac, Windows en Docker werken is dit geen werkbare oplossing in onze situatie.
Personal access tokens (PAT’s)
Helaas werken PAT’s niet goed in combinatie met het ophalen van NPM-packages uit de Azure Artifacts feed. Ik krijg hier 401 errors op en kom er niet verder mee.
Een alternatief: better-vsts-npm-auth
Ik ben verder gaan kijken en kwam het project better-vsts-npm-auth tegen. Deze tool doet een oauth2 authenticatie richting Azure DevOps en zorgt ervoor dat je als gebruiker een valide token hebt om npm packages mee op te halen
In de praktijk: Stappen die vooraf nodig zijn
De volgende stappen heb je nodig om zelf een refresh token te generen.
- Voordat je begint: Zorg ervoor dat op je ontwikkelmachine een recente versie van nodejs is geïnstalleerd
- Zorg ervoor dat je toegang hebt tot het Azure DevOps Artifacts gedeelte: https://dev.azure.com/YOUR_ORG/_packaging
Maak een nieuwe feed aan in Azure Devops
Binnen Azure DevOps valt dit onder Artifacts klik dan op “+ Create Feed”
Je kunt hier kiezen uit twee verschillende scopes
- Project, dus alleen het huidige project
- Organization: binnen de huidige Azure DevOps organization
Installeer de global package better-vsts-npm-auth
Op je lokale machine installeer je better-vsts-npm-auth
als globaal pakket zodat deze vanaf verschillende locaties aangeroepen kan worden
Doe dit middels het volgende commando:
npm i -g better-vsts-npm-auth
Setup voor front-end library
Ga naar je front-end library folder en zorg ervoor dat er twee dingen minimaal in staan.
- project
.npmrc
file
De project npmrc bevat de locaties van je eigen private feeds met een prefix van een pakketnaam. Hieronder geef ik een voorbeeld van hoe ik dat toepas. package.json
bestand
Dit bevat de namen van alle dependencies die worden gebruikt.
1. Library Project .npmrc
file
@qdraw-components:registry=https://pkgs.dev.azure.com/qdraw/_packaging/demo/npm/registry/
always-auth=true
2. Library Project package.json file
{
"name": "@qdraw-components/components",
"version": "0.0.1-rc.1",
"description": "Test front-end components",
"scripts": {
},
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^4.5.5"
},
"dependencies": {
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"files": [
"dist"
],
"types": "dist/index.d.ts"
}
Authenticatie vanaf je ontwikkelmachine
De authenticatie verloopt vervolgens via de Azure DevOps aan de hand van de recent geïnstalleerde tool. Dit doe ik binnen de front-end library folder waar ook het bovenstaande package.json en npmrc bestand staan.
better-vsts-npm-auth
Als ik bovenstaande url kopieer in de browser krijg ik het volgende scherm te zien. Druk op Accept om verder te gaan
Wanneer je op Accept klikt krijg je het Succes scherm te zien. Op dit scherm staat een command met een unieke refresh token.
We doen twee dingen met het token: Het command uitvoeren:
better-vsts-npm-auth config set refresh_token eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im9PdmN6NU1fN3AtSGpJS2xGWHo5M3VfVjBabyJ9.eyJuYW1laW
Kopieer het token gedeelte zonder: better-vsts-npm-auth config set refresh_token
. Zet dit als environment variable DEMO_NPM_REFRESH_TOKEN
, hoe je dat doet staat hieronder beschreven. Deze variable hebben we later nodig in Docker.
1. Op Mac OS voeg je het volgende toe aan je .zprofile of .zshrc:
nano ~/.zprofile
export DEMO_NPM_REFRESH_TOKEN="LongTokenHere"
Deze instellingen zijn pas actief na het herstarten van je terminalvenster
2. Op Windows voeg je het volgende toe:
- Druk de volgende toetscombinatie in: Windows + R
- Type: `sysdm.cpl`
- Click op de Advanced tab
- Iets boven OK & Cancel click op Environment Variables…
- Onder System Variable: Press New
- DEMO_NPM_REFRESH_TOKEN
- En de token (without the better-vsts prefix)
- Druk Ok om te bevestigen
Deze instellingen zijn pas actief na het herstarten van je Powershell venster
Controleer of je in de juiste omgeving bent ingelogd
Controleer of de app: “Stateless VSTS NPM OAuth” aanwezig is op: https://dev.azure.com/YOUR_ORG/_usersSettings/authorizations
Staat de app daar niet, dan ben je waarschijnlijk verbonden met de verkeerde organisatie. Als dit het geval is, probeer dan de url te kopiëren en te plakken in een browservenster dat zich in de privé- of incognito modus bevindt.
Bij authenticatie problemen
Mochten er problemen zijn met de authenticatie verwijder dan de volgende bestanden uit je home folder. Op Mac OS zou een voorbeeld dit /Users/dion
kunnen zijn en op Windows C:\Users\Dion
.
- “.vstsnpmauthrc”
- “.npmrc”
- De environment variable “DEMO_NPM_REFRESH_TOKEN”
Beide bestanden bevatten inloggegevens en kunnen veilig worden verwijderd. Probeer het daarna opnieuw. Hieronder toon ik dat de twee bestanden aanwezig zijn
NPM Publish vanuit front-end library folder
Vanuit de front-end library folder doen we een publish naar Azure DevOps. Zorg ervoor dat bij het publiceren elke keer je in de package.json een uniek oplopend versienummer hebt.
Nu staat deze package in de private npm feed en dat ziet er als volgt uit.
Unieke prefix wanneer je meerdere private feeds hebt
In ons geval gebruiken we meerdere npm feeds voor verschillende onderdelen. In het bovenstaande voorbeeld gebruik ik de prefix: “@qdraw-components” en een slash om deze prefix af te sluiten. Het is belangrijk om de naam op deze manier op te bouwen, dan kun je dit namelijk aangeven in het project npmrc bestand. Mocht je er een feed naast hebben voor icons dan kan deze niet dezelfde prefix hebben. De prefix moet dus uniek zijn en beginnen met een apenstaartje.
Docker Applicatie setup
In de applicatie waar de front-end library wordt gebruikt doen we een volgende setup: Hetzelfde. npmrc bestand staat in de applicatiemap.
De project .npmrc file
@qdraw-components:registry=https://pkgs.dev.azure.com/qdraw/_packaging/demo/npm/registry/
always-auth=true
Daarnaast heeft dit project een eigen package.json
package.json
{
"name": "use_app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"preinstall": "node scripts/preinstall.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@qdraw-components/components": "^0.0.1-rc.1"
}
}
In de preinstall wordt op basis van een refresh token een access token opgehaald zodat je private packages opgehaald en geïnstalleerd kunnen worden
Het bestand scripts/preinstall.js is te vinden op Github
In de Dockerfile wordt het bestand scripts/cleanauth.js aangeroepen om de inloggegevens uit de container te verwijderen.
scripts/cleanauth.js
#!/usr/bin/env node
// remove tokens from docker image
const fs = require('fs')
const path = require('path');
function UserProfileFolder() {
let userProfileFolder = "~";
if (process.env.CSIDL_PROFILE) {
userProfileFolder = process.env.CSIDL_PROFILE;
}
if (process.env.HOME) {
userProfileFolder = process.env.HOME;
}
return userProfileFolder;
}
const userProfileFolder = UserProfileFolder();
const vstsNpmauthRcFilePath = path.join(userProfileFolder, '.vstsnpmauthrc');
if (fs.existsSync(vstsNpmauthRcFilePath)) {
fs.unlinkSync(vstsNpmauthRcFilePath)
}
const userNpmRcFilePath = path.join(userProfileFolder, '.npmrc');
if (fs.existsSync(userNpmRcFilePath)) {
fs.unlinkSync(userNpmRcFilePath)
}
Dit project heeft de volgende Dockerfile
Dockerfile
# Install dependencies only when needed
FROM node:16-alpine AS deps
ARG YOURPROJECT_NPM_REFRESH_TOKEN=default
ARG AZURE_AUTH_TOKEN=default
RUN apk add --no-cache libc6-compat
WORKDIR /app
ENV YOURPROJECT_NPM_REFRESH_TOKEN=$YOURPROJECT_NPM_REFRESH_TOKEN
ENV AZURE_AUTH_TOKEN=$AZURE_AUTH_TOKEN
COPY use_app/scripts/preinstall.js ./scripts/preinstall.js
COPY use_app/scripts/cleanauth.js ./scripts/cleanauth.js
COPY use_app/package.json ./
RUN node scripts/preinstall.js
RUN npm ci --prefer-offline
RUN node scripts/cleanauth.js
Deze Dockerfile kan op twee manieren worden aangeroepen zodat dit een docker container wordt.
1. Docker build command
cd root_of_solution
docker build -f use_app/Dockerfile . --no-cache --build-arg DEMO_NPM_REFRESH_TOKEN=${DEMO_NPM_REFRESH_TOKEN}
2. Of als Docker compose
docker-compose.yml
version: '3.4'
services:
demo.use.app:
image: ${DOCKER_REGISTRY-}demouseapp
build:
context: .
dockerfile: use_app/Dockerfile
args:
- DEMO_NPM_REFRESH_TOKEN=${DEMO_NPM_REFRESH_TOKEN}
environment:
- DEMO=true
ports:
- "19443:9443"
Docker draait op een lokale omgeving
Azure DevOps Pipeline
Om Azure DevOps te configureren gebruik je yaml files, hierin staan alle stappen welke de build pipeline gaat uitvoeren
Zie hiervoor: azure/azure-pipelines.yml en azure/templates/build-docker.yml
Als je een organisation-scoped packages uit de Azure Artifact gebruikt dan krijg je de volgende foutmelding:
“npm ERR! 403 403 Forbidden – GET https://pkgs.dev.azure.com/YOURENV/_packaging/demo/npm/registry/@qdraw-components%2fcomponents – User d9af02c4-b000-4fd4-9754-68706813d7ca”
Om deze te verhelpen voeg je de build user toe aan de private feed
In de onderstaande schermafbeelding zie je hoe ik dat doe.
Build succesvol
Conclusie
Het was een interessante zoektocht naar de mogelijkheden van Azure DevOps en dan met name hoe ze dit onderdeel in elkaar hebben gestoken. Omdat er zo weinig van op internet te vinden was heb ik veel zelf uitgezocht en in deze blog heb ik getracht om mijn kennis zo goed mogelijk uit te schrijven.
En het pakketje is succesvol bezorgd…
Fout senario: Verbonden met de verkeerde tenant
Als je de volgende melding ziet in de preinstall.js: You are probably connected to the WRONG tenant
Ga dan naar de switcher in Azure Devops https://app.vsaex.visualstudio.com/me en selecteer aan de linkerkant het juiste bedrijf. Ga vervolgens weer terug naar oauth provider, dit is in de meeste gevallen: stateless-vsts-oauth.azurewebsites.net om een nieuw refresh token op te halen. Plaats deze weer als environment variablen en herstart het venster om het daarna opnieuw te proberen
Optionele stap: Zelf hosten van de stateless-vsts-oauth provider
Mocht je de stateless-vsts-oauth zelf willen hosten bijvoorbeeld niet afhankelijk te zijn van: stateless-vsts-oauth.azurewebsites.net dan is de code te vinden op:
github.com/zumwald/stateless-vsts-oauth
Hiervoor dien je een App te registeren op Azure Devops. Ga naar Azure Devops Register oauth app
Vul hier je bedrijfsnaam en contactgegevens in. Bij Application Information vul je bij Application website het adres waar de app gehost wordt. Dit adres moet publiek beschikbaar zijn. Verder de Authorization callback URL is het zelfde adres met als toevoeging /oauth-callback
. De Authorized scopes zijn Packaging (read and Write). Verder hoef je niks aan te vinken. Klik op Create Application om verder te gaan. De App ID en de Client Secret ben je nodig in volgende stappen. Om later bij dit overzicht te komen ga je naar https://app.vsaex.visualstudio.com/me
Gebruik het volgende format om de environment variablen te zetten
CLIENT_ID : YOUR_CLIENT_ID_GUID
CLIENT_SECRET : eyJ0eXAi....
PORT : 8080
WEBSITE_HOSTNAME : devopsauth.example.com
De App ID wordt ook gebruikt in de preinstall.js en het adres waar de oauth provider is gehost. Belangrijk is dat je deze hier ook in veranderd