Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ COPY . .
{{ $c }}
{{- end }}

RUN go build -tags strictfipsruntime -o /usr/bin/main ./{{.main}}
RUN go build -tags strictfipsruntime -o /usr/bin/main {{.main}}

FROM $GO_RUNTIME

Expand Down
9 changes: 8 additions & 1 deletion pkg/dockerfilegen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,19 @@ func GenerateDockerfiles(params Params) error {
return nil
}

mainPackagesPaths.Insert(filepath.Dir(path))
mainPackagesPaths.Insert("./" + filepath.Dir(path))
return nil
}); err != nil {
return errors.WithStack(err)
}

if params.ScanImports {
if err := scanImports(mainPackagesPaths, params.RootDir,
params.ScanImportsSubPackages, params.ScanImportsTags); err != nil {
return err
}
}

for _, p := range mainPackagesPaths.UnsortedList() {
log.Println("Main package path:", p)
}
Expand Down
190 changes: 190 additions & 0 deletions pkg/dockerfilegen/imports/scanner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package imports

import (
"fmt"
"go/build"
"go/parser"
"go/token"
"io/fs"
"os"
"path"
"path/filepath"
"strings"

"github.com/pkg/errors"
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
"k8s.io/apimachinery/pkg/util/sets"
)

var (
ErrCompileError = fmt.Errorf("could't compile")
ErrIO = fmt.Errorf("io fail")
)

// ScanForMains will scan a root dir, in specified packages to collect the
// packages that have a main() function. It works for vendorless and vendorful
// projects.
func ScanForMains(rootDir string, packages []string, tags []string) (sets.Set[string], error) {
pkgs := sets.New[string]()
collctr, err := collector(rootDir, pkgs)
if err != nil {
return nil, err
}
for _, subpkg := range packages {
if err := filepath.WalkDir(path.Join(rootDir, subpkg), scanner(collctr, tags)); err != nil {
return nil, err
}
}

return pkgs, nil
}

type collectOnlyMainFn func(imprts []string) error

func collector(rootDir string, pkgs sets.Set[string]) (collectOnlyMainFn, error) {
gomodPath := path.Join(rootDir, "go.mod")
contents, err := os.ReadFile(gomodPath)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrIO, errors.WithStack(err))
}
gm, err := modfile.ParseLax(gomodPath, contents, nil)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrCompileError, errors.WithStack(err))
}
return func(imprts []string) error {
for _, imprt := range imprts {
ok, err := isDepMainPackage(rootDir, gm, imprt)
if err != nil {
return err
}
if ok {
pkgs.Insert(imprt)
}
}
return nil
}, nil
}

func isDepMainPackage(rootDir string, gm *modfile.File, imprt string) (bool, error) {
// within repo
if strings.HasPrefix(imprt, gm.Module.Mod.Path) {
subimprt := strings.TrimPrefix(imprt, gm.Module.Mod.Path)
pkgPath := path.Join(rootDir, subimprt)
return isMainPkg(pkgPath)
}
// try vendor
pkgPath := path.Join(rootDir, "vendor", imprt)
fi, err := os.Stat(pkgPath)
if err == nil && fi.IsDir() {
return isMainPkg(pkgPath)
}
// modules
var mod module.Version
for _, req := range gm.Require {
if strings.HasPrefix(imprt, req.Mod.Path) {
mod = req.Mod
break
}
}
for _, repl := range gm.Replace {
if repl.Old.Path == mod.Path {
mod = repl.New
break
}
}
subIprt := strings.TrimPrefix(imprt, mod.Path)
pkgPath = path.Join(goModCache(), mod.Path+"@"+mod.Version, subIprt)
return isMainPkg(pkgPath)
}

func isMainPkg(pkgPath string) (bool, error) {
response := false
if err := filepath.WalkDir(pkgPath, func(path string, info fs.DirEntry, err error) error {
if err != nil || info.IsDir() || !strings.HasSuffix(info.Name(), ".go") {
if path != pkgPath && info.IsDir() {
return filepath.SkipDir
}
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("%w: ReadFile %s failed: %w",
ErrIO, path, errors.WithStack(err))
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path, string(content), parser.PackageClauseOnly)
if err != nil {
return fmt.Errorf("%w: %w", ErrCompileError, errors.WithStack(err))
}
if f.Name.Name == "main" {
response = true
return filepath.SkipAll
}
return nil
}); err != nil {
return false, err
}
return response, nil
}

func scanner(collectOnlyMainFn collectOnlyMainFn, tags []string) fs.WalkDirFunc {
// TODO: limit the Go files to the tags provided
return func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || !strings.HasSuffix(d.Name(), ".go") {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("%w: ReadFile %s failed: %w",
ErrIO, path, errors.WithStack(err))
}
imprts, err := collectImports(path, content)
if err != nil {
return err
}
return collectOnlyMainFn(imprts)
}
}

