Skip to content
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
65 changes: 38 additions & 27 deletions pkg/build/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,51 @@ import (
var dockerfileTemplates embed.FS

type GenerateDockerfileOptions struct {
GoMainPackageDirectory string
BuildContext string
GoMainPackageDirectory string
}

func generateDockerfile(
projectFile string,
projectFileRelativePath string,
options GenerateDockerfileOptions,
) (string, string, error) {
directory, err := os.MkdirTemp("", "3lv-build-*")
if err != nil {
return "", "", fmt.Errorf("Failed to create temporary directory: %w", err)
}

projectFileBase := path.Base(projectFile)
projectFile := getProjectFilePathRelativeToBuildContext(projectFileRelativePath, options.BuildContext)
buildContext := getBuildContextFromProjectFile(projectFileRelativePath, options.BuildContext)

projectFileBase := path.Base(projectFileRelativePath) // could have used `projectFile` here also, doesn't matter

if strings.HasSuffix(projectFileBase, ".csproj") {
dockerfile, buildContext, err := generateDockerfileForDotNet(
dockerfile, err := generateDockerfileForDotNet(
projectFile,
buildContext,
directory,
options,
)
if err != nil {
return "", "", fmt.Errorf("Failed to generate Dockerfile for .NET project: %w", err)
}

return dockerfile, buildContext, nil
} else if projectFileBase == "go.mod" {
dockerfile, buildContext, err := generateDockerfileForGo(
projectFile,
dockerfile, err := generateDockerfileForGo(
buildContext,
directory,
options,
options.GoMainPackageDirectory,
)
if err != nil {
return "", "", fmt.Errorf("Failed to generate Dockerfile for Go project: %w", err)
}

return dockerfile, buildContext, nil
} else if projectFileBase == "pyproject.toml" {
dockerfile, buildContext, err := generateDockerfileForPython(
dockerfile, err := generateDockerfileForPython(
projectFile,
buildContext,
directory,
options,
)
if err != nil {
return "", "", fmt.Errorf("Failed to generate Dockerfile for Python project: %w", err)
Expand All @@ -63,37 +66,45 @@ func generateDockerfile(
} else if strings.HasPrefix(projectFileBase, "Dockerfile") ||
strings.HasSuffix(projectFileBase, "Dockerfile") ||
strings.Contains(projectFileBase, "Dockerfile") {
if options.BuildContext == "" {
return projectFile, path.Dir(projectFile), nil
}

return projectFile, options.BuildContext, nil
return projectFileRelativePath, buildContext, nil
}

return "", "", fmt.Errorf(
"Unsupported project file: %s. If you want to use a Dockerfile directly,"+
" ensure the name of the Dockerfile contains the string 'Dockerfile'",
projectFileBase,
projectFileRelativePath,
)
}

func getProjectFileAndBuildContext(
func getProjectFilePathRelativeToBuildContext(
projectFileRelativePath string,
buildContextRelativePath string,
) (string, string) {
if len(buildContextRelativePath) == 0 {
return path.Base(projectFileRelativePath), path.Dir(projectFileRelativePath)
) string {
if buildContextRelativePath == "" {
return path.Base(projectFileRelativePath)
}

if strings.HasSuffix(buildContextRelativePath, "/") {
return strings.TrimPrefix(
projectFileRelativePath,
buildContextRelativePath,
), buildContextRelativePath
}
buildContextRelativePath = strings.TrimSuffix(buildContextRelativePath, "/")

return strings.TrimPrefix(
projectFileRelativePath,
buildContextRelativePath+"/",
), buildContextRelativePath
)
}

func getBuildContextFromProjectFile(
projectFileRelativePath string,
buildContextRelativePath string,
) string {
if buildContextRelativePath == "" {
projectFileDirectory := path.Dir(projectFileRelativePath)

if projectFileDirectory == "" {
return "."
}

return projectFileDirectory
}

return strings.TrimSuffix(buildContextRelativePath, "/")
}
100 changes: 36 additions & 64 deletions pkg/build/generate_dotnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,66 @@

import (
"encoding/xml"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"

"github.com/3lvia/cli/pkg/utils"
)

type DockerfileVariablesDotnet struct {
CsprojFile string // required
AssemblyName string // required
BaseImageTag string // required
RuntimeBaseImage string // required
CsprojFile string
AssemblyName string
BaseImageTag string
RuntimeBaseImage string
}

func generateDockerfileForDotNet(
projectFile string,
buildContext string,
directory string,
options GenerateDockerfileOptions,
) (string, string, error) {
csprojFileName, buildContext := getProjectFileAndBuildContext(
projectFile,
options.BuildContext,
)

assemblyName, err := findAssemblyName(
projectFile,
csprojFileName,
)
) (string, error) {
csprojXML, err := getXMLFromFile(path.Join(buildContext, projectFile))
if err != nil {
return "", "", err
return "", err
}

baseImageTag, err := findBaseImageTag(projectFile)
assemblyName, err := findAssemblyName(projectFile, csprojXML)
if err != nil {
return "", "", err
return "", err
}

runtimeBaseImage, err := findRuntimeBaseImage(projectFile)
baseImageTag, err := findBaseImageTag(csprojXML)
if err != nil {
return "", "", err
return "", err
}

const templateFile = "Dockerfile.dotnet.tmpl"
runtimeBaseImage, err := findRuntimeBaseImage(csprojXML)
if err != nil {
return "", err
}

dockerfilePath, err := utils.WriteFileWithTemplate(
directory,
"Dockerfile",
templateFile,
"Dockerfile.dotnet.tmpl",
dockerfileTemplates,
DockerfileVariablesDotnet{
CsprojFile: csprojFileName,
CsprojFile: projectFile,
AssemblyName: assemblyName,
BaseImageTag: baseImageTag,
RuntimeBaseImage: runtimeBaseImage,
},
)
if err != nil {
return "", "", err
return "", err
}

return dockerfilePath, buildContext, nil
return dockerfilePath, nil
}

type CSharpProjectFile struct {
Expand All @@ -79,78 +76,53 @@
}

func getXMLFromFile(fileName string) (*CSharpProjectFile, error) {
var project CSharpProjectFile

file, err := os.Open(fileName)
if err != nil {
return nil, fmt.Errorf("getXMLFromFile: Failed to open file: %w", err)
return &project, fmt.Errorf("Failed to open file: %w", err)
}

defer file.Close()

bytes, err := io.ReadAll(file)
if err != nil {
return nil, fmt.Errorf("getXMLFromFile: Failed to read file: %w", err)
return &project, fmt.Errorf("Failed to read file: %w", err)
}

var project CSharpProjectFile

err = xml.Unmarshal(bytes, &project)
if err != nil {
return nil, fmt.Errorf("getXMLFromFile: Failed to unmarshal file: %w", err)
return &project, fmt.Errorf("Failed to unmarshal file: %w", err)
}

return &project, nil
}

func findAssemblyName(
csprojFileRelativePath string,
csprojFileName string,
) (string, error) {
var assemblyName string

csprojXML, err := getXMLFromFile(csprojFileRelativePath)
if err != nil {
return "", err
}

assemblyName = csprojXML.PropertyGroup.AssemblyName
func findAssemblyName(csprojFileName string, csprojXML *CSharpProjectFile) (string, error) {

Check failure on line 101 in pkg/build/generate_dotnet.go

View workflow job for this annotation

GitHub Actions / Lint

findAssemblyName - result 1 (error) is always nil (unparam)
assemblyName := csprojXML.PropertyGroup.AssemblyName

if len(assemblyName) == 0 {
basename := filepath.Base(csprojFileName)
withoutExtension := strings.TrimSuffix(basename, filepath.Ext(basename))
withoutExtension := strings.TrimSuffix(path.Base(csprojFileName), filepath.Ext(csprojFileName))

return withoutExtension + ".dll", nil
}

return assemblyName + ".dll", nil
}

func findBaseImageTag(csprojFileRelativePath string) (string, error) {
csprojXML, err := getXMLFromFile(csprojFileRelativePath)
if err != nil {
return "", err
}

func findBaseImageTag(csprojXML *CSharpProjectFile) (string, error) {
targetFramework := csprojXML.PropertyGroup.TargetFramework
if len(targetFramework) == 0 {
return "", fmt.Errorf(
"findBaseImageTag: TargetFramework not found in csproj file: %s",
csprojFileRelativePath,
)
return "", errors.New("TargetFramework not found in .csproj-file.")
}

return targetFramework[3:] + "-alpine", nil
}

func findRuntimeBaseImage(csprojFileRelativePath string) (string, error) {
csprojXML, err := getXMLFromFile(csprojFileRelativePath)
if err != nil {
return "", err
}

func findRuntimeBaseImage(csprojXML *CSharpProjectFile) (string, error) {
sdk := csprojXML.SDK
if len(sdk) == 0 {
return "", fmt.Errorf(
"SDK not found in csproj file: %s",
csprojFileRelativePath,
)
return "", errors.New("SDK not found in .csproj-file.")
}

switch sdk {
Expand Down
49 changes: 7 additions & 42 deletions pkg/build/generate_go.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package build

import (
"strings"

"github.com/3lvia/cli/pkg/utils"
)

Expand All @@ -12,18 +10,13 @@ type DockerfileVariablesGo struct {
}

func generateDockerfileForGo(
projectFile string,
buildContext string,
directory string,
options GenerateDockerfileOptions,
) (string, string, error) {
goModuleDirectory, buildContext := getGoModuleDirectoryAndBuildContext(
projectFile,
options.BuildContext,
)

goMainPackageDirectory string,
) (string, error) {
dockerfileVariables := DockerfileVariablesGo{
GoModuleDirectory: goModuleDirectory,
MainPackageDirectory: options.GoMainPackageDirectory,
GoModuleDirectory: buildContext,
MainPackageDirectory: goMainPackageDirectory,
}

const templateFile = "Dockerfile.go.tmpl"
Expand All @@ -36,36 +29,8 @@ func generateDockerfileForGo(
dockerfileVariables,
)
if err != nil {
return "", "", err
}

return dockerfilePath, buildContext, nil
}

func getGoModuleDirectoryAndBuildContext(
projectFileRelativePath string,
buildContextRelativePath string,
) (string, string) {
projectFileName, buildContext := getProjectFileAndBuildContext(
projectFileRelativePath,
buildContextRelativePath,
)

return dotIfEmpty(
strings.TrimSuffix(
strings.TrimSuffix(
projectFileName,
"go.mod",
),
"/",
),
), buildContext
}

func dotIfEmpty(str string) string {
if len(str) == 0 {
return "."
return "", err
}

return str
return dockerfilePath, nil
}
Loading
Loading