Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: support for go library use case #7

Merged
merged 5 commits into from
Jun 5, 2024
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
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
Loading