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
7 changes: 6 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,32 @@ GIT_VERSION=2.32.0
GH_VERSION=2.5.2
GO_VERSION=1.17.8
HASKELL_VERSION=9.2.2
GHCI_VERSION=${HASKELL_VERSION}
HELM_VERSION=3.8.1
HTTPIE_VERSION=2.6.0
HTTP_VERSION=${HTTPIE_VERSION}
JAVA_VERSION=17.0.2
LUA_IMAGE=nickblah/lua
LUA_VERSION=5.4.3
NODE_VERSION=17.7.2
PERL_VERSION=5.34.1
PHP_VERSION=8.1.3
PROTOC_VERSION=3.9.1
PROTOC_ARCH=${DEFAULT_ARCH}
PROTOC_BASE=${DEFAULT_BASE}
POSTGRES_VERSION=14.2
PSQL_VERSION=${POSTGRES_VERSION}
PYTHON_VERSION=3.10
PYTHON2_VERSION=2.7.18
RUBY_VERSION=3.1.1
RUST_VERSION=1.59.0
RUSTC_VERSION=${RUST_VERSION}
S3CMD_BASE=python:${PYTHON_VERSION}-${DEFAULT_BASE_IMAGE}
S3CMD_VERSION=2.2.0
SWAGGER_CODEGEN_VERSION=3.0.29
SWIPL_VERSION=8.5.5
NODE_VERSION=16.13.0
TYPESCRIPT_VERSION=4.6.2
TSC_VERSION=${TYPESCRIPT_VERSION}
TSC_VERSION=${TYPESCRIPT_VERSION}
VUE_VERSION=5.0.1
YOUTUBE_DL_VERSION=2022.03.08.2
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,14 @@ This allows you to "lock" your tools to specific versions for your project.
- Create a `dockerized.env` file in your project directory.
- All commands executed within this directory will use the settings specified in this file.

**Ad-hoc**

Add `:<version>` to the end of the command to override the version.

```shell
dockerized node:15
```

**Ad-hoc (Unix)**

