Skip to content

Commit

Permalink
fix(k8s): issues with querying registries for image tags
Browse files Browse the repository at this point in the history
This affected users using a non-default deploymentRegistry, as well as
microk8s users. The registry is now properly queried when checking
build status in all cases, both when building locally and remotely,
and whether an in-cluster registry is used or not.

Fixes #1380
  • Loading branch information
edvald committed Feb 24, 2020
1 parent 29fe3aa commit 71f41d4
Show file tree
Hide file tree
Showing 16 changed files with 418 additions and 185 deletions.
2 changes: 1 addition & 1 deletion garden-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG NODE_VERSION=12.13.1-alpine
ARG NODE_VERSION=12.16.1-alpine3.11
FROM node:${NODE_VERSION} as builder

RUN apk add --no-cache \
Expand Down
17 changes: 13 additions & 4 deletions garden-service/buster.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
FROM node:12.13.1-buster
FROM node:12.16.1-buster

# system dependencies
RUN set -ex; \
apt-get update; \
apt-get install -y --no-install-recommends \
apt-transport-https \
bash \
docker \
ca-certificates \
curl \
gnupg2 \
git \
gzip \
openssl \
rsync; \
rm -rf /var/lib/apt/lists/*
rsync \
software-properties-common; \
\
curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -; \
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"; \
apt-get update; \
apt-get install docker-ce-cli; \
rm -rf /var/lib/apt/lists/*;

ADD . /garden

Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/logger/renderers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import hasAnsi = require("has-ansi")
import { LogEntry, MessageState } from "./log-entry"
import { JsonLogEntry } from "./writers/json-terminal-writer"
import { highlightYaml, deepFilter, PickFromUnion } from "../util/util"
import { isNumber, isBuffer } from "util"
import { isNumber } from "util"
import { printEmoji, sanitizeObject } from "./util"
import { LoggerType, Logger } from "./logger"

Expand Down
15 changes: 10 additions & 5 deletions garden-service/src/plugins/container/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { LogLevel } from "../../logger/log-node"
import { createOutputStream } from "../../util/util"

export async function getContainerBuildStatus({ module, log }: GetBuildStatusParams<ContainerModule>) {
const identifier = await containerHelpers.imageExistsLocally(module)
const identifier = await containerHelpers.imageExistsLocally(module, log)

if (identifier) {
log.debug({
Expand All @@ -29,16 +29,18 @@ export async function getContainerBuildStatus({ module, log }: GetBuildStatusPar
}

export async function buildContainerModule({ module, log }: BuildModuleParams<ContainerModule>) {
containerHelpers.checkDockerServerVersion(await containerHelpers.getDockerVersion())

const buildPath = module.buildPath
const image = module.spec.image
const hasDockerfile = await containerHelpers.hasDockerfile(module)

if (!!image && !hasDockerfile) {
if (await containerHelpers.imageExistsLocally(module)) {
if (await containerHelpers.imageExistsLocally(module, log)) {
return { fresh: false }
}
log.setState(`Pulling image ${image}...`)
await containerHelpers.pullImage(module)
await containerHelpers.pullImage(module, log)
return { fetched: true }
}

Expand Down Expand Up @@ -66,9 +68,12 @@ export async function buildContainerModule({ module, log }: BuildModuleParams<Co
// Stream log to a status line
const outputStream = createOutputStream(log.placeholder(LogLevel.debug))
const timeout = module.spec.build.timeout
const buildLog = await containerHelpers.dockerCli(module, [...cmdOpts, buildPath], { outputStream, timeout })
const res = await containerHelpers.dockerCli(module.buildPath, [...cmdOpts, buildPath], log, {
outputStream,
timeout,
})

return { fresh: true, buildLog, details: { identifier } }
return { fresh: true, buildLog: res.output || "", details: { identifier } }
}

export function getDockerBuildFlags(module: ContainerModule) {
Expand Down
142 changes: 106 additions & 36 deletions garden-service/src/plugins/container/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,19 @@ import { flatten, uniq } from "lodash"
import { LogEntry } from "../../logger/log-entry"
import chalk from "chalk"
import isUrl from "is-url"
import { BinaryCmd } from "../../util/ext-tools"

interface DockerVersion {
client: string
server: string
}

export const DEFAULT_BUILD_TIMEOUT = 600
export const minDockerVersion = "17.07.0"

export const minDockerVersion: DockerVersion = {
client: "19.03.0",
server: "17.07.0",
}

interface ParsedImageId {
host?: string
Expand Down Expand Up @@ -196,24 +206,25 @@ const helpers = {
}
},

async pullImage(module: ContainerModule) {
async pullImage(module: ContainerModule, log: LogEntry) {
const identifier = await helpers.getPublicImageId(module)
await helpers.dockerCli(module, ["pull", identifier])
await helpers.dockerCli(module.buildPath, ["pull", identifier], log)
},

async imageExistsLocally(module: ContainerModule) {
async imageExistsLocally(module: ContainerModule, log: LogEntry) {
const identifier = await helpers.getLocalImageId(module)
const exists = (await helpers.dockerCli(module, ["images", identifier, "-q"])).length > 0
const exists = (await helpers.dockerCli(module.buildPath, ["images", identifier, "-q"], log)).output?.length > 0
return exists ? identifier : null
},

dockerVersionChecked: false,

async getDockerVersion() {
let versionRes
/**
* Retrieves the docker client and server version.
*/
async getDockerVersion(cliPath = "docker"): Promise<DockerVersion> {
let versionRes: any

try {
versionRes = await spawn("docker", ["version", "-f", "{{ .Client.Version }} {{ .Server.Version }}"])
versionRes = await spawn(cliPath, ["version", "-f", "{{ .Client.Version }} {{ .Server.Version }}"])
} catch (err) {
throw new RuntimeError(`Unable to get docker version: ${err.message}`, {
err,
Expand All @@ -232,46 +243,71 @@ const helpers = {
})
}

return { clientVersion, serverVersion }
return { client: clientVersion, server: serverVersion }
},

async checkDockerVersion() {
if (helpers.dockerVersionChecked) {
return
/**
* Asserts that the specified docker client version meets the minimum requirements.
*/
checkDockerClientVersion(version: DockerVersion) {
if (!checkMinDockerVersion(version.client, minDockerVersion.client)) {
throw new RuntimeError(
`Docker client needs to be version ${minDockerVersion.client} or newer (got ${version.client})`,
{
...version,
}
)
}
},

const fixedMinVersion = fixDockerVersionString(minDockerVersion)
const { clientVersion, serverVersion } = await helpers.getDockerVersion()

if (!semver.gte(fixDockerVersionString(clientVersion), fixedMinVersion)) {
throw new RuntimeError(`Docker client needs to be version ${minDockerVersion} or newer (got ${clientVersion})`, {
clientVersion,
serverVersion,
})
/**
* Asserts that the specified docker client version meets the minimum requirements.
*/
checkDockerServerVersion(version: DockerVersion) {
if (!checkMinDockerVersion(version.server, minDockerVersion.server)) {
throw new RuntimeError(
`Docker server needs to be version ${minDockerVersion.server} or newer (got ${version.server})`,
{
...version,
}
)
}
},

if (!semver.gte(fixDockerVersionString(serverVersion), fixedMinVersion)) {
throw new RuntimeError(`Docker server needs to be version ${minDockerVersion} or newer (got ${serverVersion})`, {
clientVersion,
serverVersion,
})
async getDockerCliPath(log: LogEntry) {
// Check if docker is already installed
try {
const version = await helpers.getDockerVersion("docker")
helpers.checkDockerClientVersion(version)
return "docker"
} catch (_) {
// Need to fetch a docker client
return dockerBin.getPath(log)
}

helpers.dockerVersionChecked = true
},

async dockerCli(
module: ContainerModule,
cwd: string,
args: string[],
{ outputStream, timeout = DEFAULT_BUILD_TIMEOUT }: { outputStream?: Writable; timeout?: number } = {}
log: LogEntry,
{
ignoreError = false,
outputStream,
timeout = DEFAULT_BUILD_TIMEOUT,
}: { ignoreError?: boolean; outputStream?: Writable; timeout?: number } = {}
) {
await helpers.checkDockerVersion()

const cwd = module.buildPath
// Check if docker is already installed
const cliPath = await helpers.getDockerCliPath(log)

try {
const res = await spawn("docker", args, { cwd, stdout: outputStream, timeout })
return res.output || ""
const res = await spawn(cliPath, args, {
cwd,
ignoreError,
stdout: outputStream,
timeout,
env: { ...process.env, DOCKER_CLI_EXPERIMENTAL: "enabled" },
})
return res
} catch (err) {
throw new RuntimeError(`Unable to run docker command: ${err.message}`, {
err,
Expand Down Expand Up @@ -382,6 +418,10 @@ const helpers = {

export const containerHelpers = helpers

function checkMinDockerVersion(version: string, minVersion: string) {
return semver.gte(fixDockerVersionString(version), fixDockerVersionString(minVersion))
}

// Ugh, Docker doesn't use valid semver. Here's a hacky fix.
function fixDockerVersionString(v: string) {
return semver.coerce(v.replace(/\.0([\d]+)/g, ".$1"))!
Expand All @@ -390,3 +430,33 @@ function fixDockerVersionString(v: string) {
function getDockerfilePath(basePath: string, dockerfile = "Dockerfile") {
return join(basePath, dockerfile)
}

export const dockerBin = new BinaryCmd({
name: "docker",
specs: {
darwin: {
url: "https://download.docker.com/mac/static/stable/x86_64/docker-19.03.6.tgz",
sha256: "82d279c6a2df05c2bb628607f4c3eacb5a7447be6d5f2a2f65643fbb6ed2f9af",
extract: {
format: "tar",
targetPath: ["docker", "docker"],
},
},
linux: {
url: "https://download.docker.com/linux/static/stable/x86_64/docker-19.03.6.tgz",
sha256: "34ff89ce917796594cd81149b1777d07786d297ffd0fef37a796b5897052f7cc",
extract: {
format: "tar",
targetPath: ["docker", "docker"],
},
},
win32: {
url: "https://github.com/rgl/docker-ce-windows-binaries-vagrant/releases/download/v19.03.6/docker-19.03.6.zip",
sha256: "b4591baa2b7016af9ff3328a26146e4db3e6ce3fbe0503a7fd87363f29d63f5c",
extract: {
format: "zip",
targetPath: ["docker", "docker.exe"],
},
},
},
})
4 changes: 2 additions & 2 deletions garden-service/src/plugins/container/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ export async function publishContainerModule({ module, log }: PublishModuleParam
log.setState({ msg: `Publishing image ${remoteId}...` })

if (localId !== remoteId) {
await containerHelpers.dockerCli(module, ["tag", localId, remoteId])
await containerHelpers.dockerCli(module.buildPath, ["tag", localId, remoteId], log)
}

// TODO: stream output to log if at debug log level
await containerHelpers.dockerCli(module, ["push", remoteId])
await containerHelpers.dockerCli(module.buildPath, ["push", remoteId], log)

return { published: true, message: `Published ${remoteId}` }
}
Loading

0 comments on commit 71f41d4

Please sign in to comment.