Skip to content

Commit 6df154c

Browse files
Merge pull request #16 from datastack-net/feature/version
Feature/version
2 parents 2682d9d + 34749db commit 6df154c

File tree

4 files changed

+140
-13
lines changed

4 files changed

+140
-13
lines changed

.env

+6-1
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,32 @@ GIT_VERSION=2.32.0
1313
GH_VERSION=2.5.2
1414
GO_VERSION=1.17.8
1515
HASKELL_VERSION=9.2.2
16+
GHCI_VERSION=${HASKELL_VERSION}
1617
HELM_VERSION=3.8.1
1718
HTTPIE_VERSION=2.6.0
19+
HTTP_VERSION=${HTTPIE_VERSION}
1820
JAVA_VERSION=17.0.2
1921
LUA_IMAGE=nickblah/lua
2022
LUA_VERSION=5.4.3
23+
NODE_VERSION=17.7.2
2124
PERL_VERSION=5.34.1
2225
PHP_VERSION=8.1.3
2326
PROTOC_VERSION=3.9.1
2427
PROTOC_ARCH=${DEFAULT_ARCH}
2528
PROTOC_BASE=${DEFAULT_BASE}
2629
POSTGRES_VERSION=14.2
30+
PSQL_VERSION=${POSTGRES_VERSION}
2731
PYTHON_VERSION=3.10
2832
PYTHON2_VERSION=2.7.18
2933
RUBY_VERSION=3.1.1
3034
RUST_VERSION=1.59.0
35+
RUSTC_VERSION=${RUST_VERSION}
3136
S3CMD_BASE=python:${PYTHON_VERSION}-${DEFAULT_BASE_IMAGE}
3237
S3CMD_VERSION=2.2.0
3338
SWAGGER_CODEGEN_VERSION=3.0.29
3439
SWIPL_VERSION=8.5.5
35-
NODE_VERSION=16.13.0
3640
TYPESCRIPT_VERSION=4.6.2
3741
TSC_VERSION=${TYPESCRIPT_VERSION}
42+
TSC_VERSION=${TYPESCRIPT_VERSION}
3843
VUE_VERSION=5.0.1
3944
YOUTUBE_DL_VERSION=2022.03.08.2

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,14 @@ This allows you to "lock" your tools to specific versions for your project.
187187
- Create a `dockerized.env` file in your project directory.
188188
- All commands executed within this directory will use the settings specified in this file.
189189

190+
**Ad-hoc**
191+
192+
Add `:<version>` to the end of the command to override the version.
193+
194+
```shell
195+
dockerized node:15
196+
```
197+
190198
**Ad-hoc (Unix)**
191199

192200
- Override the environment variable before the command, to specify the version for that command.

docker-compose.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ services:
7171
image: "alpine/git:v${GIT_VERSION}"
7272
entrypoint: [ "git" ]
7373
ghci:
74-
image: "haskell:${HASKELL_VERSION}"
74+
image: "haskell:${GHCI_VERSION}"
7575
entrypoint: [ "ghci" ]
7676
go:
7777
image: "golang:${GO_VERSION}"
@@ -84,7 +84,7 @@ services:
8484
helm:
8585
image: "alpine/helm:${HELM_VERSION}"
8686
http:
87-
image: "alpine/httpie:${HTTPIE_VERSION}"
87+
image: "alpine/httpie:${HTTP_VERSION}"
8888
entrypoint: [ "http" ]
8989
java:
9090
image: "openjdk:${JAVA_VERSION}"
@@ -113,7 +113,7 @@ services:
113113
php:
114114
image: "php:${PHP_VERSION}"
115115
psql:
116-
image: "postgres:${POSTGRES_VERSION}"
116+
image: "postgres:${PSQL_VERSION}"
117117
entrypoint: [ "psql" ]
118118
protoc:
119119
image: "protoc:${PROTOC_VERSION}"
@@ -145,7 +145,7 @@ services:
145145
<<: *ruby
146146
entrypoint: [ "gem" ]
147147
rustc:
148-
image: "rust:${RUST_VERSION}"
148+
image: "rust:${RUSTC_VERSION}"
149149
entrypoint: [ "rustc" ]
150150
s3cmd:
151151
build:

