Skip to content

Commit

Permalink
feat(platform): added id and domain to projects
Browse files Browse the repository at this point in the history
This replaces the temporary setup we had based on project names (using
ids instead) and the GARDEN_CLOUD env variable.
  • Loading branch information
thsig committed Apr 24, 2020
1 parent a5c1ea3 commit dc6f27b
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 39 deletions.
4 changes: 2 additions & 2 deletions garden-service/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ export class GardenCli {
garden = await Garden.factory(root, contextOpts)
}

if (garden.clientAuthToken && garden.platformUrl) {
bufferedEventStream.connect(garden.events, garden.clientAuthToken, garden.platformUrl, garden.projectName)
if (garden.clientAuthToken && garden.cloudDomain && garden.projectId) {
bufferedEventStream.connect(garden.events, garden.clientAuthToken, garden.cloudDomain, garden.projectId)
}

// Register log file writers. We need to do this after the Garden class is initialised because
Expand Down
18 changes: 9 additions & 9 deletions garden-service/src/cloud/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ export const makeAuthHeader = (clientAuthToken: string) => ({ "x-access-auth-tok
/**
* Logs in to the platform if needed, and returns a valid client auth token.
*/
export async function login(platformUrl: string, log: LogEntry): Promise<string> {
export async function login(cloudDomain: string, log: LogEntry): Promise<string> {
const savedToken = await readAuthToken(log)

// Ping platform with saved token (if it exists)
if (savedToken) {
log.debug("Local client auth token found, verifying it with platform...")
if (await checkClientAuthToken(savedToken, platformUrl, log)) {
if (await checkClientAuthToken(savedToken, cloudDomain, log)) {
log.debug("Local client token is valid, no need for login.")
return savedToken
}
Expand All @@ -43,7 +43,7 @@ export async function login(platformUrl: string, log: LogEntry): Promise<string>
* the redirect and finish running.
*/
const events = new EventEmitter2()
const server = new AuthRedirectServer(platformUrl, events, log)
const server = new AuthRedirectServer(cloudDomain, events, log)
log.debug(`Redirecting to platform login page...`)
const newToken: string = await new Promise(async (resolve, _reject) => {
// The server resolves the promise with the new auth token once it's received the redirect.
Expand All @@ -61,12 +61,12 @@ export async function login(platformUrl: string, log: LogEntry): Promise<string>
/**
* Checks with the backend whether the provided client auth token is valid.
*/
export async function checkClientAuthToken(token: string, platformUrl: string, log: LogEntry): Promise<boolean> {
export async function checkClientAuthToken(token: string, cloudDomain: string, log: LogEntry): Promise<boolean> {
let valid
try {
await got({
method: "get",
url: `${platformUrl}/token/verify`,
url: `${cloudDomain}/token/verify`,
headers: makeAuthHeader(token),
})
valid = true
Expand Down Expand Up @@ -146,11 +146,11 @@ export class AuthRedirectServer {
private log: LogEntry
private server: Server
private app: Koa
private platformUrl: string
private cloudDomain: string
private events: EventEmitter2

constructor(platformUrl: string, events: EventEmitter2, log: LogEntry, public port?: number) {
this.platformUrl = platformUrl
constructor(cloudDomain: string, events: EventEmitter2, log: LogEntry, public port?: number) {
this.cloudDomain = cloudDomain
this.events = events
this.log = log.placeholder()
}
Expand All @@ -167,7 +167,7 @@ export class AuthRedirectServer {
await this.createApp()

const query = { cliport: `${this.port}` }
await open(`${this.platformUrl}/cli/login/?${qs.stringify(query)}`)
await open(`${this.cloudDomain}/cli/login/?${qs.stringify(query)}`)
}

async close() {
Expand Down
19 changes: 9 additions & 10 deletions garden-service/src/cloud/buffered-event-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export class BufferedEventStream {
private log: LogEntry
private eventBus: EventBus
public sessionId: string
private platformUrl: string
private cloudDomain: string
private clientAuthToken: string
private projectName: string
private projectId: string

/**
* We maintain this map to facilitate unsubscribing from a previously connected event bus
Expand All @@ -80,11 +80,10 @@ export class BufferedEventStream {
this.bufferedLogEntries = []
}

// TODO: Replace projectName with projectId once we've figured out the flow for that.
connect(eventBus: EventBus, clientAuthToken: string, platformUrl: string, projectName: string) {
connect(eventBus: EventBus, clientAuthToken: string, cloudDomain: string, projectId: string) {
this.clientAuthToken = clientAuthToken
this.platformUrl = platformUrl
this.projectName = projectName
this.cloudDomain = cloudDomain
this.projectId = projectId

if (!this.intervalId) {
this.startInterval()
Expand Down Expand Up @@ -150,10 +149,10 @@ export class BufferedEventStream {
const data = {
events,
sessionId: this.sessionId,
projectName: this.projectName,
projectId: this.projectId,
}
const headers = makeAuthHeader(this.clientAuthToken)
got.post(`${this.platformUrl}/events`, { json: data, headers }).catch((err) => {
got.post(`${this.cloudDomain}/events`, { json: data, headers }).catch((err) => {
this.log.error(err)
})
}
Expand All @@ -162,10 +161,10 @@ export class BufferedEventStream {
const data = {
logEntries,
sessionId: this.sessionId,
projectName: this.projectName,
projectId: this.projectId,
}
const headers = makeAuthHeader(this.clientAuthToken)
got.post(`${this.platformUrl}/log-entries`, { json: data, headers }).catch((err) => {
got.post(`${this.cloudDomain}/log-entries`, { json: data, headers }).catch((err) => {
this.log.error(err)
})
}
Expand Down
4 changes: 2 additions & 2 deletions garden-service/src/commands/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export class LoginCommand extends Command {
async action({ garden, log, headerLog }: CommandParams): Promise<CommandResult> {
printHeader(headerLog, "Login", "cloud")

if (garden.platformUrl) {
await login(garden.platformUrl, log)
if (garden.cloudDomain) {
await login(garden.cloudDomain, log)
}

return {}
Expand Down
15 changes: 13 additions & 2 deletions garden-service/src/config/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ export interface ProjectConfig {
kind: "Project"
name: string
path: string
id?: string
domain?: string
configPath?: string
defaultEnvironment: string
dotIgnoreFiles: string[]
Expand Down Expand Up @@ -251,8 +253,7 @@ const projectOutputSchema = () =>
.description(
dedent`
The value for the output. Must be a primitive (string, number, boolean or null). May also be any valid template
string.
`
string.`
)
.example("${modules.my-module.outputs.some-output}"),
})
Expand All @@ -277,6 +278,16 @@ export const projectDocsSchema = () =>
.meta({ internal: true })
.description("The path to the project config file."),
name: projectNameSchema(),
// TODO: Refer to platform documentation for more details.
id: joi
.string()
.meta({ internal: true })
.description("The project's ID in Garden Cloud."),
// TODO: Refer to platform documentation for more details.
domain: joi
.string()
.meta({ internal: true })
.description("The domain to use for cloud features. Should point to the API/backend hostname."),
// Note: We provide a different schema below for actual validation, but need to define it this way for docs
// because joi.alternatives() isn't handled well in the doc generation.
environments: joi
Expand Down
48 changes: 34 additions & 14 deletions garden-service/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Module, getModuleCacheContext, getModuleKey, ModuleConfigMap, moduleFro
import { pluginModuleSchema, ModuleTypeMap } from "./types/plugin/plugin"
import { SourceConfig, ProjectConfig, resolveProjectConfig, pickEnvironment, OutputSpec } from "./config/project"
import { findByName, pickKeys, getPackageVersion, getNames, findByNames } from "./util/util"
import { ConfigurationError, PluginError, RuntimeError, InternalError } from "./exceptions"
import { ConfigurationError, PluginError, RuntimeError } from "./exceptions"
import { VcsHandler, ModuleVersion } from "./vcs/vcs"
import { GitHandler } from "./vcs/git"
import { BuildDir } from "./build-dir"
Expand Down Expand Up @@ -111,7 +111,8 @@ export interface GardenParams {
moduleExcludePatterns?: string[]
opts: GardenOpts
outputs: OutputSpec[]
platformUrl: string | null
projectId: string | null
cloudDomain: string | null
plugins: RegisterPluginParam[]
production: boolean
projectName: string
Expand Down Expand Up @@ -140,7 +141,8 @@ export class Garden {

// Platform-related instance variables
public clientAuthToken: string | null
public platformUrl: string | null
public projectId: string | null
public cloudDomain: string | null
public sessionId: string | null

public readonly configStore: ConfigStore
Expand Down Expand Up @@ -174,7 +176,7 @@ export class Garden {
constructor(params: GardenParams) {
this.buildDir = params.buildDir
this.clientAuthToken = params.clientAuthToken
this.platformUrl = params.platformUrl
this.cloudDomain = params.cloudDomain
this.sessionId = params.sessionId
this.environmentName = params.environmentName
this.gardenDirPath = params.gardenDirPath
Expand Down Expand Up @@ -287,20 +289,37 @@ export class Garden {

const sessionId = opts.sessionId || null

// TODO: Read the platformUrl from config.
const platformUrl = process.env.GARDEN_CLOUD ? `http://${process.env.GARDEN_CLOUD}` : null
const { id: projectId, domain: cloudDomain } = config

const clientAuthToken = await readAuthToken(log)
// If a client auth token exists in local storage, we assume that the user wants to be logged in to the platform.
if (clientAuthToken && !opts.noPlatform) {
if (!platformUrl) {
const errMsg = deline`
GARDEN_CLOUD environment variable is not set. Make sure it is set to the appropriate API
backend endpoint (e.g. myusername-cloud-api.cloud.dev.garden.io, without an http/https
prefix).`
throw new InternalError(errMsg, {})
if (!cloudDomain || !projectId) {
const errorMessages: string[] = []
if (!cloudDomain) {
errorMessages.push(deline`
${chalk.bold("project.domain")} is not set in your project-level ${chalk.bold("garden.yml")}. Make sure it
is set to the appropriate API backend endpoint (e.g. myusername-cloud-api.cloud.dev.garden.io, without an
http/https prefix).
`)
}
if (!projectId) {
errorMessages.push(deline`
${chalk.bold("project.id")} is not set in your project-level ${chalk.bold("garden.yml")}. Please visit
Garden Cloud's web UI for your project and copy your project's ID from there.
`)
}
if (errorMessages.length > 0) {
throw new ConfigurationError(
dedent`
${errorMessages.join("\n\n")}
Logging out via the ${chalk.bold("garden logout")} command will suppress this message.`,
{}
)
}
} else {
const tokenIsValid = await checkClientAuthToken(clientAuthToken, platformUrl, log)
const tokenIsValid = await checkClientAuthToken(clientAuthToken, cloudDomain, log)
if (!tokenIsValid) {
log.warn(deline`
You were previously logged in to the platform, but your session has expired or is invalid. Please run
Expand All @@ -315,7 +334,8 @@ export class Garden {
artifactsPath,
clientAuthToken,
sessionId,
platformUrl,
cloudDomain: cloudDomain || null,
projectId: projectId || null,
projectRoot,
projectName,
environmentName,
Expand Down

0 comments on commit dc6f27b

Please sign in to comment.