Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ Custom features for dev containers used in development of Omoxyz software projec

## Features

- **Lefthook** (`lefthook`) – fast polyglot Git hooks manager to automate code checks, formatting, and tests before commits and pushes.
- **Air** (`go-air`) - live reloader for Go apps
- **Lefthook** ([lefthook](./src/lefthook/README.md)) – fast polyglot Git hooks manager to automate code checks, formatting, and tests before commits and pushes.
- **Air** ([go-air](./src/go-air/README.md)) - live reloader for Go apps
- **Protoc** ([protoc](./src/protoc/README.md))

## Usage

Expand Down
5 changes: 5 additions & 0 deletions src/protoc/NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## OS Support

This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.

`bash` is required to execute the `install.sh` script.
20 changes: 20 additions & 0 deletions src/protoc/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "Protoc",
"id": "protoc",
"version": "1.0.0",
"documentationURL": "http://github.com/omoxyz/devcontainer-features/tree/main/src/protoc",
"description": "Install Protoc protocol buffer compiler.",
"options": {
"version": {
"default": "latest",
"description": "Select the version to install.",
"proposals": [
"latest"
],
"type": "string"
}
},
"installsAfter": [
"ghcr.io/devcontainers/features/git"
]
}
122 changes: 122 additions & 0 deletions src/protoc/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env bash

if [ "$(id -u)" -ne 0 ]; then
echo -e 'Scripts must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
exit 1
fi

source ./utils.sh

PROTOC_VERSION=${VERSION:-"latest"}
INSTALL_DIRECTLY_FROM_GITHUB_RELEASE=${INSTALLDIRECTLYFROMGITHUBRELEASE:-"true"}
GITHUB_REPO=https://github.com/google/protobuf

# Exit immediately if a command exits with a non-zero status.
set -e

apt_get_update

# Clean up
rm -rf /var/lib/apt/lists/*

export DEBIAN_FRONTEND=noninteractive

get_github_filename() {
local version=$1
local arch=$2
echo "protoc-${version}-linux-${arch}.zip"
}

install_from_github() {
local version_list=$(git ls-remote --tags ${GITHUB_REPO})

# Get 2 latest appropriate versions
versions=($(find_latest_versions $PROTOC_VERSION version_list "tags/v"))
if [ $? -eq 1 ]; then
echo "Can't find appropriate version"
exit 1
fi

latest_version=${versions[0]}
prev_version=${versions[1]}

echo "Downloading protoc v${latest_version}...."

check_packages wget

# Get architecture
local arch=$(dpkg --print-architecture)

# Map to generic architecture
case "$arch" in
amd64)
arch="x86_64"
;;
i386)
arch="x86_32"
;;
arm64)
arch="aarch64"
;;
armhf)
arch="arm"
;;
ppc64el)
arch="ppc64le"
;;
s390x)
arch="s390x"
;;
*)
echo "Unknown architecture $arch."
exit 1
;;
esac

local filename=$(get_github_filename $latest_version $arch)

set +e

# Create temporary directory
mkdir -p /tmp/protoc
pushd /tmp/protoc

# Download zip file
wget ${GITHUB_REPO}/releases/download/v${latest_version}/${filename}
local exit_code=$?

set -e

if [ "$exit_code" != "0" ]; then
# Handle situation where git tags are ahead of what was is available to actually download
echo "(!) protoc version ${latest_version} failed to download. Attempting to fall back to ${prev_version} to retry..."
filename=$(get_github_filename $prev_version $arch)
wget ${GITHUB_REPO}/releases/download/v${prev_version}/${filename}
fi

unzip /tmp/protoc/${filename} -d /tmp/protoc

# Install bin/
if [[ -d /tmp/protoc/bin ]]; then
echo "Installing binaries to /usr/local/bin/..."
cp -r /tmp/protoc/bin/* /usr/local/bin/
fi

# Move include/
if [[ -d /tmp/protoc/include ]]; then
echo "Installing headers to /usr/local/include/..."
cp -r /tmp/protoc/include/* /usr/local/include/
fi

# Remove temporary directory
popd
rm -rf /tmp/protoc
}

# Install curl, unzip if missing
check_packages curl ca-certificates unzip git

install_from_github

# Clean up
rm -rf /var/lib/apt/lists/*
88 changes: 88 additions & 0 deletions src/protoc/utils.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env bash

# Refresh the local package index if no package list entries are stored on the system.
apt_get_update() {
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
fi
}

# Checks if packages are installed and installs them if not
check_packages() {
for pkg in "$@"; do
# Check if it's a command in PATH
if command -v "$pkg" &> /dev/null; then
echo "[OK] $pkg found in PATH"
continue
fi

# Check if it's a Debian/Ubuntu package installed
if dpkg -s "$pkg" &> /dev/null; then
echo "[OK] $pkg package installed"
continue
fi

# If not found, install package
echo "$pkg not found. Installing..."
apt_get_update
apt-get install -y --no-install-recommends $pkg

done
}

# Find 2 latest versions that appropriate to requested version
find_latest_versions() {
local requested_version=$1
local version_list=${!2}

# Version prefix such as "tags/v"
local prefix_regex=${3:-''}

# Version number part separator such as "." in "1.0.0"
local separator=${4:-"."}
local escaped_separator=${separator//./\\.}

local suffix_regex=${5:-''}

# Format and sort version list
local version_regex="${prefix_regex}\\K[0-9]+(${escaped_separator}[0-9]+){0,2}${suffix_regex}$"
version_list="$(printf "%s\n" "${version_list[@]}" | grep -oP $version_regex| tr -d ' ' | tr $separator '.' | sort -rV)"

if [ "${requested_version}" = "latest" ]; then
echo "$(echo "${version_list}" | head -n 2)"
else
# Try to get latest matching version

set +e
local regex="^"

# Get major version or exit
local major="$(echo "${requested_version}" | grep -oE '^[0-9]+')"
if [ $major != '' ]; then
regex="${regex}${major}"
else
echo "Invalid version \"${requested_version}\". Use \"latest\" or MAJOR[.MINOR][.PATCH]"
return 1
fi

# Get minor number or accept any
local minor="$(echo "${requested_version}" | grep -oP '^[0-9]+\.\K[0-9]+')"
regex="${regex}$([ "$minor" != '' ] && echo "${escaped_separator}${minor}" || echo "(${escaped_separator}[0-9]+)?")"


# Get patch number or accept any
local patch="$(echo "${requested_version}" | grep -oP '^[0-9]+\.[0-9]+\.\K[0-9]+')"
regex="${regex}$([ "$patch" != '' ] && echo "${escaped_separator}${patch}" || echo "(${escaped_separator}[0-9]+)?")"
set -e

echo "$(echo "${version_list}" | grep -E -m 2 "^${regex}$")"
fi
}

get_apt_versions() {
package="$1"
apt list -a "$package" 2>/dev/null \
| awk -F' ' 'NR>1 {print $2}' \
| sort -rV
}
8 changes: 8 additions & 0 deletions test/protoc/scenarios.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"test": {
"image": "mcr.microsoft.com/devcontainers/base:debian",
"features": {
"protoc": {}
}
}
}
9 changes: 9 additions & 0 deletions test/protoc/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash

set -e

source dev-container-features-test-lib

check "protoc version" protoc --version

reportResults