Skip to content

Commit

Permalink
improvement: using a new release endpoint for self-update (#5229)
Browse files Browse the repository at this point in the history
* improvement: using a new release endpoint for self-update

* test: fixed self-update tests to mock the release endpoint

* docs: architecture flag in self-update command

* improvement: better error handling when retrieving releases

* test: releases endpoint failure and clarifying comments on desired version

---------

Co-authored-by: Anna Mager <78752267+twelvemo@users.noreply.github.com>
  • Loading branch information
mkhq and twelvemo committed Oct 13, 2023
1 parent d189d76 commit 33cfebc
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 27 deletions.
4 changes: 2 additions & 2 deletions core/src/cli/cli.ts
Expand Up @@ -345,8 +345,8 @@ ${renderCommands(commands)}

// Note: No reason to await the check
checkForUpdates(garden.globalConfigStore, log).catch((err) => {
log.verbose("Something went wrong while checking for the latest Garden version.")
log.verbose(err)
log.verbose(`Something went wrong while checking for the latest Garden version.`)
log.verbose(err.toString())
})

await checkForStaticDir()
Expand Down
4 changes: 2 additions & 2 deletions core/src/cli/helpers.ts
Expand Up @@ -22,7 +22,7 @@ import { ParameterValues, Parameter, ParameterObject, globalDisplayOptions } fro
import { GardenError, ParameterError, RuntimeError, toGardenError } from "../exceptions"
import { getPackageVersion, removeSlice } from "../util/util"
import { Log } from "../logger/log-entry"
import { STATIC_DIR, VERSION_CHECK_URL, gardenEnv, ERROR_LOG_FILENAME } from "../constants"
import { STATIC_DIR, gardenEnv, ERROR_LOG_FILENAME } from "../constants"
import { printWarningMessage } from "../logger/util"
import { GlobalConfigStore } from "../config-store/global"
import { got } from "../util/http"
Expand Down Expand Up @@ -95,7 +95,7 @@ export async function checkForUpdates(config: GlobalConfigStore, logger: Log) {
headers["X-ci-name"] = ci.name
}

const res = await got(`${VERSION_CHECK_URL}?${qs.stringify(query)}`, { headers }).json<any>()
const res = await got(`${gardenEnv.GARDEN_VERSION_CHECK_ENDPOINT}?${qs.stringify(query)}`, { headers }).json<any>()
const versionCheck = await config.get("versionCheck")
const showMessage = versionCheck && moment().subtract(1, "days").isAfter(moment(versionCheck.lastRun))

Expand Down
69 changes: 52 additions & 17 deletions core/src/commands/self-update.ts
Expand Up @@ -20,8 +20,10 @@ import { createReadStream, createWriteStream } from "fs"
import { copy, mkdirp, move, readdir, remove } from "fs-extra"
import { GotHttpError, got } from "../util/http"
import { promisify } from "node:util"
import { gardenEnv } from "../constants"
import semver from "semver"
import stream from "stream"
import { Log } from "../logger/log-entry"

const ARM64_INTRODUCTION_VERSION = "0.13.12"

Expand All @@ -43,6 +45,10 @@ const selfUpdateOpts = {
choices: ["macos", "linux", "windows"],
help: `Override the platform, instead of detecting it automatically.`,
}),
"architecture": new ChoicesParameter({
choices: ["arm64", "amd64"],
help: `Override the architecture, instead of detecting it automatically.`,
}),
"major": new BooleanParameter({
defaultValue: false,
// TODO Core 1.0 major release: add these notes:
Expand Down Expand Up @@ -97,7 +103,7 @@ export type Pagination = { pageNumber: number; pageSize: number }

export async function fetchReleases({ pageNumber, pageSize }: Pagination) {
const results: any[] = await got(
`https://api.github.com/repos/garden-io/garden/releases?page=${pageNumber}&per_page=${[pageSize]}`
`${gardenEnv.GARDEN_RELEASES_ENDPOINT}?page=${pageNumber}&per_page=${[pageSize]}`
).json()
return results
}
Expand Down Expand Up @@ -158,25 +164,46 @@ export async function findRelease({
* @return the latest version tag
* @throws {RuntimeError} if the latest version cannot be detected
*/
export async function getLatestVersion(): Promise<string> {
const latestVersionRes: any = await got("https://api.github.com/repos/garden-io/garden/releases/latest").json()
const latestVersion = latestVersionRes.tag_name
export async function getLatestVersion(log: Log): Promise<string> {
let latestVersion: string | undefined = undefined
const endpoint = `${gardenEnv.GARDEN_RELEASES_ENDPOINT}/latest`

try {
const latestVersionRes: any = await got(endpoint).json()
latestVersion = latestVersionRes.tag_name
} catch (err) {
log.debug(`Retrieving the latest Garden version from ${endpoint} failed with error ${err}.`)
}

if (!latestVersion) {
throw new RuntimeError({
message: `Unable to detect the latest Garden version: ${latestVersionRes}`,
message: `Unable to retrieve the latest Garden release version, this could be a temporary service error, please try again later.`,
})
}

return latestVersion
}

export async function getLatestVersions(numOfStableVersions: number) {
const res: any = await got("https://api.github.com/repos/garden-io/garden/releases?per_page=100").json()
export async function getLatestVersions(numOfStableVersions: number, log: Log) {
let releasesResponse: any | undefined = undefined
const endpoint = `${gardenEnv.GARDEN_RELEASES_ENDPOINT}`

try {
releasesResponse = await got(`${endpoint}?per_page=100`).json()
} catch (err) {
log.debug(`Retrieving the latest Garden releases from ${endpoint} failed with error ${err}.`)
}

if (!releasesResponse) {
throw new RuntimeError({
message: `Unable to retrieve the list of Garden releases, this could be a temporary service error, please try again later.`,
})
}

return [
chalk.cyan("edge-acorn"),
chalk.cyan("edge-bonsai"),
...res
...releasesResponse
.filter((r: any) => !r.prerelease && !r.draft)
.map((r: any) => chalk.cyan(r.name))
.slice(0, numOfStableVersions),
Expand Down Expand Up @@ -249,11 +276,11 @@ export class SelfUpdateCommand extends Command<SelfUpdateArgs, SelfUpdateOpts> {
installationDirectory = resolve(installationDirectory)

log.info(chalk.white("Checking for target and latest versions..."))
const latestVersion = await getLatestVersion()
const latestVersion = await getLatestVersion(log)

if (!desiredVersion) {
const versionScope = getVersionScope(opts)
desiredVersion = await this.findTargetVersion(currentVersion, versionScope)
desiredVersion = await this.findTargetVersion(currentVersion, versionScope, latestVersion)
}

log.info(chalk.white("Current Garden version: ") + chalk.cyan(currentVersion))
Expand Down Expand Up @@ -305,7 +332,7 @@ export class SelfUpdateCommand extends Command<SelfUpdateArgs, SelfUpdateOpts> {
platform = getPlatform() === "darwin" ? "macos" : getPlatform()
}

let architecture = getArchitecture()
let architecture: Architecture = opts.architecture ? (opts.architecture as Architecture) : getArchitecture()
const isArmInRosetta = isDarwinARM()

// When running under Rosetta,
Expand All @@ -314,13 +341,15 @@ export class SelfUpdateCommand extends Command<SelfUpdateArgs, SelfUpdateOpts> {
// so we override the architecture here
// and then check if the version is supported or not
// potentially reverting it back to amd64 again
if (isArmInRosetta) {
if (!opts.architecture && isArmInRosetta) {
architecture = "arm64"
}

if (
!opts.architecture &&
architecture === "arm64" &&
desiredVersion !== "edge-bonsai" &&
semver.valid(desiredVersion) !== null &&
semver.lt(desiredVersion, ARM64_INTRODUCTION_VERSION)
) {
if (platform === "macos") {
Expand Down Expand Up @@ -366,12 +395,14 @@ export class SelfUpdateCommand extends Command<SelfUpdateArgs, SelfUpdateOpts> {

// Print the latest available stable versions
try {
const latestVersions = await getLatestVersions(10)
const latestVersions = await getLatestVersions(10, log)

log.info(
chalk.white.bold(`Here are the latest available versions: `) + latestVersions.join(chalk.white(", "))
)
} catch {}
} catch (err) {
log.debug(`Could not retrieve the latest available versions, ${err}`)
}

return {
result: {
Expand Down Expand Up @@ -480,14 +511,18 @@ export class SelfUpdateCommand extends Command<SelfUpdateArgs, SelfUpdateOpts> {
* @return the matching version tag
* @throws {RuntimeError} if the desired version cannot be detected, or if the current version cannot be recognized as a valid release version
*/
private async findTargetVersion(currentVersion: string, versionScope: VersionScope): Promise<string> {
private async findTargetVersion(
currentVersion: string,
versionScope: VersionScope,
latestVersion: string
): Promise<string> {
if (isEdgeVersion(currentVersion)) {
return getLatestVersion()
return latestVersion
}

const currentSemVer = semver.parse(currentVersion)
if (isPreReleaseVersion(currentSemVer)) {
return getLatestVersion()
return latestVersion
}

// The current version is necessary, it's not possible to proceed without its value
Expand Down
11 changes: 10 additions & 1 deletion core/src/constants.ts
Expand Up @@ -45,7 +45,6 @@ export const SEGMENT_DEV_API_KEY = "D3DUZ3lBSDO3krnuIO7eYDdtlDAjooKW" // ggignor
export const SEGMENT_PROD_API_KEY = "b6ovUD9A0YjQqT3ZWetWUbuZ9OmGxKMa" // ggignore

export const DOCS_BASE_URL = "https://docs.garden.io"
export const VERSION_CHECK_URL = "https://get.garden.io/version"

export const DEFAULT_GARDEN_CLOUD_DOMAIN = "https://app.garden.io"

Expand Down Expand Up @@ -78,4 +77,14 @@ export const gardenEnv = {
GARDEN_WORKFLOW_RUN_UID: env.get("GARDEN_WORKFLOW_RUN_UID").required(false).asString(),
GARDEN_CLOUD_DOMAIN: env.get("GARDEN_CLOUD_DOMAIN").required(false).asUrlString(),
GARDEN_ENABLE_TRACING: env.get("GARDEN_ENABLE_TRACING").required(false).default("true").asBool(),
GARDEN_VERSION_CHECK_ENDPOINT: env
.get("GARDEN_VERSION_CHECK_ENDPOINT")
.required(false)
.default("https://get.garden.io/version")
.asUrlString(),
GARDEN_RELEASES_ENDPOINT: env
.get("GARDEN_RELEASES_ENDPOINT")
.required(false)
.default("https://get.garden.io/releases")
.asUrlString(),
}
Binary file not shown.

0 comments on commit 33cfebc

Please sign in to comment.