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!"
0 commit comments