From 09b0d41831a4fd3cb1591a7ac0c4324c757ba1ca Mon Sep 17 00:00:00 2001 From: Lewis Thorley Date: Wed, 5 Jun 2024 16:16:29 +0100 Subject: [PATCH 1/7] feat: add new endpoint that uses flags and accountId --- packages/config/src/api/site_info.ts | 32 ++++++++++++++++++++-------- packages/config/src/main.ts | 2 +- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/config/src/api/site_info.ts b/packages/config/src/api/site_info.ts index 93d08df1ee..4caf72e453 100644 --- a/packages/config/src/api/site_info.ts +++ b/packages/config/src/api/site_info.ts @@ -9,8 +9,8 @@ import { ModeOption, TestOptions } from '../types/options.js' type GetSiteInfoOpts = { siteId: string + accountId?: string mode: ModeOption - siteFeatureFlagPrefix: string offline?: boolean api?: NetlifyAPI context?: string @@ -29,27 +29,31 @@ type GetSiteInfoOpts = { export const getSiteInfo = async function ({ api, siteId, + accountId, mode, - siteFeatureFlagPrefix, context, offline = false, testOpts = {}, + featureFlags = {}, }: GetSiteInfoOpts) { const { env: testEnv = false } = testOpts + const useV2Endpoint = !!accountId && featureFlags.cli_integration_installations_meta + if (api === undefined || mode === 'buildbot' || testEnv) { const siteInfo = siteId === undefined ? {} : { id: siteId } - const integrations = mode === 'buildbot' && !offline ? await getIntegrations({ siteId, testOpts, offline }) : [] + const integrations = + mode === 'buildbot' && !offline ? await getIntegrations({ siteId, testOpts, offline, useV2Endpoint }) : [] return { siteInfo, accounts: [], addons: [], integrations } } const promises = [ - getSite(api, siteId, siteFeatureFlagPrefix), + getSite(api, siteId), getAccounts(api), getAddons(api, siteId), - getIntegrations({ siteId, testOpts, offline }), + getIntegrations({ siteId, testOpts, offline, useV2Endpoint }), ] const [siteInfo, accounts, addons, integrations] = await Promise.all(promises) @@ -63,13 +67,13 @@ export const getSiteInfo = async function ({ return { siteInfo, accounts, addons, integrations } } -const getSite = async function (api: NetlifyAPI, siteId: string, siteFeatureFlagPrefix: string | null = null) { +const getSite = async function (api: NetlifyAPI, siteId: string) { if (siteId === undefined) { return {} } try { - const site = await (api as any).getSite({ siteId, feature_flags: siteFeatureFlagPrefix }) + const site = await (api as any).getSite({ siteId }) return { ...site, id: siteId } } catch (error) { throwUserError(`Failed retrieving site data for site ${siteId}: ${error.message}. ${ERROR_CALL_TO_ACTION}`) @@ -100,14 +104,18 @@ const getAddons = async function (api: NetlifyAPI, siteId: string) { type GetIntegrationsOpts = { siteId?: string + accountId?: string testOpts: TestOptions offline: boolean + useV2Endpoint: boolean } const getIntegrations = async function ({ siteId, + accountId, testOpts, offline, + useV2Endpoint, }: GetIntegrationsOpts): Promise { if (!siteId || offline) { return [] @@ -117,13 +125,19 @@ const getIntegrations = async function ({ const baseUrl = new URL(host ? `http://${host}` : `https://api.netlifysdk.com`) + const url = useV2Endpoint + ? `${baseUrl}team/${accountId}/integrations/installations/meta` + : `${baseUrl}site/${siteId}/integrations/safe` + try { - const response = await fetch(`${baseUrl}site/${siteId}/integrations/safe`) + const response = await fetch(url) const integrations = await response.json() return Array.isArray(integrations) ? integrations : [] } catch (error) { - // for now, we'll just ignore errors, as this is early days + // Integrations should not block the build if they fail to load + // TODO: We should consider blocking the build as integrations are a critical part of the build process + // https://linear.app/netlify/issue/CT-1214/implement-strategy-in-builds-to-deal-with-integrations-that-we-fail-to return [] } } diff --git a/packages/config/src/main.ts b/packages/config/src/main.ts index 1971acb574..906a2d8471 100644 --- a/packages/config/src/main.ts +++ b/packages/config/src/main.ts @@ -57,6 +57,7 @@ export const resolveConfig = async function (opts) { base, branch, siteId, + accountId, deployId, buildId, baseRelDir, @@ -72,7 +73,6 @@ export const resolveConfig = async function (opts) { siteId, mode, offline, - siteFeatureFlagPrefix, featureFlags, testOpts, }) From 3dfb890464190389c00a85a26572eeff29be1e48 Mon Sep 17 00:00:00 2001 From: Lewis Thorley Date: Wed, 5 Jun 2024 16:19:57 +0100 Subject: [PATCH 2/7] chore: forgotten prop --- packages/config/src/main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/config/src/main.ts b/packages/config/src/main.ts index 906a2d8471..6678e2ba2f 100644 --- a/packages/config/src/main.ts +++ b/packages/config/src/main.ts @@ -71,6 +71,7 @@ export const resolveConfig = async function (opts) { api, context, siteId, + accountId, mode, offline, featureFlags, From b90954bc6936c12c04dbcb0ef063abac128c4cc5 Mon Sep 17 00:00:00 2001 From: Lewis Thorley Date: Thu, 6 Jun 2024 12:30:11 +0100 Subject: [PATCH 3/7] feat: add test coverage --- packages/config/src/api/site_info.ts | 37 ++++++++++-- packages/config/tests/api/tests.js | 86 ++++++++++++++++++++++++++-- 2 files changed, 114 insertions(+), 9 deletions(-) diff --git a/packages/config/src/api/site_info.ts b/packages/config/src/api/site_info.ts index 4caf72e453..62d45248cd 100644 --- a/packages/config/src/api/site_info.ts +++ b/packages/config/src/api/site_info.ts @@ -40,11 +40,40 @@ export const getSiteInfo = async function ({ const useV2Endpoint = !!accountId && featureFlags.cli_integration_installations_meta + if (useV2Endpoint) { + if (api === undefined || mode === 'buildbot' || testEnv) { + const siteInfo = siteId === undefined ? {} : { id: siteId } + + const integrations = + mode === 'buildbot' && !offline + ? await getIntegrations({ siteId, testOpts, offline, useV2Endpoint, accountId }) + : [] + + return { siteInfo, accounts: [], addons: [], integrations } + } + + const promises = [ + getSite(api, siteId), + getAccounts(api), + getAddons(api, siteId), + getIntegrations({ siteId, testOpts, offline, useV2Endpoint, accountId }), + ] + + const [siteInfo, accounts, addons, integrations] = await Promise.all(promises) + + if (siteInfo.use_envelope) { + const envelope = await getEnvelope({ api, accountId: siteInfo.account_slug, siteId, context }) + + siteInfo.build_settings.env = envelope + } + + return { siteInfo, accounts, addons, integrations } + } + if (api === undefined || mode === 'buildbot' || testEnv) { const siteInfo = siteId === undefined ? {} : { id: siteId } - const integrations = - mode === 'buildbot' && !offline ? await getIntegrations({ siteId, testOpts, offline, useV2Endpoint }) : [] + const integrations = mode === 'buildbot' && !offline ? await getIntegrations({ siteId, testOpts, offline }) : [] return { siteInfo, accounts: [], addons: [], integrations } } @@ -53,7 +82,7 @@ export const getSiteInfo = async function ({ getSite(api, siteId), getAccounts(api), getAddons(api, siteId), - getIntegrations({ siteId, testOpts, offline, useV2Endpoint }), + getIntegrations({ siteId, testOpts, offline }), ] const [siteInfo, accounts, addons, integrations] = await Promise.all(promises) @@ -107,7 +136,7 @@ type GetIntegrationsOpts = { accountId?: string testOpts: TestOptions offline: boolean - useV2Endpoint: boolean + useV2Endpoint?: boolean } const getIntegrations = async function ({ diff --git a/packages/config/tests/api/tests.js b/packages/config/tests/api/tests.js index 61f9a2a113..60f55666a1 100644 --- a/packages/config/tests/api/tests.js +++ b/packages/config/tests/api/tests.js @@ -26,6 +26,17 @@ const SITE_INTEGRATIONS_RESPONSE = { ], } +const TEAM_INSTALLATIONS_META_RESPONSE = { + path: '/team/account1/integrations/installations/meta', + response: [ + { + slug: 'test', + version: 'so-cool', + has_build: true, + }, + ], +} + const SITE_INTEGRATIONS_EMPTY_RESPONSE = { path: '/site/test/integrations/safe', response: [], @@ -307,9 +318,10 @@ test('In integration dev mode, integration specified in config is returned and b t.assert(config.integrations[0].version === undefined) }) -test('Integrations are returned if feature flag is true, mode buildbot', async (t) => { +test('Integrations are not returned if offline', async (t) => { const { output } = await new Fixture('./fixtures/base') .withFlags({ + offline: true, siteId: 'test', mode: 'buildbot', }) @@ -317,6 +329,22 @@ test('Integrations are returned if feature flag is true, mode buildbot', async ( const config = JSON.parse(output) + t.assert(config.integrations) + t.assert(config.integrations.length === 0) +}) + +test('Integrations are returned if feature flag is false and mode is buildbot', async (t) => { + const { output } = await new Fixture('./fixtures/base') + .withFlags({ + siteId: 'test', + mode: 'buildbot', + accountId: 'account1', + token: 'test', + }) + .runConfigServer([SITE_INFO_DATA, SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + + const config = JSON.parse(output) + t.assert(config.integrations) t.assert(config.integrations.length === 1) t.assert(config.integrations[0].slug === 'test') @@ -324,19 +352,67 @@ test('Integrations are returned if feature flag is true, mode buildbot', async ( t.assert(config.integrations[0].has_build === true) }) -test('Integrations are not returned if offline', async (t) => { +test('Integrations are returned if feature flag is false and mode is dev', async (t) => { + const { output } = await new Fixture('./fixtures/base') + .withFlags({ + siteId: 'test', + mode: 'dev', + token: 'test', + }) + .runConfigServer([SITE_INFO_DATA, SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + + const config = JSON.parse(output) + + t.assert(config.integrations) + t.assert(config.integrations.length === 1) + t.assert(config.integrations[0].slug === 'test') + t.assert(config.integrations[0].version === 'so-cool') + t.assert(config.integrations[0].has_build === true) +}) + +// new tests +test('Integrations are returned if flag is true for site and mode is buildbot', async (t) => { const { output } = await new Fixture('./fixtures/base') .withFlags({ - offline: true, siteId: 'test', mode: 'buildbot', + token: 'test', + accountId: 'account1', + featureFlags: { + cli_integration_installations_meta: true, + }, }) - .runConfigServer([SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + .runConfigServer([SITE_INFO_DATA, TEAM_INSTALLATIONS_META_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) const config = JSON.parse(output) t.assert(config.integrations) - t.assert(config.integrations.length === 0) + t.assert(config.integrations.length === 1) + t.assert(config.integrations[0].slug === 'test') + t.assert(config.integrations[0].version === 'so-cool') + t.assert(config.integrations[0].has_build === true) +}) + +test('Integrations are returned if flag is true for site and mode is dev', async (t) => { + const { output } = await new Fixture('./fixtures/base') + .withFlags({ + siteId: 'test', + mode: 'dev', + token: 'test', + accountId: 'account1', + featureFlags: { + cli_integration_installations_meta: true, + }, + }) + .runConfigServer([SITE_INFO_DATA, TEAM_INSTALLATIONS_META_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + + const config = JSON.parse(output) + + t.assert(config.integrations) + t.assert(config.integrations.length === 1) + t.assert(config.integrations[0].slug === 'test') + t.assert(config.integrations[0].version === 'so-cool') + t.assert(config.integrations[0].has_build === true) }) test('baseRelDir is true if build.base is overridden', async (t) => { From 57d1f78c86731ee94a7e5f1e9f411343490485a2 Mon Sep 17 00:00:00 2001 From: Lewis Thorley Date: Thu, 6 Jun 2024 12:32:20 +0100 Subject: [PATCH 4/7] chore: remove comments --- packages/config/tests/api/tests.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/config/tests/api/tests.js b/packages/config/tests/api/tests.js index 60f55666a1..b13d6d920c 100644 --- a/packages/config/tests/api/tests.js +++ b/packages/config/tests/api/tests.js @@ -370,7 +370,6 @@ test('Integrations are returned if feature flag is false and mode is dev', async t.assert(config.integrations[0].has_build === true) }) -// new tests test('Integrations are returned if flag is true for site and mode is buildbot', async (t) => { const { output } = await new Fixture('./fixtures/base') .withFlags({ From 641bf33fe00c0a467055d43067b7eb4a31d227bb Mon Sep 17 00:00:00 2001 From: Lewis Thorley Date: Thu, 6 Jun 2024 12:35:36 +0100 Subject: [PATCH 5/7] chore: remove unused var --- packages/config/src/main.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/config/src/main.ts b/packages/config/src/main.ts index 6678e2ba2f..d3ffeaeb23 100644 --- a/packages/config/src/main.ts +++ b/packages/config/src/main.ts @@ -25,19 +25,8 @@ import { getRedirectsPath, addRedirects } from './redirects.js' * `config` together with related properties such as the `configPath`. */ export const resolveConfig = async function (opts) { - const { - cachedConfig, - cachedConfigPath, - host, - scheme, - packagePath, - pathPrefix, - testOpts, - token, - offline, - siteFeatureFlagPrefix, - ...optsA - } = addDefaultOpts(opts) as $TSFixMe + const { cachedConfig, cachedConfigPath, host, scheme, packagePath, pathPrefix, testOpts, token, offline, ...optsA } = + addDefaultOpts(opts) as $TSFixMe // `api` is not JSON-serializable, so we cannot cache it inside `cachedConfig` const api = getApiClient({ token, offline, host, scheme, pathPrefix, testOpts }) From 3552f7317f56ba0e802a3f97294ff91106b53c42 Mon Sep 17 00:00:00 2001 From: Lewis Thorley Date: Thu, 6 Jun 2024 15:47:19 +0100 Subject: [PATCH 6/7] chore: add accountId to flags --- packages/config/src/bin/flags.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/config/src/bin/flags.ts b/packages/config/src/bin/flags.ts index 388651c3b9..485defa77e 100644 --- a/packages/config/src/bin/flags.ts +++ b/packages/config/src/bin/flags.ts @@ -123,6 +123,10 @@ The NETLIFY_AUTH_TOKEN environment variable can be used as well.`, string: true, describe: `Netlify Site ID.`, }, + accountId: { + string: true, + describe: 'Netlify Account ID. This will only be available in buildbot mode.', + }, context: { string: true, describe: `Build context. From 357d3e2f3cf3b843649c5725854ff2c55952706c Mon Sep 17 00:00:00 2001 From: Lewis Thorley Date: Thu, 6 Jun 2024 16:25:11 +0100 Subject: [PATCH 7/7] chore: update snapshot --- .../config/tests/cli/snapshots/tests.js.md | 2 ++ .../config/tests/cli/snapshots/tests.js.snap | Bin 1902 -> 1914 bytes 2 files changed, 2 insertions(+) diff --git a/packages/config/tests/cli/snapshots/tests.js.md b/packages/config/tests/cli/snapshots/tests.js.md index 08fe267bcb..4e8ed0f0e3 100644 --- a/packages/config/tests/cli/snapshots/tests.js.md +++ b/packages/config/tests/cli/snapshots/tests.js.md @@ -37,6 +37,8 @@ Generated by [AVA](https://avajs.dev). The NETLIFY_AUTH_TOKEN␊ environment variable can be used as well. [string]␊ --siteId Netlify Site ID. [string]␊ + --accountId Netlify Account ID. This will only be available in buildbot␊ + mode. [string]␊ --context Build context.␊ Default: 'production' [string]␊ --branch Repository branch.␊ diff --git a/packages/config/tests/cli/snapshots/tests.js.snap b/packages/config/tests/cli/snapshots/tests.js.snap index c97b653c67dff983f61f41013b4912a211354ea0..db27e8ec204890361f5c34a8dd5eae270534d609 100644 GIT binary patch literal 1914 zcmV-=2Zi`SRzVV!Ma@wZUDdxv}kB=Yk$7eZj$0N$! zQ~BrLpopR{-r4bi21GI(NS`bD$6L?C3LoG5=gzG!Z|Udu-CN&$@z*^Dijwj9j?3AY zOwdW^u;1z&^eUCgi&CizN)VU=VZh=1o zkH(Q}p@#^Q!~-<()EmV2*`{Yv(w}3aRjjru^LF zLZMSQPtKos_ZX`#v0{v=guE7uV*s6ana0Kn#ZQ5yn$J(j`e6&1x-l2p(>^fmX>(xa zDeCL<)#K!t}f<&HhF+(Fjp)^PD8BkhT=R2aH zm740g)jgsBwVIV&rOS><3&gI=L#7ro7j7`+k<7kNBswFMBF^Z!HWkk>p^@!`p;1Vv zH{vRH#8d9Um8@E4pv#$pSIMH?)!I;8ddb?j9|-P+uAaL4o9G+~%v?XI>?XV1RBda2 zj>nX+WHmQAVq75!Q8fQ81ZiSWI-;BPnw&Cvg&+y}v!Mu^$amdPtG3T~SPU^_5x!SA zrkAFe+jpN-9#r%u%_v z_{)^)h0`QLm&a$lteW$8?K#04p3;fNCHZ#=$q=Z+u~!nBC^ z%uHHH5HsRJN_nihJPuj3$7I?k->tUu0ec{liPCCmI3X-kB)|kwQ&@_-$CIG(V3Pq> z)2OzXO?`AkQWnR9sRA}jW_h<;KWOZwha`C@i~~-R*_Z$WfoVA|d9m=<4AT%&i!kGg z-9f9}9JZPTh<}`w+^9G9oaV66+23#Vhh67s0V?*Kqk7Y6wD#+5ZF5%N^(i{&J4gKj zw4|mzqoL^>wmZjLnUz@dVYjvK351XxCeYfot;p-<^ReMgo2&sA7wp~AXR)A<=!$Gey=s(^0 zaaI1H{>&*#M-7&ic)KyIw~LtlwL_SbaJ$o}7okXr5SBF@^{-_i9S+*^L#!^Yfj#G9BFUk)S)1VV7J0*pt~3}p_V`@rHX7-5t6?j7B)(Rxz|n_)`4 zTB)&;`am8B84)?WB2vw%x6y8)F~yTSg1wU=*nc-CSZ$c)SIe&-kYA%EXzA6`>o(|B zFD}cjMYHSbmbK(+$<>nUha}fm4|@ua2qlXAKL2h5oe}+Qqz}p0BAO&^D%7InjYLVC z3N1*Hg-M$VwW-iebXcIYKzYM}^84mIA1U+6lE?4#yc1kzlwRQ4%ONzw5?hQc+arn3 zK4xv7IJSRSZk1xU7-_E$E3cWCw-{6`q+E3f*GQw=r*8mVm7mEyz$Po#Nw>+$B9fKgwkG;#mpWg` z%#SV!c=0=#IS6@_LC99=IuSC%92XO${_XCjQlpM8pH4CJOkbhSk>Y1;r8>d7*pfwq zPZ3?S9=Lhrs>f%+)q<-9R|~EdTrIfX5O9sc+Q-DL#-K~FwR#bcsyU8EemIe_d3|n#Kw7xprEQXFYC-B z)YCypZy$>Y00000000B+oXv0BHWa}7fufst+Ibfum$pM~r-uzgfE^rLNxV66<_|PM z5E!&XSxjWoBdH`_(ZkOBE4qJiJMJ)2lq|`XlQ@;rFZz<0BHw#_eB}G_#m+l-=u`Wg z|MfQ#Sop{-mp$Z9Feh-rJu3L0?;HmTc>L9)#}A%9P_Kti9(?=gpQi)~AG_1CO^J&~ z;G})nYqk%%l}hEbRO%qXFuFhh5(hBEsV{#Q9s&n?6wspr1_h?1QtElAr1}NE0GNXb z!$cqlKn@`IrQ1FLba;kr0Vn}nI5ZHIQmH*rWBFDI0Btl#y}BN~1oMs8zfOjf`UsNK zP~Zh(T#C;Mj2lTX9wN4yz{pRt*C7-h5H$2k#v8!JKB|=RBsP!>rHi;42gwvfYfuq% z?B@;un1l*OSV;FyD93`bDPWWe;9w?Qs@_r-b{ayClGc2AcX*v}84F|-dEwH@Jgf(d zA(Aqt0{UF8yqqA5V`!O&u5-~Au;(S;(!vR}&*2D#%cEX3X^1b?N<${9&1G~Pnl$gv zXf31h;XHCJbmXFN>>x0vgoZ-YP(DGn+88hvu-w8#aYyL|yVou54gSov@Rnh?1z;c} zPxiKUrj|Okx+~j3vr$>pnB$nt!9b1h(?bvsH8s z86>usJnST!NIzOv_`9x;NfIm#3W(gZ9EHj9cPU37@#%!A!y(zkY60X3k!zuls`y>w zbjL@#N(EuY72Ex0t1)Oc3J|}Um0YjY_pHXC-rnDD_68kmrvMdu)={lt z)tmdZma;jk@BS1W^sJ*^0a{Yap3%^-4qNTxwX8}c`mod7w+8)As{jq25EH&tt?Ka7 znYT>f7**n!hm%RJ6~*e`yp`(RTCcl(Jm^_1Yv1a1jt7lqx3=A~itzQTtg1b$R)o~N zZQH6H>=qyzkwL#(>GyWZKP}4d*LJOs#F-j-3#!A`P>&7Ujg1^V%RzrlAydD;eoKaEgO`I!aRVhP((N z#?qNqUyZV9c9wltwYzx{ltK+kg3l2pO)0b>MFuAGaL$xMO)1m>r2)z>1So%K#Pvv?PnIE?YQWWMxs+fqkPU1No+(RvFx_ga)%UNy?81}!iBEUVb$ zm--gKH^$rqd}9<=N)oty{+g*zlFq(ZkuFtg#QeEzij}5VX^NGmSZRutD`@z%E`Fw1 z`GwpAOtEsAbW^M>qFDJ|bELl2sOptOf2R>6C;Cig4niJf5VBdiPlU`c$MpoMz8br# z)Nr6p$74twJ(h_n5cHBPJWjAKvSilbQ$*LS2R=V?Rr538YQS|tivd>yt_ED!Gz9~$ z%cK_su3=jHn7CCNbR)J_uj5e><7h~(%Q@7_aOjU8D~B4KG&pH+a+wN)lLjZ3>NhxP za1t1tG&uSGRnR0!=R@aZ-9z?y@<0F7Kk!mxa=5df;m(~;fIHRaPz`&2PV6a@AeR8j o1#%3B8V+61VmQ=rsNv8xO~G*JGUegET09DVLS^xk5