@@ -21,7 +21,7 @@ import type {
2121} from "./trpc.js"
2222import { describeTRPCClientError , getAuthenticatedApiClient } from "./trpc.js"
2323import type { GardenErrorParams } from "../../exceptions.js"
24- import { CloudApiError , GardenError } from "../../exceptions.js"
24+ import { CloudApiError , GardenError , ParameterError } from "../../exceptions.js"
2525import { gardenEnv } from "../../constants.js"
2626import { LogLevel } from "../../logger/logger.js"
2727import { getCloudDistributionName , getCloudLogSectionName } from "../util.js"
@@ -74,7 +74,8 @@ interface GardenCloudApiFactoryParams {
7474 log : Log
7575 cloudDomain : string
7676 globalConfigStore : GlobalConfigStore
77- organizationId : string
77+ organizationId : string | undefined
78+ legacyProjectId : string | undefined
7879 skipLogging ?: boolean
7980 __trpcClientOverrideForTesting ?: ApiTrpcClient
8081}
@@ -135,7 +136,8 @@ export class GardenCloudApi {
135136 static async factory ( {
136137 log,
137138 cloudDomain,
138- organizationId,
139+ organizationId : paramOrganizationId ,
140+ legacyProjectId,
139141 globalConfigStore,
140142 __trpcClientOverrideForTesting,
141143 skipLogging = false ,
@@ -149,9 +151,19 @@ export class GardenCloudApi {
149151
150152 cloudFactoryLog . info ( "Authorizing..." )
151153
154+ let authToken : string | undefined
155+ let organizationId : string | undefined = paramOrganizationId
156+
152157 if ( gardenEnv . GARDEN_AUTH_TOKEN ) {
153- log . silly ( ( ) => "Using auth token from GARDEN_AUTH_TOKEN env var" )
154- if ( ! ( await isTokenValid ( { authToken : gardenEnv . GARDEN_AUTH_TOKEN , cloudDomain, log : cloudLog } ) ) ) {
158+ log . debug ( ( ) => "Using auth token from GARDEN_AUTH_TOKEN env var" )
159+ if (
160+ ! ( await isTokenValid ( {
161+ authToken : gardenEnv . GARDEN_AUTH_TOKEN ,
162+ cloudDomain,
163+ log : cloudLog ,
164+ __trpcClientOverrideForTesting,
165+ } ) )
166+ ) {
155167 throw new CloudApiError ( {
156168 message : deline `
157169 The provided access token is expired or has been revoked for ${ cloudDomain } , please create a new one from the ${ distroName } UI.
@@ -160,40 +172,77 @@ export class GardenCloudApi {
160172 } )
161173 }
162174
163- cloudFactoryLog . info ( styles . success ( successMsg ) )
164- return new GardenCloudApi ( {
165- log : cloudLog ,
166- domain : cloudDomain ,
167- organizationId,
168- globalConfigStore,
169- authToken : gardenEnv . GARDEN_AUTH_TOKEN ,
170- __trpcClientOverrideForTesting,
171- } )
172- }
175+ authToken = gardenEnv . GARDEN_AUTH_TOKEN
176+ } else {
177+ const tokenData = await getStoredAuthToken ( log , globalConfigStore , cloudDomain )
178+ authToken = tokenData ?. token
179+
180+ if ( ! tokenData || ! authToken ) {
181+ log . debug (
182+ `No auth token found, proceeding without access to ${ distroName } . Command results for this command run will not be available in ${ distroName } .`
183+ )
184+ return undefined
185+ }
173186
174- const tokenData = await getStoredAuthToken ( log , globalConfigStore , cloudDomain )
175- let authToken = tokenData ?. token
187+ // Refresh the token if it has expired.
188+ if ( isTokenExpired ( tokenData ) ) {
189+ cloudFactoryLog . debug ( { msg : `Current auth token is expired, attempting to refresh` } )
190+ authToken = (
191+ await refreshAuthTokenAndWriteToConfigStore ( {
192+ log,
193+ globalConfigStore,
194+ cloudDomain,
195+ refreshToken : tokenData . refreshToken ,
196+ __trpcClientOverrideForTesting,
197+ } )
198+ ) . accessToken
199+ }
176200
177- if ( ! tokenData || ! authToken ) {
178- log . debug (
179- `No auth token found, proceeding without access to ${ distroName } . Command results for this command run will not be available in ${ distroName } .`
180- )
181- return undefined
182- }
201+ const tokenValid = await isTokenValid ( { cloudDomain, authToken, log, __trpcClientOverrideForTesting } )
183202
184- // Refresh the token if it has expired.
185- if ( isTokenExpired ( tokenData ) ) {
186- cloudFactoryLog . debug ( { msg : `Current auth token is expired, attempting to refresh` } )
187- authToken = (
188- await refreshAuthTokenAndWriteToConfigStore ( log , globalConfigStore , cloudDomain , tokenData . refreshToken )
189- ) . accessToken
203+ if ( ! tokenValid ) {
204+ log . debug ( { msg : `The stored token was not valid.` } )
205+ return undefined
206+ }
190207 }
191208
192- const tokenValid = await isTokenValid ( { cloudDomain, authToken, log } )
209+ // Resolve organization ID if not provided but legacy project ID is available
210+ if ( ! organizationId && legacyProjectId ) {
211+ cloudFactoryLog . debug ( { msg : `No organization ID provided, attempting to resolve from project ID` } )
212+ try {
213+ organizationId = await GardenCloudApi . getDefaultOrganizationIdForLegacyProject (
214+ cloudDomain ,
215+ authToken ,
216+ legacyProjectId ,
217+ __trpcClientOverrideForTesting
218+ )
219+ if ( organizationId ) {
220+ cloudFactoryLog . debug ( { msg : `Resolved organization ID: ${ organizationId } ` } )
221+ cloudFactoryLog . warn ( {
222+ msg :
223+ dedent `
224+ Organization ID resolved from project ID. Please update your project configuration to specify the organization ID.
225+
226+ Add the following to your project configuration to avoid this message in the future:
227+
228+ ${ styles . command ( `organizationId: ${ organizationId } ` ) }
229+ ` + "\n" ,
230+ } )
231+ } else {
232+ cloudFactoryLog . debug ( { msg : `Could not resolve organization ID from project ID` } )
233+ }
234+ } catch ( error ) {
235+ cloudFactoryLog . warn ( { msg : `Failed to resolve organization ID from project ID: ${ error } ` } )
236+ }
237+ }
193238
194- if ( ! tokenValid ) {
195- log . debug ( { msg : `The stored token was not valid.` } )
196- return undefined
239+ if ( ! organizationId ) {
240+ throw new ParameterError ( {
241+ message : deline `
242+ Could not determine organization ID. Please provide an organizationId in your project configuration
243+ or ensure your project is properly configured in ${ distroName } .
244+ ` ,
245+ } )
197246 }
198247
199248 // Start refresh interval if using JWT
@@ -203,9 +252,13 @@ export class GardenCloudApi {
203252 organizationId,
204253 globalConfigStore,
205254 authToken,
255+ __trpcClientOverrideForTesting,
206256 } )
207- cloudFactoryLog . debug ( { msg : `Starting refresh interval.` } )
208- api . startInterval ( )
257+
258+ if ( ! gardenEnv . GARDEN_AUTH_TOKEN ) {
259+ cloudFactoryLog . debug ( { msg : `Starting refresh interval.` } )
260+ api . startInterval ( )
261+ }
209262
210263 cloudFactoryLog . success ( successMsg )
211264 return api
@@ -244,12 +297,12 @@ export class GardenCloudApi {
244297 const { sub, isAfter } = await import ( "date-fns" )
245298
246299 if ( isAfter ( new Date ( ) , sub ( token . validity , { seconds : refreshThreshold } ) ) ) {
247- const tokenResponse = await refreshAuthTokenAndWriteToConfigStore (
248- this . log ,
249- this . globalConfigStore ,
250- this . domain ,
251- token . refreshToken
252- )
300+ const tokenResponse = await refreshAuthTokenAndWriteToConfigStore ( {
301+ log : this . log ,
302+ globalConfigStore : this . globalConfigStore ,
303+ cloudDomain : this . domain ,
304+ refreshToken : token . refreshToken ,
305+ } )
253306 this . authToken = tokenResponse . accessToken
254307 }
255308 }
@@ -354,7 +407,7 @@ export class GardenCloudApi {
354407 importVariables : ImportVariablesConfig
355408 environmentName : string
356409 log : Log
357- legacyProjectId ? : string | undefined
410+ legacyProjectId : string | undefined
358411 } ) {
359412 log . info ( `Fetching remote variables` )
360413 const variableListIds = await this . getVariableListIds ( importVariables , legacyProjectId , log )
@@ -395,6 +448,28 @@ export class GardenCloudApi {
395448 return variables
396449 }
397450
451+ static async getDefaultOrganizationIdForLegacyProject (
452+ domain : string ,
453+ authToken : string ,
454+ legacyProjectId : string ,
455+ __trpcClientOverrideForTesting ?: ApiTrpcClient
456+ ) : Promise < string | undefined > {
457+ const tokenGetter = ( ) => authToken
458+ const client = __trpcClientOverrideForTesting || getAuthenticatedApiClient ( { hostUrl : domain , tokenGetter } )
459+
460+ try {
461+ const response = await client . organization . legacyGetDefaultOrganization . query ( {
462+ legacyProjectId,
463+ } )
464+ return response . id ?? undefined
465+ } catch ( error ) {
466+ if ( error instanceof TRPCClientError ) {
467+ throw GardenCloudTRPCError . wrapTRPCClientError ( error )
468+ }
469+ throw error
470+ }
471+ }
472+
398473 async getVariableListIds (
399474 importVariables : ImportVariablesConfig ,
400475 legacyProjectId : string | undefined ,
@@ -417,13 +492,13 @@ export class GardenCloudApi {
417492 } )
418493 log . warn ( `No variable lists configured, falling back to default variable list: ${ response . id } ` )
419494 // Write a YAML snippet to help the user configure the variable list
420- log . info ( dedent `
495+ log . warn ( dedent `
421496 To avoid using the default variable list (and suppress this message), you can configure remote variables in your project configuration:
422- ${ styles . highlight (
497+ ${ styles . command (
423498 `
424499 importVariables:
425500 - from: "garden-cloud"
426- list: ${ styles . success ( '"' + response . id + '"' ) }
501+ list: ${ '"' + response . id + '"' }
427502 description: "${ response . description } "
428503 `
429504 ) }
0 commit comments