Skip to content

Commit

Permalink
refactor: support for go library use case (#7)
Browse files Browse the repository at this point in the history
* refactor: support for go library use case

Signed-off-by: Kaan Yagci <[email protected]>

* test: add expected rendered Dockerfile

Signed-off-by: Kaan Yagci <[email protected]>

* test(core): add render tests

Signed-off-by: Kaan Yagci <[email protected]>

* chore: add test makefile target

Signed-off-by: Kaan Yagci <[email protected]>

* chore(gha): add a workflow to build and test

Signed-off-by: Kaan Yagci <[email protected]>

---------

Signed-off-by: Kaan Yagci <[email protected]>
  • Loading branch information
kaanyagci authored Jun 5, 2024
1 parent 4a69fab commit 4eccb1b
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 45 deletions.
72 changes: 72 additions & 0 deletions .github/workflows/build_and_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Ensure version increment
on:
pull_request:
branches:
- main
types:
- opened
- synchronize
- reopened
paths:
- cli/*$
- core/**
- docker-plugins/**
- example/**
concurrency:
group: "${{ github.workflow_ref }} - ${{ github.ref }} - ${{ github.event_name }}"
cancel-in-progress: true
jobs:
build_cli:
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]
with:
fetch-depth: 1
- name: Setup Go 1.22.x
uses: actions/setup-go@v5
with:
go-version: "1.22.x"
- name: Build the CLI
run: make build
build_plugins:
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]
with:
fetch-depth: 1
- name: Setup Go 1.22.x
uses: actions/setup-go@v5
with:
go-version: "1.22.x"
- name: Build Docker plugins
run: make build-plugins
test_docker_plugins:
runs-on: ubuntu-latest
needs: build_plugins
steps:
- uses: actions/[email protected]
with:
fetch-depth: 1
- name: Setup Go 1.22.x
uses: actions/setup-go@v5
with:
go-version: "1.22.x"
- name: Create plugins directory
run: mkdir -p ~/.docker/cli-plugins/
- name: Build Docker plugins
run: make install-plugins
test:
runs-on: ubuntu-latest
needs: [build_cli, build_plugins]
steps:
- uses: actions/[email protected]
with:
fetch-depth: 1
- name: Setup Go 1.22.x
uses: actions/setup-go@v5
with:
go-version: '1.22.x'
- name: Test
run: make test


4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ install-plugins: clean build-plugins
chmod +x ${PLUGINS_OUTPUT_FOLDER}/docker-*
cp ${PLUGINS_OUTPUT_FOLDER}/* ${DOCKER_PLUGINS_PATH}/

.PHONY: test
test:
go test -v ./core

.PHONY: clean
clean: clean-output-folder

Expand Down
4 changes: 2 additions & 2 deletions cli/wharf.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package main
import (
"fmt"

"github.com/Makepad-fr/wharf/core"
wharf "github.com/Makepad-fr/wharf/core"
)

func main() {
fmt.Println(core.Version)
fmt.Println(wharf.Version)
}
50 changes: 11 additions & 39 deletions core/render.go
Original file line number Diff line number Diff line change
@@ -1,63 +1,35 @@
package core
package wharf

import (
"errors"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"text/template"

"gopkg.in/yaml.v3"
)

var defaultTemplateFileName = "Dockerfile.template"

func init() {
v, ok := os.LookupEnv("DOCKER_RENDER_DEFAULT_TEMPLATE_FILE_NAME")
if ok {
defaultTemplateFileName = v
}
}

// const defaultContextPath =

func Run() error {
os.Args = os.Args[1:]
valuesFilePath := flag.String("values", "./docker-values.yaml", "The path for the values file to use")
outputFilePath := flag.String("output", "", "The path for the output file")
templateFileName := flag.String("file-name", defaultTemplateFileName, "The name of the template file")
flag.Parse()
if flag.NArg() != 1 {
return errors.New("the path for the template file is required\n")
}
contextPath := flag.Arg(0)
template, err := getTemplate(contextPath, *templateFileName)
// Render renders the template file in the given contextPath using the values files from the given path
// It writes the rendered Dockerfile to the io.Writer passed in parameters. It returns an error if something goes wrong
func Render(contextPath, templateFileName, valuesFilePath string, output io.Writer) error {
template, err := getTemplate(contextPath, templateFileName)
if err != nil {
return err
}
values, err := readValues(contextPath, *valuesFilePath)
values, err := readValues(contextPath, valuesFilePath)
if err != nil {
return err
}
err = render(*outputFilePath, template, values)

err = render(template, values, output)
return nil
}

// render renders the template and values to the file with the given path.
// If something goes wrong it returns an error
func render(path string, tmpl *template.Template, values map[string]any) error {
var file *os.File = os.Stdout
var err error
if len(strings.TrimSpace(path)) > 0 {
file, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer file.Close()
}
err = tmpl.Execute(file, values)
func render(tmpl *template.Template, values map[string]any, file io.Writer) error {
err := tmpl.Execute(file, values)
if err != nil {
return err
}
Expand Down
130 changes: 130 additions & 0 deletions core/render_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package wharf

import (
"io"
"os"
"strings"
"testing"
)

const expectedRenderedDockerfile = `
# Use an official base image
FROM ubuntu:latest
# Set the exposed port
EXPOSE 9091
# Set environment variables
ENV DEBUG="false"
ENV LOG_LEVEL="info"
# Copy source code
COPY . /app
# Run command
CMD echo Hello World
`

func TestRenderToString(t *testing.T) {
var stringBuilder strings.Builder
err := Render("../example/", "Dockerfile.template", "docker-values.yaml", &stringBuilder)
if err != nil {
t.Error(err)
}

generated := strings.TrimSpace(stringBuilder.String())
expected := strings.TrimSpace(expectedRenderedDockerfile)

if generated != expected {
t.Log("Strings are not equal")
t.Logf("Generated length: %d", len(generated))
t.Logf("Expected length: %d", len(expected))

for i := 0; i < len(generated) && i < len(expected); i++ {
if generated[i] != expected[i] {
t.Errorf("Difference at char %d: '%c' != '%c'", i, generated[i], expected[i])
break
}
}

if len(generated) != len(expected) {
t.Error("Generated and expected strings have different lengths")
}
}
}

func TestRenderToOutputFile(t *testing.T) {
// Render the template to a file
// Compare result with the expected one
file, err := os.CreateTemp(os.TempDir(), "Dockerfile")
if err != nil {
t.Error(err)
}
defer func() {
file.Close()
os.Remove(file.Name())
}()
err = Render("../example", "Dockerfile.template", "docker-values.yaml", file)
if err != nil {
t.Error(err)
}
equals, err := compareFiles(file, "../example/Dockerfile.expected")
if err != nil {
t.Error(err)
}
if !equals {
t.Error("Files are not equals")
}
}

// CompareFiles checks if the contents of two files are the same.
func compareFiles(file1 *os.File, file2 string) (bool, error) {
file1.Seek(0, io.SeekStart)
f2, err := os.Open(file2)
if err != nil {
return false, err
}
defer f2.Close()

const bufferSize = 4096
buf1 := make([]byte, bufferSize)
buf2 := make([]byte, bufferSize)

for {
n1, err1 := file1.Read(buf1)
n2, err2 := f2.Read(buf2)

if n1 != n2 || err1 != nil || err2 != nil {
if err1 == io.EOF && err2 == io.EOF {
return true, nil // End of both files reached
}
return false, nil
}

if n1 == 0 && n2 == 0 {
break // End of both files reached
}

if !compareChunks(buf1[:n1], buf2[:n2]) {
return false, nil
}
}

return true, nil
}

// compareChunks checks if two byte slices are equal.
func compareChunks(chunk1, chunk2 []byte) bool {
if len(chunk1) != len(chunk2) {
return false
}
for i := range chunk1 {
if chunk1[i] != chunk2[i] {
return false
}
}
return true
}
2 changes: 1 addition & 1 deletion core/version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package core
package wharf

const Version = "0.0.1"
45 changes: 42 additions & 3 deletions docker-plugins/render/docker_render.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,67 @@
package main

import (
"errors"
"flag"
"fmt"
"os"
"strings"

"github.com/Makepad-fr/wharf/core"
wharf "github.com/Makepad-fr/wharf/core"
"github.com/Makepad-fr/wharf/docker-plugins/commons"
)

var metadata = commons.PluginMetadata{
SchemaVersion: "0.1.0",
Vendor: "MAKEPAD",
Version: core.Version,
Version: wharf.Version,
ShortDescription: "Render Dockerfiles from templates",
URL: "https://github.com/Makepad-fr/wharf",
Experimental: true,
}

var defaultTemplateFileName = "Dockerfile.template"

func init() {
v, ok := os.LookupEnv("DOCKER_RENDER_DEFAULT_TEMPLATE_FILE_NAME")
if ok {
defaultTemplateFileName = v
}
}

func run() error {
os.Args = os.Args[1:]
valuesFilePath := flag.String("values", "./docker-values.yaml", "The path for the values file to use")
outputFilePath := flag.String("output", "", "The path for the output file")
templateFileName := flag.String("file-name", defaultTemplateFileName, "The name of the template file")
flag.Parse()
if flag.NArg() != 1 {
return errors.New("the path for the template file is required\n")
}
contextPath := flag.Arg(0)
// If output filepath is an

var file *os.File = os.Stdout

var err error

if outputFilePath != nil && (len(strings.TrimSpace(*outputFilePath)) > 0) {
file, err = os.OpenFile(*outputFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer file.Close()
}

err = wharf.Render(contextPath, *templateFileName, *valuesFilePath, file)
return err
}

func main() {
if commons.ShowPluginMetaData(metadata) {
return
}
err := core.Run()
err := run()
if err != nil {
fmt.Println(err)
flag.Usage()
Expand Down
18 changes: 18 additions & 0 deletions example/Dockerfile.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Use an official base image
FROM ubuntu:latest

# Set the exposed port
EXPOSE 9091

# Set environment variables

ENV DEBUG="false"

ENV LOG_LEVEL="info"


# Copy source code
COPY . /app

# Run command
CMD echo Hello World

0 comments on commit 4eccb1b

Please sign in to comment.