lib/dockerized.go

+122-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"github.com/compose-spec/compose-go/cli"
7+
"github.com/compose-spec/compose-go/loader"
78
"github.com/compose-spec/compose-go/types"
89
"github.com/docker/cli/cli/command"
910
"github.com/docker/cli/cli/flags"
@@ -14,6 +15,7 @@ import (
1415
"os"
1516
"os/signal"
1617
"path/filepath"
18+
"regexp"
1719
"sort"
1820
"strings"
1921
"syscall"
@@ -34,7 +36,7 @@ var options = []string{
3436
func main() {
3537
normalizeEnvironment()
3638

37-
dockerizedOptions, commandName, commandArgs := parseArguments()
39+
dockerizedOptions, commandName, commandVersion, commandArgs := parseArguments()
3840

3941
var optionHelp = contains(dockerizedOptions, "--help") || contains(dockerizedOptions, "-h")
4042
var optionVerbose = contains(dockerizedOptions, "--verbose") || contains(dockerizedOptions, "-v")
@@ -76,7 +78,55 @@ func main() {
7678
panic(err)
7779
}
7880

79-
project, err := getProject(dockerizedDockerComposeFilePath)
81+
if commandVersion != "" {
82+
rawProject, err := getRawProject(dockerizedDockerComposeFilePath)
83+
if err != nil {
84+
panic(err)
85+
}
86+
87+
rawService, err := rawProject.GetService(commandName)
88+
89+
var versionVariableExpected = strings.ReplaceAll(strings.ToUpper(commandName), "-", "_") + "_VERSION"
90+
var versionVariablesUsed []string
91+
for _, variable := range ExtractVariables(rawService) {
92+
if strings.HasSuffix(variable, "_VERSION") {
93+
versionVariablesUsed = append(versionVariablesUsed, variable)
94+
}
95+
}
96+
97+
if len(versionVariablesUsed) == 0 {
98+
fmt.Printf("Error: Version selection for %s is currently not supported.\n", commandName)
99+
os.Exit(1)
100+
}
101+
102+
versionKey := versionVariableExpected
103+
104+
if !contains(versionVariablesUsed, versionVariableExpected) {
105+
if len(versionVariablesUsed) == 1 {
106+
fmt.Printf("Error: To specify the version of %s, please set %s.\n",
107+
commandName,
108+
versionVariablesUsed[0],
109+
)
110+
os.Exit(1)
111+
} else if len(versionVariablesUsed) > 1 {
112+
fmt.Println("Multiple version variables found:")
113+
for _, variable := range versionVariablesUsed {
114+
fmt.Println(" " + variable)
115+
}
116+
os.Exit(1)
117+
}
118+
}
119+
120+
if optionVerbose {
121+
fmt.Printf("Setting %s to %s...\n", versionKey, commandVersion)
122+
}
123+
err = os.Setenv(versionKey, commandVersion)
124+
if err != nil {
125+
panic(err)
126+
}
127+
}
128+
129+
project, err := getProject(dockerizedDockerComposeFilePath, true)
80130
if err != nil {
81131
panic(err)
82132
}
@@ -302,7 +352,26 @@ func newSigContext() (context.Context, func()) {
302352
return ctx, cancel
303353
}
304354

305-
func getProject(dockerComposeFilePath string) (*types.Project, error) {
355+
func getRawProject(dockerComposeFilePath string) (*types.Project, error) {
356+
options, err := cli.NewProjectOptions([]string{
357+
dockerComposeFilePath,
358+
},
359+
cli.WithInterpolation(false),
360+
cli.WithLoadOptions(func(l *loader.Options) {
361+
l.SkipValidation = true
362+
l.SkipConsistencyCheck = true
363+
l.SkipNormalization = true
364+
}),
365+
)
366+
367+
if err != nil {
368+
return nil, nil
369+
}
370+
371+
return cli.ProjectFromOptions(options)
372+
}
373+
374+
func getProject(dockerComposeFilePath string, interpolation bool) (*types.Project, error) {
306375
options, err := cli.NewProjectOptions([]string{
307376
dockerComposeFilePath,
308377
},
@@ -363,7 +432,7 @@ func getBackend() (*api.ServiceProxy, error) {
363432
}
364433

365434
func dockerComposeBuild(dockerComposeFilePath string, buildOptions api.BuildOptions) error {
366-
project, err := getProject(dockerComposeFilePath)
435+
project, err := getProject(dockerComposeFilePath, true)
367436
if err != nil {
368437
return err
369438
}
@@ -422,12 +491,17 @@ func dockerComposeRun(project *types.Project, runOptions api.RunOptions, volumes
422491
}
423492

424493
func help(dockerComposeFilePath string) error {
425-
project, err := getProject(dockerComposeFilePath)
494+
project, err := getProject(dockerComposeFilePath, false)
426495
if err != nil {
427496
return err
428497
}
429498

430-
fmt.Println("Usage: dockerized [options] <command> [arguments]")
499+
fmt.Println("Usage: dockerized [options] <command>[:version] [arguments]")
500+
fmt.Println("")
501+
fmt.Println("Examples:")
502+
fmt.Println(" dockerized go")
503+
fmt.Println(" dockerized go:1.8 build")
504+
fmt.Println(" dockerized --shell go")
431505
fmt.Println("")
432506
fmt.Println("Commands:")
433507

@@ -455,10 +529,11 @@ func help(dockerComposeFilePath string) error {
455529
return nil
456530
}
457531

458-
func parseArguments() ([]string, string, []string) {
532+
func parseArguments() ([]string, string, string, []string) {
459533
commandName := ""
460534
var commandArgs []string
461535
var dockerizedOptions []string
536+
var commandVersion string
462537
for _, arg := range os.Args[1:] {
463538
if arg[0] == '-' && commandName == "" {
464539
if contains(options, arg) {
@@ -475,5 +550,44 @@ func parseArguments() ([]string, string, []string) {
475550
}
476551
}
477552
}
478-
return dockerizedOptions, commandName, commandArgs
553+
if strings.ContainsRune(commandName, ':') {
554+
commandSplit := strings.Split(commandName, ":")
555+
commandName = commandSplit[0]
556+
commandVersion = commandSplit[1]
557+
}
558+
return dockerizedOptions, commandName, commandVersion, commandArgs
559+
}
560+
561+
func unique(s []string) []string {
562+
keys := make(map[string]bool)
563+
var list []string
564+
for _, entry := range s {
565+
if _, value := keys[entry]; !value {
566+
keys[entry] = true
567+
list = append(list, entry)
568+
}
569+
}
570+
return list
571+
}
572+
573+
func ExtractVariables(rawService types.ServiceConfig) []string {
574+
var usedVariables []string
575+
for envKey := range rawService.Environment {
576+
usedVariables = append(usedVariables, envKey)
577+
}
578+
if rawService.Build != nil {
579+
for argKey := range rawService.Build.Args {
580+
usedVariables = append(usedVariables, argKey)
581+
}
582+
}
583+
584+
pattern := regexp.MustCompile(`\$\{([^}]+)\}`)
585+
for _, match := range pattern.FindAllStringSubmatch(rawService.Image, -1) {
586+
usedVariables = append(usedVariables, match[1])
587+
}
588+
589+
usedVariables = unique(usedVariables)
590+
sort.Strings(usedVariables)
591+
592+
return usedVariables
479593
}

0 commit comments

Comments
 (0)