Skip to content
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
212 changes: 212 additions & 0 deletions builder/build/cnb/cnb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/eapache/channels"
"github.com/goodrain/rainbond/builder"
"github.com/goodrain/rainbond/builder/build"
"github.com/goodrain/rainbond/builder/parser/code"
"github.com/goodrain/rainbond/event"
Expand Down Expand Up @@ -1106,3 +1107,214 @@ func TestRunCNBBuildJob(t *testing.T) {
})
}

// --- config.go: offline mode ---

func TestIsOfflineMode(t *testing.T) {
t.Run("offline when marker file exists", func(t *testing.T) {
dir := t.TempDir()
marker := filepath.Join(dir, "BP_DEPENDENCY_MIRROR")
os.WriteFile(marker, []byte("file:///grdata/cnb"), 0644)

orig := offlineMirrorMarker
offlineMirrorMarker = marker
defer func() { offlineMirrorMarker = orig }()

if !isOfflineMode() {
t.Error("expected offline mode when marker file exists")
}
})

t.Run("online when marker file absent", func(t *testing.T) {
orig := offlineMirrorMarker
offlineMirrorMarker = "/nonexistent/path/marker"
defer func() { offlineMirrorMarker = orig }()

if isOfflineMode() {
t.Error("expected online mode when marker file absent")
}
})
}

func TestGetCNBBuilderImageOffline(t *testing.T) {
tests := []struct {
name string
envVar string
offline bool
registryDomain string
want string
}{
{
name: "default online",
want: DefaultCNBBuilder,
},
{
name: "env var override",
envVar: "custom-registry.com/builder:v1",
want: "custom-registry.com/builder:v1",
},
{
name: "offline with goodrain.me",
offline: true,
registryDomain: "goodrain.me",
want: "goodrain.me/" + cnbBuilderShortName,
},
{
name: "offline with custom registry",
offline: true,
registryDomain: "harbor.example.com/lib",
want: "harbor.example.com/lib/" + cnbBuilderShortName,
},
{
name: "env var takes priority over offline",
envVar: "custom:v1",
offline: true,
registryDomain: "goodrain.me",
want: "custom:v1",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Unsetenv("CNB_BUILDER_IMAGE")
if tt.envVar != "" {
os.Setenv("CNB_BUILDER_IMAGE", tt.envVar)
defer os.Unsetenv("CNB_BUILDER_IMAGE")
}

orig := offlineMirrorMarker
if tt.offline {
dir := t.TempDir()
marker := filepath.Join(dir, "marker")
os.WriteFile(marker, []byte("file:///grdata/cnb"), 0644)
offlineMirrorMarker = marker
} else {
offlineMirrorMarker = "/nonexistent/marker"
}
defer func() { offlineMirrorMarker = orig }()

if tt.registryDomain != "" {
origDomain := builder.REGISTRYDOMAIN
builder.REGISTRYDOMAIN = tt.registryDomain
defer func() { builder.REGISTRYDOMAIN = origDomain }()
}

if got := GetCNBBuilderImage(); got != tt.want {
t.Errorf("GetCNBBuilderImage() = %q; want %q", got, tt.want)
}
})
}
}

func TestGetCNBRunImageOffline(t *testing.T) {
tests := []struct {
name string
envVar string
offline bool
registryDomain string
want string
}{
{
name: "default online",
want: DefaultCNBRunImage,
},
{
name: "offline with goodrain.me",
offline: true,
registryDomain: "goodrain.me",
want: "goodrain.me/" + cnbRunShortName,
},
{
name: "offline with custom registry",
offline: true,
registryDomain: "harbor.example.com/lib",
want: "harbor.example.com/lib/" + cnbRunShortName,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Unsetenv("CNB_RUN_IMAGE")
if tt.envVar != "" {
os.Setenv("CNB_RUN_IMAGE", tt.envVar)
defer os.Unsetenv("CNB_RUN_IMAGE")
}

orig := offlineMirrorMarker
if tt.offline {
dir := t.TempDir()
marker := filepath.Join(dir, "marker")
os.WriteFile(marker, []byte("file:///grdata/cnb"), 0644)
offlineMirrorMarker = marker
} else {
offlineMirrorMarker = "/nonexistent/marker"
}
defer func() { offlineMirrorMarker = orig }()

if tt.registryDomain != "" {
origDomain := builder.REGISTRYDOMAIN
builder.REGISTRYDOMAIN = tt.registryDomain
defer func() { builder.REGISTRYDOMAIN = origDomain }()
}

if got := GetCNBRunImage(); got != tt.want {
t.Errorf("GetCNBRunImage() = %q; want %q", got, tt.want)
}
})
}
}