- Override the environment variable before the command, to specify the version for that command.
Expand Down
8 changes: 4 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ services:
image: "alpine/git:v${GIT_VERSION}"
entrypoint: [ "git" ]
ghci:
image: "haskell:${HASKELL_VERSION}"
image: "haskell:${GHCI_VERSION}"
entrypoint: [ "ghci" ]
go:
image: "golang:${GO_VERSION}"
Expand All @@ -84,7 +84,7 @@ services:
helm:
image: "alpine/helm:${HELM_VERSION}"
http:
image: "alpine/httpie:${HTTPIE_VERSION}"
image: "alpine/httpie:${HTTP_VERSION}"
entrypoint: [ "http" ]
java:
image: "openjdk:${JAVA_VERSION}"
Expand Down Expand Up @@ -113,7 +113,7 @@ services:
php:
image: "php:${PHP_VERSION}"
psql:
image: "postgres:${POSTGRES_VERSION}"
image: "postgres:${PSQL_VERSION}"
entrypoint: [ "psql" ]
protoc:
image: "protoc:${PROTOC_VERSION}"
Expand Down Expand Up @@ -145,7 +145,7 @@ services:
<<: *ruby
entrypoint: [ "gem" ]
rustc:
image: "rust:${RUST_VERSION}"
image: "rust:${RUSTC_VERSION}"
entrypoint: [ "rustc" ]
s3cmd:
build:
Expand Down
130 changes: 122 additions & 8 deletions lib/dockerized.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"
Expand All @@ -14,6 +15,7 @@ import (
"os"
"os/signal"
"path/filepath"
"regexp"
"sort"
"strings"
"syscall"
Expand All @@ -34,7 +36,7 @@ var options = []string{
func main() {
normalizeEnvironment()

dockerizedOptions, commandName, commandArgs := parseArguments()
dockerizedOptions, commandName, commandVersion, commandArgs := parseArguments()

var optionHelp = contains(dockerizedOptions, "--help") || contains(dockerizedOptions, "-h")
var optionVerbose = contains(dockerizedOptions, "--verbose") || contains(dockerizedOptions, "-v")
Expand Down Expand Up @@ -76,7 +78,55 @@ func main() {
panic(err)
}

project, err := getProject(dockerizedDockerComposeFilePath)
if commandVersion != "" {
rawProject, err := getRawProject(dockerizedDockerComposeFilePath)
if err != nil {
panic(err)
}

rawService, err := rawProject.GetService(commandName)

var versionVariableExpected = strings.ReplaceAll(strings.ToUpper(commandName), "-", "_") + "_VERSION"
var versionVariablesUsed []string
for _, variable := range ExtractVariables(rawService) {
if strings.HasSuffix(variable, "_VERSION") {
versionVariablesUsed = append(versionVariablesUsed, variable)
}
}

if len(versionVariablesUsed) == 0 {
fmt.Printf("Error: Version selection for %s is currently not supported.\n", commandName)
os.Exit(1)
}

versionKey := versionVariableExpected

if !contains(versionVariablesUsed, versionVariableExpected) {
if len(versionVariablesUsed) == 1 {
fmt.Printf("Error: To specify the version of %s, please set %s.\n",
commandName,
versionVariablesUsed[0],
)
os.Exit(1)
} else if len(versionVariablesUsed) > 1 {
fmt.Println("Multiple version variables found:")
for _, variable := range versionVariablesUsed {
fmt.Println(" " + variable)
}
os.Exit(1)
}
}

if optionVerbose {
fmt.Printf("Setting %s to %s...\n", versionKey, commandVersion)
}
err = os.Setenv(versionKey, commandVersion)
if err != nil {
panic(err)
}
}

project, err := getProject(dockerizedDockerComposeFilePath, true)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -302,7 +352,26 @@ func newSigContext() (context.Context, func()) {
return ctx, cancel
}

func getProject(dockerComposeFilePath string) (*types.Project, error) {
func getRawProject(dockerComposeFilePath string) (*types.Project, error) {
options, err := cli.NewProjectOptions([]string{
dockerComposeFilePath,
},
cli.WithInterpolation(false),
cli.WithLoadOptions(func(l *loader.Options) {
l.SkipValidation = true
l.SkipConsistencyCheck = true
l.SkipNormalization = true
}),
)

if err != nil {
return nil, nil
}

return cli.ProjectFromOptions(options)
}

func getProject(dockerComposeFilePath string, interpolation bool) (*types.Project, error) {
options, err := cli.NewProjectOptions([]string{
dockerComposeFilePath,
},
Expand Down Expand Up @@ -363,7 +432,7 @@ func getBackend() (*api.ServiceProxy, error) {
}

func dockerComposeBuild(dockerComposeFilePath string, buildOptions api.BuildOptions) error {
project, err := getProject(dockerComposeFilePath)
project, err := getProject(dockerComposeFilePath, true)
if err != nil {
return err
}
Expand Down Expand Up @@ -422,12 +491,17 @@ func dockerComposeRun(project *types.Project, runOptions api.RunOptions, volumes
}

func help(dockerComposeFilePath string) error {
project, err := getProject(dockerComposeFilePath)
project, err := getProject(dockerComposeFilePath, false)
if err != nil {
return err
}

fmt.Println("Usage: dockerized [options] <command> [arguments]")
fmt.Println("Usage: dockerized [options] <command>[:version] [arguments]")
fmt.Println("")
fmt.Println("Examples:")
fmt.Println(" dockerized go")
fmt.Println(" dockerized go:1.8 build")
fmt.Println(" dockerized --shell go")
fmt.Println("")
fmt.Println("Commands:")

Expand Down Expand Up @@ -455,10 +529,11 @@ func help(dockerComposeFilePath string) error {
return nil
}

func parseArguments() ([]string, string, []string) {
func parseArguments() ([]string, string, string, []string) {
commandName := ""
var commandArgs []string
var dockerizedOptions []string
var commandVersion string
for _, arg := range os.Args[1:] {
if arg[0] == '-' && commandName == "" {
if contains(options, arg) {
Expand All @@ -475,5 +550,44 @@ func parseArguments() ([]string, string, []string) {
}
}
}
return dockerizedOptions, commandName, commandArgs
if strings.ContainsRune(commandName, ':') {
commandSplit := strings.Split(commandName, ":")
commandName = commandSplit[0]
commandVersion = commandSplit[1]
}
return dockerizedOptions, commandName, commandVersion, commandArgs
}

func unique(s []string) []string {
keys := make(map[string]bool)
var list []string
for _, entry := range s {
if _, value := keys[entry]; !value {
keys[entry] = true
list = append(list, entry)
}
}
return list
}

func ExtractVariables(rawService types.ServiceConfig) []string {
var usedVariables []string
for envKey := range rawService.Environment {
usedVariables = append(usedVariables, envKey)
}
if rawService.Build != nil {
for argKey := range rawService.Build.Args {
usedVariables = append(usedVariables, argKey)
}
}

pattern := regexp.MustCompile(`\$\{([^}]+)\}`)
for _, match := range pattern.FindAllStringSubmatch(rawService.Image, -1) {
usedVariables = append(usedVariables, match[1])
}

usedVariables = unique(usedVariables)
sort.Strings(usedVariables)

return usedVariables
}