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

chore: support include #16

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
12 changes: 12 additions & 0 deletions core/fs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package wharf

import "os"

// IsDirectory checks if the given path is a directory
func isDirectory(path string) (bool, error) {
info, err := os.Stat(path)
if err != nil {
return false, err
}
return info.IsDir(), nil
}
82 changes: 82 additions & 0 deletions core/function_factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package wharf

import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"

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

// newFunctionFactory creates a new function factory witht the given context path
func newFunctionFactory(contextPath string) functionFactory {
return functionFactory{
contextPath: contextPath,
}
}

// functionFactory crates a context aware functionMap for each template.
type functionFactory struct {
contextPath string // THe path of the current template file that executes this function map
}

// Include includdes returns the content of as string. The [path] should either be
// a Dockerfile or a Dockerfile.template. If it's a Dockefile.template the [values] will
// be used to render the template using wharf. If it's a Dockerfile the cotnent will be rendered as it's.
// If any erorr happens it will returned as an error string as template function can not return errors as Go objects.
// FIXME: Users may use other filenames other then Dockerfile and Dockerifle.template especially when they are using from the same folder
func (f functionFactory) Include(path string, values types.ValueMap) string {
// Update the passed path with the context path
path = filepath.Join(f.contextPath, path)
isDir, err := isDirectory(path)

if err != nil {
return fmt.Sprintf("Error while importing %s: %s", path, err.Error())
}
// Create a generic error message template
const errorMessage = "Error: Include should be called either with a Dockerfile.template path or with a Dockerfile path"
if isDir {
return errorMessage
} else {
baseFileName := filepath.Base(path)
if baseFileName == "Dockerfile" {
// If the path is a Dockerfile path
file, err := os.Open(path)
if err != nil {
return fmt.Sprintf("Error: error while opening Dockerfile from %s", path)
}
content, err := io.ReadAll(file)
if err != nil {
return fmt.Sprintf("Error: error while reading file content %s", err.Error())
}
return string(content)
}
if baseFileName == "Dockerfile.template" {
// IF the path is a Dockerfile.template
contextPath := filepath.Dir(path)
// Don't use include in included templates as it creates a cyclic dependency
// TODO: Maybe we can copy the funcMap before using it?
template, err := getTemplate(contextPath, baseFileName, map[string]any{})
if err != nil {
return fmt.Sprintf("Error: error while getting template from %s: %s", path, err.Error())
}
var renderedContent bytes.Buffer
err = render(template, values, &renderedContent)
if err != nil {
return fmt.Sprintf("Error: error while rendering template %s: %s", path, err.Error())
}
return renderedContent.String()
}
return errorMessage
}
}

// Creates the types.FuncMap for the related function factory
// FIXME: Check if it's possible to create this function map programmatically
func (f functionFactory) FuncMap() types.FuncMap {
return map[string]any{
"include": f.Include,
}
}
14 changes: 8 additions & 6 deletions core/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import (
// 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)
// Create a new function map
funcMap := newFunctionFactory(contextPath).FuncMap()
template, err := getTemplate(contextPath, templateFileName, funcMap)
if err != nil {
return err
}
Expand All @@ -23,12 +25,13 @@ func Render(contextPath, templateFileName, valuesFilePath string, output io.Writ
}

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

// render renders the template and values to the file with the given path.
// If something goes wrong it returns an error
func render(tmpl *template.Template, values map[string]any, file io.Writer) error {
// Include function map to the current template just before rendering the values
err := tmpl.Execute(file, values)
if err != nil {
return err
Expand Down Expand Up @@ -56,21 +59,20 @@ func readValues(contextPath, valuesFilePath string) (map[string]any, error) {
}

// getTemplate returns the template.Template from given context path and templatefile name
func getTemplate(contextPath, templateFileName string) (*template.Template, error) {
func getTemplate(contextPath, templateFileName string, funcMap template.FuncMap) (*template.Template, error) {
info, err := os.Stat(contextPath)
if err != nil {
return nil, err
}
if !info.IsDir() {
return nil, fmt.Errorf("Context path %s should be a directory path. To pass template file name use -file-name option", contextPath)
return nil, fmt.Errorf("context path %s should be a directory path. To pass template file name use -file-name option", contextPath)
}

contextPath = filepath.Join(contextPath, templateFileName)

templ, err := template.ParseFiles(contextPath)
templ, err := template.New(filepath.Base(templateFileName)).Funcs(funcMap).ParseFiles(contextPath)
if err != nil {
return nil, err
}

return templ, nil
}
3 changes: 2 additions & 1 deletion go.work
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
go 1.22.2
go 1.22.4

use (
./cli
./core
./docker-cli-plugins/commons
./docker-cli-plugins/render
./types
)
3 changes: 3 additions & 0 deletions types/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/Makepad-fr/wharf/types

go 1.22.4
9 changes: 9 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package types

import "text/template"

// TODO: template.FuncMap uses map[string]any and any is a function with an arbitrary number of arguments func(args ....), we need to check if we can create a type for that
type FuncMap = template.FuncMap

// TODO: Change to more restrictive type as it will be used only for values
type ValueMap = map[string]any
Loading