@@ -9,12 +9,26 @@ import { devContainerDown, devContainerUp, shellExec } from '../testUtils';
99
1010const pkg = require ( '../../../package.json' ) ;
1111
12+ enum AuthStrategy {
13+ Anonymous ,
14+ DockerConfigAuthFile ,
15+ // PlatformCredentialHelper,
16+ // RefreshToken,
17+ }
18+
1219interface TestPlan {
1320 name : string ;
1421 configName : string ;
1522 testFeatureId : string ;
1623 testCommand ?: string ;
1724 testCommandResult ?: RegExp ;
25+ // Optionally tell the test to set up with a specfic auth strategy.
26+ // If not set, the test will run with anonymous.
27+ // NOTE:
28+ // These will be skipped unless the environment has the relevant 'authCredentialEnvSecret' set in the environment.
29+ useAuthStrategy ?: AuthStrategy ;
30+ // Format: registry|username|passwordOrToken
31+ authCredentialEnvSecret ?: string ;
1832}
1933
2034const defaultTestPlan = {
@@ -32,11 +46,54 @@ const registryCompatibilityTestPlan: TestPlan[] = [
3246 name : 'Anonymous access of GHCR' ,
3347 configName : 'github-anonymous' ,
3448 testFeatureId : 'ghcr.io/devcontainers/feature-starter/color' ,
49+ } ,
50+ // https://learn.microsoft.com/en-us/azure/container-registry/container-registry-repository-scoped-permissions
51+ {
52+ name : 'Authenticated access of Azure Container Registry with registry scoped token' ,
53+ configName : 'azure-registry-scoped' ,
54+ testFeatureId : 'privatedevcontainercli.azurecr.io/features/rabbit' ,
55+ useAuthStrategy : AuthStrategy . DockerConfigAuthFile ,
56+ authCredentialEnvSecret : 'FEATURES_TEST__AZURE_REGISTRY_SCOPED_CREDENTIAL' ,
57+ testCommand : 'rabbit' ,
58+ testCommandResult : / r a b b i t - i s - t h e - b e s t - a n i m a l / ,
3559 }
3660] ;
3761
62+ function constructAuthFromStrategy ( tmpFolder : string , authStrategy : AuthStrategy , authCredentialEnvSecret ?: string ) : string | undefined {
63+ const generateAuthFolder = ( ) => {
64+ const randomChars = Math . random ( ) . toString ( 36 ) . substring ( 2 , 6 ) ;
65+ const tmpAuthFolder = path . join ( tmpFolder , randomChars , 'auth' ) ;
66+ shellExec ( `mkdir -p ${ tmpAuthFolder } ` ) ;
67+ return tmpAuthFolder ;
68+ } ;
69+
70+ switch ( authStrategy ) {
71+ case AuthStrategy . Anonymous :
72+ return ;
73+ case AuthStrategy . DockerConfigAuthFile :
74+ if ( ! authCredentialEnvSecret ) {
75+ return ;
76+ }
77+ const split = process . env [ authCredentialEnvSecret ] ?. split ( '|' ) ;
78+ if ( ! split || split . length !== 3 ) {
79+ return ;
80+ }
81+ const tmpAuthFolder = generateAuthFolder ( ) ;
82+
83+ const registry = split ?. [ 0 ] ;
84+ const username = split ?. [ 1 ] ;
85+ const passwordOrToken = split ?. [ 2 ] ;
86+ const encodedAuth = Buffer . from ( `${ username } :${ passwordOrToken } ` ) . toString ( 'base64' ) ;
87+
88+ shellExec ( `echo '{"auths":{"${ registry } ":{"auth": "${ encodedAuth } "}}}' > ${ tmpAuthFolder } /config.json` ) ;
89+ return tmpAuthFolder ;
90+ default :
91+ return ;
92+ }
93+ }
94+
3895describe ( 'Registry Compatibility' , function ( ) {
39- this . timeout ( '1200s ' ) ;
96+ this . timeout ( '120s ' ) ;
4097 const tmp = path . relative ( process . cwd ( ) , path . join ( __dirname , 'tmp' ) ) ;
4198 const cli = `npx --prefix ${ tmp } devcontainer` ;
4299
@@ -46,14 +103,19 @@ describe('Registry Compatibility', function () {
46103 await shellExec ( `npm --prefix ${ tmp } install devcontainers-cli-${ pkg . version } .tgz` ) ;
47104 } ) ;
48105
49- registryCompatibilityTestPlan . forEach ( ( { name, configName, testFeatureId, testCommand, testCommandResult } ) => {
106+ registryCompatibilityTestPlan . forEach ( ( { name, configName, testFeatureId, testCommand, testCommandResult, useAuthStrategy , authCredentialEnvSecret } ) => {
50107 this . timeout ( '120s' ) ;
51- describe ( name , ( ) => {
52- describe ( 'devcontainer up' , ( ) => {
108+ describe ( name , async function ( ) {
109+ ( ( authCredentialEnvSecret && ! process . env [ authCredentialEnvSecret ] ) ? describe . skip : describe ) ( 'devcontainer up' , async function ( ) {
110+
111+ const authFolder = constructAuthFromStrategy ( tmp , useAuthStrategy ?? AuthStrategy . Anonymous , authCredentialEnvSecret ) ;
112+
53113 let containerId : string | null = null ;
54114 const testFolder = `${ __dirname } /configs/registry-compatibility/${ configName } ` ;
55115
56- before ( async ( ) => containerId = ( await devContainerUp ( cli , testFolder , { 'logLevel' : 'trace' } ) ) . containerId ) ;
116+ before ( async ( ) => containerId = ( await devContainerUp ( cli , testFolder , {
117+ 'logLevel' : 'trace' , prefix : authFolder ? `DOCKER_CONFIG=${ authFolder } ` : undefined
118+ } ) ) . containerId ) ;
57119 after ( async ( ) => await devContainerDown ( { containerId } ) ) ;
58120
59121 const cmd = testCommand ?? defaultTestPlan . testCommand ;
@@ -66,12 +128,20 @@ describe('Registry Compatibility', function () {
66128 } ) ;
67129 } ) ;
68130
69- describe ( `devcontainer features info manifest` , async ( ) => {
70- it ( 'fetches manifest' , async ( ) => {
131+ ( ( authCredentialEnvSecret && ! process . env [ authCredentialEnvSecret ] ) ? describe . skip : describe ) ( `devcontainer features info manifest` , async function ( ) {
132+
133+ const authFolder = constructAuthFromStrategy ( tmp , useAuthStrategy ?? AuthStrategy . Anonymous , authCredentialEnvSecret ) ;
134+
135+ it ( 'fetches manifest' , async function ( ) {
71136 let infoManifestResult : { stdout : string ; stderr : string } | null = null ;
72137 let success = false ;
73138 try {
74- infoManifestResult = await shellExec ( `${ cli } features info manifest ${ testFeatureId } --log-level trace` ) ;
139+ if ( authFolder ) {
140+ infoManifestResult = await shellExec ( `DOCKER_CONFIG=${ authFolder } ${ cli } features info manifest ${ testFeatureId } --log-level trace` ) ;
141+
142+ } else {
143+ infoManifestResult = await shellExec ( `${ cli } features info manifest ${ testFeatureId } --log-level trace` ) ;
144+ }
75145 success = true ;
76146 } catch ( error ) {
77147 assert . fail ( 'features info tags sub-command should not throw' ) ;
0 commit comments