// --- job.go: insecure-registry for run image ---

func TestBuildCreatorArgsInsecureRegistry(t *testing.T) {
b := &Builder{}

t.Run("same registry - single insecure-registry", func(t *testing.T) {
origDomain := builder.REGISTRYDOMAIN
builder.REGISTRYDOMAIN = "goodrain.me"
defer func() { builder.REGISTRYDOMAIN = origDomain }()

dir := newNodeDir(t)
re := &build.Request{SourceDir: dir, BuildEnvs: map[string]string{}}
args := b.buildCreatorArgs(re, "goodrain.me/app:v1", "goodrain.me/run:v1")

count := 0
for _, a := range args {
if strings.HasPrefix(a, "-insecure-registry=") {
count++
}
}
if count != 1 {
t.Errorf("expected 1 insecure-registry flag, got %d; args=%v", count, args)
}
})

t.Run("different registry - two insecure-registry flags", func(t *testing.T) {
origDomain := builder.REGISTRYDOMAIN
builder.REGISTRYDOMAIN = "goodrain.me"
defer func() { builder.REGISTRYDOMAIN = origDomain }()

dir := newNodeDir(t)
re := &build.Request{SourceDir: dir, BuildEnvs: map[string]string{}}
args := b.buildCreatorArgs(re, "goodrain.me/app:v1", "registry.cn-hangzhou.aliyuncs.com/goodrain/run:v1")

insecureFlags := []string{}
for _, a := range args {
if strings.HasPrefix(a, "-insecure-registry=") {
insecureFlags = append(insecureFlags, a)
}
}
if len(insecureFlags) != 2 {
t.Errorf("expected 2 insecure-registry flags, got %d; flags=%v", len(insecureFlags), insecureFlags)
}
foundRunHost := false
for _, f := range insecureFlags {
if strings.Contains(f, "registry.cn-hangzhou.aliyuncs.com") {
foundRunHost = true
}
}
if !foundRunHost {
t.Errorf("expected insecure-registry for run image host; flags=%v", insecureFlags)
}
})
}

51 changes: 43 additions & 8 deletions builder/build/cnb/config.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,57 @@
package cnb

import "github.com/goodrain/rainbond/util"
import (
"os"
"path"

"github.com/goodrain/rainbond/builder"
"github.com/sirupsen/logrus"
)

const (
// DefaultCNBBuilder is the default CNB builder image
// DefaultCNBBuilder is the default online CNB builder image
DefaultCNBBuilder = "registry.cn-hangzhou.aliyuncs.com/goodrain/ubuntu-noble-builder:0.0.72"
// DefaultCNBRunImage is the default CNB run image
// DefaultCNBRunImage is the default online CNB run image
DefaultCNBRunImage = "registry.cn-hangzhou.aliyuncs.com/goodrain/ubuntu-noble-run:0.0.50"
// CNBLifecycleCreatorPath is the path to the lifecycle creator binary in builder image
// CNBLifecycleCreatorPath is the path to the lifecycle creator binary
CNBLifecycleCreatorPath = "/lifecycle/creator"

// Short image names for constructing internal registry references
cnbBuilderShortName = "ubuntu-noble-builder:0.0.72"
cnbRunShortName = "ubuntu-noble-run:0.0.50"
)

// GetCNBBuilderImage returns the CNB builder image from environment or default
// isOfflineMode checks whether the cluster is in offline/air-gapped mode
// by looking for the same marker file used by getDependencyMirror.
func isOfflineMode() bool {
_, err := os.Stat(offlineMirrorMarker)
return err == nil
}

