Skip to content
Merged
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
60 changes: 11 additions & 49 deletions packages/config/src/api/site_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type GetSiteInfoOpts = {
offline?: boolean
api?: NetlifyAPI
context?: string
featureFlags?: Record<string, boolean>
testOpts?: TestOptions
}
/**
Expand All @@ -36,49 +37,22 @@ export const getSiteInfo = async function ({
}: GetSiteInfoOpts) {
const { env: testEnv = false } = testOpts

if (api === undefined || testEnv || offline) {
if (api === undefined || mode === 'buildbot' || testEnv) {
const siteInfo = siteId === undefined ? {} : { id: siteId }

return { siteInfo, accounts: [], addons: [], integrations: [] }
}

const siteInfo = await getSite(api, siteId, siteFeatureFlagPrefix)
const featureFlags = siteInfo.feature_flags

const useV2Endpoint = featureFlags?.cli_integration_installations_meta

if (useV2Endpoint) {
const promises = [
getAccounts(api),
getAddons(api, siteId),
getIntegrations({ siteId, testOpts, offline, accountId: siteInfo.account_id, featureFlags }),
]

const [accounts, addons, integrations] = await Promise.all<any[]>(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 (mode === 'buildbot') {
const siteInfo = siteId === undefined ? {} : { id: siteId }

const integrations = await getIntegrations({ siteId, testOpts, offline, featureFlags })
const integrations = mode === 'buildbot' && !offline ? await getIntegrations({ siteId, testOpts, offline }) : []

return { siteInfo, accounts: [], addons: [], integrations }
}

const promises = [
getSite(api, siteId, siteFeatureFlagPrefix),
getAccounts(api),
getAddons(api, siteId),
getIntegrations({ siteId, testOpts, offline, featureFlags }),
getIntegrations({ siteId, testOpts, offline }),
]

const [accounts, addons, integrations] = await Promise.all(promises)
const [siteInfo, accounts, addons, integrations] = await Promise.all(promises)

if (siteInfo.use_envelope) {
const envelope = await getEnvelope({ api, accountId: siteInfo.account_slug, siteId, context })
Expand All @@ -98,7 +72,7 @@ const getSite = async function (api: NetlifyAPI, siteId: string, siteFeatureFlag
const site = await (api as any).getSite({ siteId, feature_flags: siteFeatureFlagPrefix })
return { ...site, id: siteId }
} catch (error) {
return throwUserError(`Failed retrieving site data for site ${siteId}: ${error.message}. ${ERROR_CALL_TO_ACTION}`)
throwUserError(`Failed retrieving site data for site ${siteId}: ${error.message}. ${ERROR_CALL_TO_ACTION}`)
}
}

Expand All @@ -107,7 +81,7 @@ const getAccounts = async function (api: NetlifyAPI) {
const accounts = await (api as any).listAccountsForUser()
return Array.isArray(accounts) ? accounts : []
} catch (error) {
return throwUserError(`Failed retrieving user account: ${error.message}. ${ERROR_CALL_TO_ACTION}`)
throwUserError(`Failed retrieving user account: ${error.message}. ${ERROR_CALL_TO_ACTION}`)
}
}

Expand All @@ -120,24 +94,20 @@ const getAddons = async function (api: NetlifyAPI, siteId: string) {
const addons = await (api as any).listServiceInstancesForSite({ siteId })
return Array.isArray(addons) ? addons : []
} catch (error) {
return throwUserError(`Failed retrieving addons for site ${siteId}: ${error.message}. ${ERROR_CALL_TO_ACTION}`)
throwUserError(`Failed retrieving addons for site ${siteId}: ${error.message}. ${ERROR_CALL_TO_ACTION}`)
}
}

type GetIntegrationsOpts = {
siteId?: string
accountId?: string
testOpts: TestOptions
offline: boolean
featureFlags?: Record<string, boolean>
}

const getIntegrations = async function ({
siteId,
accountId,
testOpts,
offline,
featureFlags,
}: GetIntegrationsOpts): Promise<IntegrationResponse[]> {
if (!siteId || offline) {
return []
Expand All @@ -147,21 +117,13 @@ const getIntegrations = async function ({

const baseUrl = new URL(host ? `http://${host}` : `https://api.netlifysdk.com`)

const useV2Endpoint = featureFlags?.cli_integration_installations_meta

const url = useV2Endpoint
? `${baseUrl}team/${accountId}/integrations/installations/meta`
: `${baseUrl}site/${siteId}/integrations/safe`

try {
const response = await fetch(url)
const response = await fetch(`${baseUrl}site/${siteId}/integrations/safe`)

const integrations = await response.json()
return Array.isArray(integrations) ? integrations : []
} catch (error) {
// 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
// for now, we'll just ignore errors, as this is early days
return []
}
}
2 changes: 1 addition & 1 deletion packages/config/src/error.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// We distinguish between errors thrown intentionally and uncaught exceptions
// (such as bugs) with a `customErrorInfo.type` property.
export const throwUserError = function (messageOrError: string | Error, error?: Error): never {
export const throwUserError = function (messageOrError: string | Error, error?: Error) {
const errorA = getError(messageOrError, error)
errorA[CUSTOM_ERROR_KEY] = { type: USER_ERROR_TYPE }
throw errorA
Expand Down
1 change: 1 addition & 0 deletions packages/config/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const resolveConfig = async function (opts) {
mode,
offline,
siteFeatureFlagPrefix,
featureFlags,
testOpts,
})

Expand Down
114 changes: 5 additions & 109 deletions packages/config/tests/api/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,11 @@ 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: [],
}

const siteInfoWithFeatureFlag = (flag) => {
return {
path: SITE_INFO_PATH,
response: {
ssl_url: 'test',
name: 'test-name',
build_settings: { repo_url: 'test' },
account_id: 'account1',
feature_flags: {
[flag]: true,
},
},
}
}

const SITE_INFO_BUILD_SETTINGS = {
path: SITE_INFO_PATH,
response: {
Expand Down Expand Up @@ -333,114 +307,36 @@ test('In integration dev mode, integration specified in config is returned and b
t.assert(config.integrations[0].version === undefined)
})

test('Integrations are not returned if offline', async (t) => {
test('Integrations are returned if feature flag is true, mode buildbot', async (t) => {
const { output } = await new Fixture('./fixtures/base')
.withFlags({
offline: true,
siteId: 'test',
mode: 'buildbot',
})
.runConfigServer([SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE])

const config = JSON.parse(output)

t.assert(config.integrations)
t.assert(config.integrations.length === 0)
})

test('Integrations are not returned if no api', async (t) => {
const { output } = await new Fixture('./fixtures/base')
.withFlags({
siteId: 'test',
mode: 'buildbot',
})
.runConfigServer([SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE])

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',
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)
})

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) => {
test('Integrations are not returned if offline', async (t) => {
const { output } = await new Fixture('./fixtures/base')
.withFlags({
offline: true,
siteId: 'test',
mode: 'buildbot',
token: 'test',
})
.runConfigServer([
siteInfoWithFeatureFlag('cli_integration_installations_meta'),
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('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',
})
.runConfigServer([
siteInfoWithFeatureFlag('cli_integration_installations_meta'),
TEAM_INSTALLATIONS_META_RESPONSE,
FETCH_INTEGRATIONS_EMPTY_RESPONSE,
])
.runConfigServer([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)
t.assert(config.integrations.length === 0)
})

test('baseRelDir is true if build.base is overridden', async (t) => {
Expand Down