func collectImports(pth string, content []byte) ([]string, error) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, pth, string(content), parser.ImportsOnly)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrCompileError, errors.WithStack(err))
}
imprts := make([]string, 0, len(f.Imports))
for _, spec := range f.Imports {
imprt := strings.TrimSuffix(strings.TrimPrefix(spec.Path.Value, "\""), "\"")
imprts = append(imprts, imprt)
}
return imprts, nil
}

func goModCache() string {
return envOr("GOMODCACHE", gopathDir("pkg/mod"))
}

func envOr(key, def string) string {
val := os.Getenv(key)
if val == "" {
val = def
}
return val
}

func gopathDir(rel string) string {
list := filepath.SplitList(gopath())
if len(list) == 0 || list[0] == "" {
return ""
}
return filepath.Join(list[0], rel)
}

func gopath() string {
gp := os.Getenv("GOPATH")
if gp == "" {
gp = build.Default.GOPATH
}
return gp
}
24 changes: 24 additions & 0 deletions pkg/dockerfilegen/imports/scanner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package imports_test

import (
"path"
"runtime"
"testing"

"github.com/openshift-knative/hack/pkg/dockerfilegen/imports"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestScanForMains(t *testing.T) {
_, file, _, _ := runtime.Caller(0)
rootDir := path.Dir(path.Dir(path.Dir(path.Dir(file))))
pkgs, err := imports.ScanForMains(rootDir,
[]string{"pkg/dockerfilegen/imports/testdata"},
[]string{"tools"},
)
require.NoError(t, err)
assert.True(t, pkgs.Has("github.com/openshift-knative/hack/cmd/prowgen"))
assert.True(t, pkgs.Has("knative.dev/pkg/codegen/cmd/injection-gen"))
assert.Equal(t, 2, pkgs.Len())
}
9 changes: 9 additions & 0 deletions pkg/dockerfilegen/imports/testdata/example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//go:build tools

package testdata

import (
_ "github.com/openshift-knative/hack/cmd/prowgen"
_ "knative.dev/pkg"
_ "knative.dev/pkg/codegen/cmd/injection-gen"
)
23 changes: 23 additions & 0 deletions pkg/dockerfilegen/imports_scanner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dockerfilegen

import (
"log"

"github.com/openshift-knative/hack/pkg/dockerfilegen/imports"
"k8s.io/apimachinery/pkg/util/sets"
)

func scanImports(paths sets.Set[string], rootDir string, packages []string, tags []string) error {
m, err := imports.ScanForMains(rootDir, packages, tags)
if err != nil {
return err
}
for _, pkg := range m.UnsortedList() {
if !paths.Has(pkg) && !paths.Has("vendor/"+pkg) {
paths.Insert(pkg)
log.Println("Found main package from imports:", pkg)
}
}

return nil
}
5 changes: 5 additions & 0 deletions pkg/dockerfilegen/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ type Params struct {
AdditionalBuildEnvVars []string `json:"additional-build-env" desc:"Additional env vars to be added to builder in the image"`
TemplateName string `json:"template-name" desc:"Dockerfile template name to use. Supported values are [default, func-util]"`
RpmsLockFileEnabled bool `json:"generate-rpms-lock-file" desc:"Enable the creation of the rpms.lock.yaml file"`
ScanImports bool `json:"scan-imports" desc:"Should the imports be scanned for main packages (executables)"`
ScanImportsSubPackages []string `json:"scan-imports-subpackages" desc:"Which sub-packages to scan for main imports"`
ScanImportsTags []string `json:"scan-imports-tags" desc:"Which build tags use to limit the scan"`
}

func (p *Params) ConfigureFlags() (*pflag.FlagSet, error) {
Expand Down Expand Up @@ -67,6 +70,8 @@ func DefaultParams(wd string) Params {
AdditionalBuildEnvVars: nil,
TemplateName: DefaultDockerfileTemplateName,
RpmsLockFileEnabled: false,
ScanImportsSubPackages: []string{"hack"},
ScanImportsTags: []string{"tools"},
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# DO NOT EDIT! Generated Dockerfile for cmd/discover.
# DO NOT EDIT! Generated Dockerfile for ./cmd/discover.
ARG GO_BUILDER=registry.ci.openshift.org/openshift/release:rhel-8-release-golang-1.22-openshift-4.17
ARG GO_RUNTIME=registry.access.redhat.com/ubi8/ubi-minimal

Expand Down
Loading