// GetCNBBuilderImage returns the CNB builder image reference.
// Priority: env var > offline (REGISTRYDOMAIN) > default online URL.
func GetCNBBuilderImage() string {
return util.GetenvDefault("CNB_BUILDER_IMAGE", DefaultCNBBuilder)
if v := os.Getenv("CNB_BUILDER_IMAGE"); v != "" {
return v
}
if isOfflineMode() {
img := path.Join(builder.REGISTRYDOMAIN, cnbBuilderShortName)
logrus.Infof("Offline mode: using CNB builder image from internal registry: %s", img)
return img
}
return DefaultCNBBuilder
}

// GetCNBRunImage returns the CNB run image from environment or default
// GetCNBRunImage returns the CNB run image reference.
// Priority: env var > offline (REGISTRYDOMAIN) > default online URL.
func GetCNBRunImage() string {
return util.GetenvDefault("CNB_RUN_IMAGE", DefaultCNBRunImage)
if v := os.Getenv("CNB_RUN_IMAGE"); v != "" {
return v
}
if isOfflineMode() {
img := path.Join(builder.REGISTRYDOMAIN, cnbRunShortName)
logrus.Infof("Offline mode: using CNB run image from internal registry: %s", img)
return img
}
return DefaultCNBRunImage
}
22 changes: 22 additions & 0 deletions builder/build/cnb/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ func (b *Builder) buildCreatorArgs(re *build.Request, buildImageName, runImage s
"-log-level=" + logLevel,
}

// If the run image is on a different registry, add an extra -insecure-registry
// so the lifecycle creator can access it (e.g., self-signed cert or HTTP).
if runImageHost := imageHost(runImage); runImageHost != "" && runImageHost != registryHost {
args = append(args, "-insecure-registry="+runImageHost)
}

if noCache {
// Skip both cache restore and image layer reuse
args = append(args, "-skip-restore")
Expand Down Expand Up @@ -319,4 +325,20 @@ func stableImageTag(imageName, tag string) string {
return imageName[:i] + ":" + tag
}
return imageName + ":" + tag
}

// imageHost extracts the registry host from an image reference.
// e.g. "registry.cn-hangzhou.aliyuncs.com/goodrain/run:v1" → "registry.cn-hangzhou.aliyuncs.com"
//
// "goodrain.me/run:v1" → "goodrain.me"
// "run:v1" (no slash, Docker Hub) → ""
func imageHost(image string) string {
// Strip tag
if i := strings.LastIndex(image, ":"); i != -1 {
image = image[:i]
}
if i := strings.Index(image, "/"); i != -1 {
return image[:i]
}
return ""
}
2 changes: 1 addition & 1 deletion builder/build/cnb/lang_nodejs.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ const defaultOnlineMirror = "https://buildpack.rainbond.com/cnb"

// offlineMirrorMarker is the file path inside the build pod (grdata mount)
// that an offline provisioning tool writes to switch to local file:// mirror.
const offlineMirrorMarker = "/grdata/cnb/BP_DEPENDENCY_MIRROR"
var offlineMirrorMarker = "/grdata/cnb/BP_DEPENDENCY_MIRROR"

// getDependencyMirror returns the CNB dependency mirror URL.
// Priority: env var > offline marker file > default online URL.
Expand Down
11 changes: 3 additions & 8 deletions builder/build/code_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,14 +573,9 @@ func (e *ErrorBuild) Error() string {
}

func (s *slugBuild) HandleNodeJsDir(re *Request) error {
// Check if this is a frontend Node.js project that needs static file handling
// This is determined by RUNTIME_TYPE from framework detection or BUILD_RUNTIME_TYPE from console
isFrontend := re.BuildEnvs["RUNTIME_TYPE"] == "frontend" || re.BuildEnvs["BUILD_RUNTIME_TYPE"] == "frontend"

// Also check for combined language types containing "static" for backward compatibility
isStaticLang := strings.Contains(string(re.Lang), string(code.Static))

if isFrontend || isStaticLang {
// For slug builds, use language type to determine if static file handling is needed.
// RUNTIME_TYPE is set by framework detection for CNB builds and should not be used here.
if re.Lang == code.NodeJSStatic {
if ok, _ := util.FileExists(path.Join(re.SourceDir, "nodestatic.json")); ok {
if err := os.RemoveAll(path.Join(re.SourceDir, "nodestatic.json")); err != nil {
logrus.Error("remove nodestatic json error:", err)
Expand Down