Skip to content

Commit 143f147

Browse files
authored
Add go feature (#33)
* Add go feature with support for Azure Linux
1 parent b232e9f commit 143f147

11 files changed

Lines changed: 412 additions & 0 deletions

.github/workflows/test-all.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
- docfx
1717
- external-repository
1818
- microsoft-git
19+
- go
1920
baseImage:
2021
- mcr.microsoft.com/devcontainers/base:ubuntu
2122
- mcr.microsoft.com/devcontainers/base:debian

.github/workflows/test-pr.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
docfx: ./**/docfx/**
2020
external-repository: ./**/external-repository/**
2121
microsoft-git: ./**/microsoft-git/**
22+
go: ./**/go/**
2223
test:
2324
needs: [detect-changes]
2425
runs-on: ubuntu-latest

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ to assist teams in adopting Codespaces. Here is a list of the features in this r
1010
| [docfx](src/docfx) | Install docfx to support editing and testing documentation |
1111
| [external-repository](src/external-repository) | Handles all the details of working with an external git repository in Codespaces |
1212
| [microsoft-git](src/microsoft-git) | Install microsoft/git with Scalar and GVFS support |
13+
| [go](src/go) | Install go (with support for Mariner) |
1314

1415
## Contributing
1516

src/go/NOTES.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
3+
## OS Support
4+
5+
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed
6+
as well as Azure Linux (Mariner) with the `tdnf` package manager installed.
7+
8+
`bash` is required to execute the `install.sh` script.

src/go/devcontainer-feature.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"id": "go",
3+
"version": "1.2.0",
4+
"name": "Go",
5+
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/go",
6+
"description": "Installs Go and common Go utilities. Auto-detects latest version and installs needed dependencies.",
7+
"options": {
8+
"version": {
9+
"type": "string",
10+
"proposals": [
11+
"latest",
12+
"none",
13+
"1.19",
14+
"1.18"
15+
],
16+
"default": "latest",
17+
"description": "Select or enter a Go version to install"
18+
},
19+
"golangciLintVersion": {
20+
"type": "string",
21+
"default": "latest",
22+
"description": "Version of golangci-lint to install"
23+
}
24+
},
25+
"init": true,
26+
"customizations": {
27+
"vscode": {
28+
"extensions": [
29+
"golang.Go"
30+
]
31+
}
32+
},
33+
"containerEnv": {
34+
"GOROOT": "/usr/local/go",
35+
"GOPATH": "/go",
36+
"PATH": "/usr/local/go/bin:/go/bin:${PATH}"
37+
},
38+
"capAdd": [
39+
"SYS_PTRACE"
40+
],
41+
"securityOpt": [
42+
"seccomp=unconfined"
43+
],
44+
"installsAfter": [
45+
"ghcr.io/devcontainers/features/common-utils"
46+
]
47+
}

src/go/install.sh

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
#!/usr/bin/env bash
2+
#-------------------------------------------------------------------------------------------------------------
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information
5+
#-------------------------------------------------------------------------------------------------------------
6+
#
7+
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/go.md
8+
# Maintainer: The VS Code and Codespaces Teams
9+
10+
TARGET_GO_VERSION="${VERSION:-"latest"}"
11+
GOLANGCILINT_VERSION="${GOLANGCILINTVERSION:-"latest"}"
12+
13+
TARGET_GOROOT="${TARGET_GOROOT:-"/usr/local/go"}"
14+
TARGET_GOPATH="${TARGET_GOPATH:-"/go"}"
15+
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
16+
INSTALL_GO_TOOLS="${INSTALL_GO_TOOLS:-"true"}"
17+
18+
# https://www.google.com/linuxrepositories/
19+
GO_GPG_KEY_URI="https://dl.google.com/linux/linux_signing_key.pub"
20+
21+
set -e
22+
# Source /etc/os-release to get OS info
23+
. /etc/os-release
24+
25+
if [ "$(id -u)" -ne 0 ]; then
26+
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
27+
exit 1
28+
fi
29+
30+
# Ensure that login shells get the correct path if the user updated the PATH using ENV.
31+
rm -f /etc/profile.d/00-restore-env.sh
32+
echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
33+
chmod +x /etc/profile.d/00-restore-env.sh
34+
35+
# Determine the appropriate non-root user
36+
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
37+
USERNAME=""
38+
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
39+
for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do
40+
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
41+
USERNAME=${CURRENT_USER}
42+
break
43+
fi
44+
done
45+
if [ "${USERNAME}" = "" ]; then
46+
USERNAME=root
47+
fi
48+
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
49+
USERNAME=root
50+
fi
51+
52+
# Figure out correct version of a three part version number is not passed
53+
find_version_from_git_tags() {
54+
local variable_name=$1
55+
local requested_version=${!variable_name}
56+
if [ "${requested_version}" = "none" ]; then return; fi
57+
local repository=$2
58+
local prefix=${3:-"tags/v"}
59+
local separator=${4:-"."}
60+
local last_part_optional=${5:-"false"}
61+
if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
62+
local escaped_separator=${separator//./\\.}
63+
local last_part
64+
if [ "${last_part_optional}" = "true" ]; then
65+
last_part="(${escaped_separator}[0-9]+)?"
66+
else
67+
last_part="${escaped_separator}[0-9]+"
68+
fi
69+
local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
70+
local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
71+
if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
72+
declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
73+
else
74+
set +e
75+
declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
76+
set -e
77+
fi
78+
fi
79+
if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
80+
echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
81+
exit 1
82+
fi
83+
echo "${variable_name}=${!variable_name}"
84+
}
85+
86+
# Get central common setting
87+
get_common_setting() {
88+
if [ "${common_settings_file_loaded}" != "true" ]; then
89+
curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
90+
common_settings_file_loaded=true
91+
fi
92+
if [ -f "/tmp/vsdc-settings.env" ]; then
93+
local multi_line=""
94+
if [ "$2" = "true" ]; then multi_line="-z"; fi
95+
local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
96+
if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
97+
fi
98+
echo "$1=${!1}"
99+
}
100+
101+
apt_get_update()
102+
{
103+
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
104+
echo "Running apt-get update..."
105+
apt-get update -y
106+
fi
107+
}
108+
109+
# Checks if packages are installed and installs them if not
110+
check_packages() {
111+
if ! dpkg -s "$@" > /dev/null 2>&1; then
112+
apt_get_update
113+
apt-get -y install --no-install-recommends "$@"
114+
fi
115+
}
116+
117+
export DEBIAN_FRONTEND=noninteractive
118+
119+
if [ "${ID}" = "mariner" ]; then
120+
tdnf install -y curl ca-certificates gnupg2 tar g++ gcc make git build-essential
121+
else
122+
# Clean up
123+
rm -rf /var/lib/apt/lists/*
124+
check_packages curl ca-certificates
125+
# Install curl, tar, git, other dependencies if missing
126+
check_packages curl ca-certificates gnupg2 tar g++ gcc libc6-dev make pkg-config
127+
if ! type git > /dev/null 2>&1; then
128+
check_packages git
129+
fi
130+
fi
131+
132+
# Get closest match for version number specified
133+
find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true"
134+
135+
architecture="$(uname -m)"
136+
case $architecture in
137+
x86_64) architecture="amd64";;
138+
aarch64 | armv8*) architecture="arm64";;
139+
aarch32 | armv7* | armvhf*) architecture="armv6l";;
140+
i?86) architecture="386";;
141+
*) echo "(!) Architecture $architecture unsupported"; exit 1 ;;
142+
esac
143+
144+
# Install Go
145+
umask 0002
146+
if ! cat /etc/group | grep -e "^golang:" > /dev/null 2>&1; then
147+
groupadd -r golang
148+
fi
149+
usermod -a -G golang "${USERNAME}"
150+
mkdir -p "${TARGET_GOROOT}" "${TARGET_GOPATH}"
151+
152+
if [[ "${TARGET_GO_VERSION}" != "none" ]] && [[ "$(go version)" != *"${TARGET_GO_VERSION}"* ]]; then
153+
# Use a temporary location for gpg keys to avoid polluting image
154+
export GNUPGHOME="/tmp/tmp-gnupg"
155+
mkdir -p ${GNUPGHOME}
156+
chmod 700 ${GNUPGHOME}
157+
get_common_setting GO_GPG_KEY_URI
158+
curl -sSL -o /tmp/tmp-gnupg/golang_key "${GO_GPG_KEY_URI}"
159+
gpg -q --import /tmp/tmp-gnupg/golang_key
160+
echo "Downloading Go ${TARGET_GO_VERSION}..."
161+
set +e
162+
curl -fsSL -o /tmp/go.tar.gz "https://golang.org/dl/go${TARGET_GO_VERSION}.linux-${architecture}.tar.gz"
163+
exit_code=$?
164+
set -e
165+
if [ "$exit_code" != "0" ]; then
166+
echo "(!) Download failed."
167+
# Try one break fix version number less if we get a failure. Use "set +e" since "set -e" can cause failures in valid scenarios.
168+
set +e
169+
major="$(echo "${TARGET_GO_VERSION}" | grep -oE '^[0-9]+' || echo '')"
170+
minor="$(echo "${TARGET_GO_VERSION}" | grep -oP '^[0-9]+\.\K[0-9]+' || echo '')"
171+
breakfix="$(echo "${TARGET_GO_VERSION}" | grep -oP '^[0-9]+\.[0-9]+\.\K[0-9]+' 2>/dev/null || echo '')"
172+
# Handle Go's odd version pattern where "0" releases omit the last part
173+
if [ "${breakfix}" = "" ] || [ "${breakfix}" = "0" ]; then
174+
((minor=minor-1))
175+
TARGET_GO_VERSION="${major}.${minor}"
176+
# Look for latest version from previous minor release
177+
find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true"
178+
else
179+
((breakfix=breakfix-1))
180+
if [ "${breakfix}" = "0" ]; then
181+
TARGET_GO_VERSION="${major}.${minor}"
182+
else
183+
TARGET_GO_VERSION="${major}.${minor}.${breakfix}"
184+
fi
185+
fi
186+
set -e
187+
echo "Trying ${TARGET_GO_VERSION}..."
188+
curl -fsSL -o /tmp/go.tar.gz "https://golang.org/dl/go${TARGET_GO_VERSION}.linux-${architecture}.tar.gz"
189+
fi
190+
curl -fsSL -o /tmp/go.tar.gz.asc "https://golang.org/dl/go${TARGET_GO_VERSION}.linux-${architecture}.tar.gz.asc"
191+
gpg --verify /tmp/go.tar.gz.asc /tmp/go.tar.gz
192+
echo "Extracting Go ${TARGET_GO_VERSION}..."
193+
tar -xzf /tmp/go.tar.gz -C "${TARGET_GOROOT}" --strip-components=1
194+
rm -rf /tmp/go.tar.gz /tmp/go.tar.gz.asc /tmp/tmp-gnupg
195+
else
196+
echo "(!) Go is already installed with version ${TARGET_GO_VERSION}. Skipping."
197+
fi
198+
199+
# Install Go tools that are isImportant && !replacedByGopls based on
200+
# https://github.com/golang/vscode-go/blob/v0.38.0/src/goToolsInformation.ts
201+
GO_TOOLS="\
202+
golang.org/x/tools/gopls@latest \
203+
honnef.co/go/tools/cmd/staticcheck@latest \
204+
golang.org/x/lint/golint@latest \
205+
github.com/mgechev/revive@latest \
206+
github.com/go-delve/delve/cmd/dlv@latest \
207+
github.com/fatih/gomodifytags@latest \
208+
github.com/haya14busa/goplay/cmd/goplay@latest \
209+
github.com/cweill/gotests/gotests@latest \
210+
github.com/josharian/impl@latest"
211+
212+
if [ "${INSTALL_GO_TOOLS}" = "true" ]; then
213+
echo "Installing common Go tools..."
214+
export PATH=${TARGET_GOROOT}/bin:${PATH}
215+
mkdir -p /tmp/gotools /usr/local/etc/vscode-dev-containers ${TARGET_GOPATH}/bin
216+
cd /tmp/gotools
217+
export GOPATH=/tmp/gotools
218+
export GOCACHE=/tmp/gotools/cache
219+
220+
# Use go get for versions of go under 1.16
221+
go_install_command=install
222+
if [[ "1.16" > "$(go version | grep -oP 'go\K[0-9]+\.[0-9]+(\.[0-9]+)?')" ]]; then
223+
export GO111MODULE=on
224+
go_install_command=get
225+
echo "Go version < 1.16, using go get."
226+
fi
227+
228+
(echo "${GO_TOOLS}" | xargs -n 1 go ${go_install_command} -v )2>&1 | tee -a /usr/local/etc/vscode-dev-containers/go.log
229+
230+
# Move Go tools into path and clean up
231+
if [ -d /tmp/gotools/bin ]; then
232+
mv /tmp/gotools/bin/* ${TARGET_GOPATH}/bin/
233+
rm -rf /tmp/gotools
234+
fi
235+
236+
# Install golangci-lint from precompiled binares
237+
if [ "$GOLANGCILINT_VERSION" = "latest" ] || [ "$GOLANGCILINT_VERSION" = "" ]; then
238+
echo "Installing golangci-lint latest..."
239+
curl -fsSL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
240+
sh -s -- -b "${TARGET_GOPATH}/bin"
241+
else
242+
echo "Installing golangci-lint ${GOLANGCILINT_VERSION}..."
243+
curl -fsSL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
244+
sh -s -- -b "${TARGET_GOPATH}/bin" "v${GOLANGCILINT_VERSION}"
245+
fi
246+
fi
247+
248+
249+
chown -R "${USERNAME}:golang" "${TARGET_GOROOT}" "${TARGET_GOPATH}"
250+
chmod -R g+r+w "${TARGET_GOROOT}" "${TARGET_GOPATH}"
251+
find "${TARGET_GOROOT}" -type d -print0 | xargs -n 1 -0 chmod g+s
252+
find "${TARGET_GOPATH}" -type d -print0 | xargs -n 1 -0 chmod g+s
253+
254+
# Clean up
255+
if [ "${ID}" = "mariner" ]; then
256+
tdnf clean all
257+
else
258+
rm -rf /var/lib/apt/lists/*
259+
fi
260+
261+
echo "Done!"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Optional: Import test library
6+
source dev-container-features-test-lib
7+
8+
check "mkcert version" bash -c "mkcert --version | grep v1.4.2"
9+
check "mkcert is installed at correct path" bash -c "which mkcert | grep /go/bin/mkcert"
10+
check "golangci-lint version" bash -c "golangci-lint --version | grep 'golangci-lint has version 1.50.0'"
11+
12+
# Report result
13+
reportResults

test/go/install_go_twice.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Optional: Import test library
6+
source dev-container-features-test-lib
7+
8+
check "go-version" bash -c "go version | grep 1.19"
9+
10+
# Report result
11+
reportResults

test/go/install_mariner.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Optional: Import test library
6+
source dev-container-features-test-lib
7+
8+
check "go-version" bash -c "go version"
9+
check "golangci-lint version" bash -c "golangci-lint --version | grep 'golangci-lint has version 1.50.0'"
10+
11+
# Report result
12+
reportResults

0 commit comments

Comments
 (0)