├── go.mod ├── README.md ├── go.sum └── main.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/codenoid/docker-script 2 | 3 | go 1.21.5 4 | 5 | require github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DockerScript 2 | 3 | ![production ready](https://img.shields.io/badge/Project%20Stage-Production%20Ready-brightgreen.svg) 4 | 5 | Embed your project files into single existing Dockerfile file with respect to .dockerignore & GZIP compression support 6 | 7 | Inspired by @adtac's [Gist](https://gist.github.com/adtac/595b5823ef73b329167b815757bbce9f) 8 | 9 | ## Features 10 | 11 | - [x] .dockerignore support 12 | - [x] GZIP Compression Support (~80% Smaller!!!) 13 | 14 | ## How to run 15 | 16 | Install [Go](https://dev.to/codenoid_/easiest-way-to-install-go-on-linux-gim) to follow the step below 17 | 18 | ```sh 19 | go install github.com/codenoid/docker-script@latest # install dockerscript 20 | 21 | cd your-project-which-contains-dockerfile 22 | 23 | # generates Dockerfile.script which will contains all of your project files 24 | docker-script -path . 25 | 26 | # run the generated Dockerfile 27 | ./Dockerfile.script 28 | ``` 29 | 30 | ## Is this ready for production? 31 | 32 | yeah yeah, it's ready, but you may need to test your Dockerfile.script first, you may want to change something -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= 6 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= 7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 8 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 9 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 12 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "compress/gzip" 7 | "encoding/base64" 8 | "flag" 9 | "fmt" 10 | "io" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | 15 | ignore "github.com/sabhiram/go-gitignore" 16 | ) 17 | 18 | var ignorePattern *ignore.GitIgnore 19 | 20 | func main() { 21 | dockerfilePath := "" 22 | flag.StringVar(&dockerfilePath, "path", ".", "Path to the directory containing the Dockerfile") 23 | flag.Parse() 24 | 25 | fullDockerfilePath := filepath.Join(dockerfilePath, "Dockerfile") 26 | gitignorePath := filepath.Join(dockerfilePath, ".gitignore") 27 | dockerignorePath := filepath.Join(dockerfilePath, ".dockerignore") 28 | 29 | if pathfile, err := os.Stat(gitignorePath); err != nil || !pathfile.IsDir() { 30 | ignorePattern, _ = ignore.CompileIgnoreFile(gitignorePath) 31 | } 32 | 33 | if pathfile, err := os.Stat(dockerignorePath); err != nil || !pathfile.IsDir() { 34 | ignorePattern, _ = ignore.CompileIgnoreFile(dockerignorePath) 35 | } 36 | 37 | file, err := os.Open(fullDockerfilePath) 38 | if err != nil { 39 | fmt.Printf("Error opening Dockerfile in %s: %s\n", dockerfilePath, err) 40 | return 41 | } 42 | defer file.Close() 43 | 44 | newFilePath := filepath.Join(dockerfilePath, "Dockerfile.script") 45 | newFile, err := os.Create(newFilePath) 46 | if err != nil { 47 | fmt.Printf("Error creating Dockerfile.script in %s: %s\n", dockerfilePath, err) 48 | return 49 | } 50 | defer newFile.Close() 51 | 52 | writeShebang(newFile) 53 | fmt.Println(dockerfilePath) 54 | copyDockerfileContent(file, newFile, dockerfilePath) 55 | 56 | fmt.Printf("Dockerfile.script created successfully in %s\n", dockerfilePath) 57 | } 58 | 59 | func writeShebang(file *os.File) { 60 | fmt.Fprintln(file, `#!/usr/bin/env -S bash -c "docker run --network host -it --rm \$(docker build --progress plain -f \$0 . 2>&1 | tee /dev/stderr | grep 'writing image sha256:' | grep -oP 'sha256:[0-9a-f]+' | cut -d' ' -f1)"`) 61 | } 62 | 63 | func copyDockerfileContent(originalFile *os.File, newFile *os.File, directory string) error { 64 | scanner := bufio.NewScanner(originalFile) 65 | fromCopied := false 66 | copyBefore := "" 67 | 68 | copyCmd := []string{"ADD", "COPY"} 69 | content, _ := io.ReadAll(originalFile) 70 | lines := strings.Split(string(content), "\n") 71 | 72 | for _, line := range lines { 73 | line = strings.ToUpper(strings.TrimSpace(line)) 74 | for _, cmd := range copyCmd { 75 | if strings.HasPrefix(line, cmd) { 76 | copyBefore = cmd 77 | goto APPEND 78 | } 79 | } 80 | } 81 | 82 | APPEND: 83 | for i, line := range lines { 84 | 85 | // Write the line to the new file 86 | if _, err := fmt.Fprintln(newFile, line); err != nil { 87 | return err 88 | } 89 | 90 | // Check if the line is a FROM directive 91 | if !fromCopied && copyBefore != "" { 92 | if len(lines) > i+1 && strings.Contains(lines[i+1], copyBefore) { 93 | fromCopied = true 94 | 95 | // After copying FROM line, embed project files 96 | embedProjectFiles(directory, newFile) 97 | } 98 | } 99 | } 100 | 101 | return scanner.Err() 102 | } 103 | 104 | func embedProjectFiles(directory string, newFile *os.File) { 105 | err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { 106 | if err != nil { 107 | return err 108 | } 109 | 110 | relativePath, err := filepath.Rel(directory, path) 111 | if err != nil { 112 | return err 113 | } 114 | 115 | if ignorePattern != nil { 116 | // Ignore files based on .dockerignore patterns 117 | isSkip := ignorePattern.MatchesPath(relativePath) 118 | if relativePath == "Dockerfile" { 119 | isSkip = true 120 | } 121 | if isSkip { 122 | return nil 123 | } 124 | } 125 | 126 | if info.IsDir() { 127 | return nil 128 | } 129 | 130 | fileContent, err := os.ReadFile(path) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | // Compress the file content using gzip 136 | var gzipBuffer bytes.Buffer 137 | gzipWriter := gzip.NewWriter(&gzipBuffer) 138 | _, err = gzipWriter.Write(fileContent) 139 | if err != nil { 140 | gzipWriter.Close() 141 | return err 142 | } 143 | gzipWriter.Close() 144 | 145 | // Encode the compressed content to base64 146 | encodedContent := base64.StdEncoding.EncodeToString(gzipBuffer.Bytes()) 147 | fmt.Fprintf(newFile, "RUN mkdir -p %s\n", getParentPath(relativePath)) 148 | fmt.Fprintf(newFile, "RUN echo '%s' | base64 -d | gunzip > %s\n", encodedContent, relativePath) 149 | 150 | return nil 151 | }) 152 | 153 | if err != nil { 154 | fmt.Printf("Error embedding project files: %s\n", err) 155 | } 156 | } 157 | 158 | func getParentPath(fullPath string) string { 159 | // Clean the path to fix any irregularities 160 | fullPath = filepath.Clean(fullPath) 161 | 162 | // Split the path into directory and base 163 | dir, _ := filepath.Split(fullPath) 164 | 165 | // Clean the directory to remove the trailing slash 166 | return filepath.Clean(dir) 167 | } 168 | --------------------------------------------------------------------------------