Ctrl K
ComunidadGuías…

builder/build/cnb/build.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package cnb

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/goodrain/rainbond/builder/build"
	jobc "github.com/goodrain/rainbond/builder/job"
	"github.com/goodrain/rainbond/builder/parser"
	"github.com/goodrain/rainbond/builder/sources"
	"github.com/goodrain/rainbond/util"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

func init() {
	build.RegisterCNBBuilder(NewBuilder)
}

// Builder implements the build.Build interface for Cloud Native Buildpacks
type Builder struct {
	jobCtrl          jobc.Controller
	createAuthSecret func(*build.Request) (corev1.Secret, error)
	deleteAuthSecret func(*build.Request, string)
	prepareBuildKit  func(ctx context.Context, kubeClient kubernetes.Interface, namespace, cmName, imageDomain string) error
	createConfigMap  func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, cm *corev1.ConfigMap) (*corev1.ConfigMap, error)
	deleteConfigMap  func(ctx context.Context, kubeClient kubernetes.Interface, namespace, name string) error
}

// NewBuilder creates a new CNB builder
func NewBuilder() (build.Build, error) {
	return &Builder{
		jobCtrl:          jobc.GetJobController(),
		createAuthSecret: build.CreateAuthSecret,
		deleteAuthSecret: build.DeleteAuthSecret,
		prepareBuildKit:  sources.PrepareBuildKitTomlCM,
		createConfigMap: func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, cm *corev1.ConfigMap) (*corev1.ConfigMap, error) {
			return kubeClient.CoreV1().ConfigMaps(namespace).Create(ctx, cm, metav1.CreateOptions{})
		},
		deleteConfigMap: func(ctx context.Context, kubeClient kubernetes.Interface, namespace, name string) error {
			err := kubeClient.CoreV1().ConfigMaps(namespace).Delete(ctx, name, metav1.DeleteOptions{})
			if k8serrors.IsNotFound(err) {
				return nil
			}
			return err
		},
	}, nil
}

// Build executes the CNB build process
func (b *Builder) Build(re *build.Request) (*build.Response, error) {
	re.Logger.Info("Starting CNB build", map[string]string{"step": "builder-exector"})

	if re.ServerType == "pkg" {
		workspace, cleanup, err := parser.PreparePackageBuildWorkspace(re.SourceDir, re.Commit.Message)
		if err != nil {
			re.Logger.Error(err.Error(), map[string]string{"step": "build-code", "status": "failure"})
			return nil, err
		}
		defer cleanup()
		request := *re
		request.SourceDir = workspace
		re = &request
		re.Logger.Info("Prepared source package build input", map[string]string{"step": "build-code"})
	}

	if err := applyVersionPolicy(re); err != nil {
		re.Logger.Error(err.Error(), map[string]string{"step": "build-code", "status": "failure"})
		return nil, err
	}
	if err := validateSupportedBuildParams(re); err != nil {
		re.Logger.Error(err.Error(), map[string]string{"step": "build-code", "status": "failure"})
		return nil, err
	}

	b.stopPreBuildJob(re)

	if err := b.validateProjectFiles(re); err != nil {
		return nil, err
	}

	lang := getLanguageConfig(re)
	if err := lang.InjectMirrorConfig(re); err != nil {
		logrus.Warnf("inject mirror config failed: %v", err)
	}

	if err := b.setSourceDirPermissions(re); err != nil {
		logrus.Warnf("set source dir permissions failed: %v", err)
	}

	buildImageName := build.CreateImageName(re.ServiceID, re.DeployVersion)

	if err := b.runCNBBuildJob(re, buildImageName); err != nil {
		re.Logger.Error(util.Translation("CNB build failed"), map[string]string{"step": "build-code", "status": "failure"})
		logrus.Error("CNB build job error:", err.Error())
		return nil, err
	}

	re.Logger.Info("CNB build success", map[string]string{"step": "build-exector"})
	return &build.Response{
		MediumPath: buildImageName,
		MediumType: build.ImageMediumType,
	}, nil
}

// stopPreBuildJob stops any previous build jobs for the service (best-effort).
func (b *Builder) stopPreBuildJob(re *build.Request) {
	jobList, err := b.jobCtrl.GetServiceJobs(re.ServiceID)
	if err != nil {
		logrus.Errorf("get pre build job for service %s failure: %s", re.ServiceID, err.Error())
		return
	}
	for _, job := range jobList {
		b.jobCtrl.DeleteJob(job.Name)
	}
}

// validateProjectFiles checks for common project configuration issues
func (b *Builder) validateProjectFiles(re *build.Request) error {
	lockFiles := []string{"package-lock.json", "yarn.lock", "pnpm-lock.yaml"}
	for _, lockFile := range lockFiles {
		lockfilePath := filepath.Join(re.SourceDir, lockFile)
		if info, err := os.Stat(lockfilePath); err == nil {
			if info.Size() == 0 {
				msg := fmt.Sprintf("ERROR: %s is empty. Please run the appropriate install command (npm install / yarn install / pnpm install) to generate a valid lock file, then commit and push.", lockFile)
				re.Logger.Error(msg, map[string]string{"step": "build-code"})
				return fmt.Errorf("empty lock file: %s", lockFile)
			}
		}
	}
	return nil
}

// setSourceDirPermissions removes .git and sets permissions for CNB build
func (b *Builder) setSourceDirPermissions(re *build.Request) error {
	if !truthyBuildEnv(firstNonEmptyEnv(re.BuildEnvs, "KEEP_GIT", "BUILD_KEEP_GIT")) {
		gitDir := filepath.Join(re.SourceDir, ".git")
		if _, err := os.Stat(gitDir); err == nil {
			if err := os.RemoveAll(gitDir); err != nil {
				logrus.Warnf("failed to remove .git directory: %v", err)
			}
		}
	}
	// chmod 777 so files are accessible; actual chown to cnb user
	// is done inside the CNB Job container where the cnb user exists
	return filepath.WalkDir(re.SourceDir, func(path string, d os.DirEntry, err error) error {
		if err != nil {
			return err
		}
		return os.Chmod(path, 0777)
	})
}