package cnb import ( "crypto/sha1" "fmt" "os" "strings" "github.com/goodrain/rainbond/builder/build" "github.com/goodrain/rainbond/builder/parser/code" corev1 "k8s.io/api/core/v1" ) const ( defaultCNBMavenVersion = "3.9.14" defaultCNBComposerVersion = "2.7.9" pythonPackageManagerPip = "pip" pythonPackageManagerPipenv = "pipenv" pythonPackageManagerPoetry = "poetry" pythonPackageManagerConda = "conda" bindingTypeKeyPrefix = "cnb-binding-" bindingTypeKeySuffix = "-type" k8sNamePartMaxLength = 63 bindingTypeHashHexLength = 8 ) type platformBinding struct { Name string Type string ConfigMapName string ConfigMapKey string TargetFile string } func ensureBuildEnvs(re *build.Request) { if re.BuildEnvs == nil { re.BuildEnvs = map[string]string{} } } func truthyBuildEnv(value string) bool { switch strings.ToLower(strings.TrimSpace(value)) { case "1", "t", "true", "y", "yes", "on": return true default: return false } } func appendEnvVar(envs []corev1.EnvVar, name, value string) []corev1.EnvVar { value = strings.TrimSpace(value) if value == "" { return envs } return append(envs, corev1.EnvVar{Name: name, Value: value}) } func setAnnotationValue(annotations map[string]string, key, value string) { value = strings.TrimSpace(value) if value == "" { return } annotations[key] = value } func validateSupportedBuildParams(re *build.Request) error { ensureBuildEnvs(re) if strings.TrimSpace(re.BuildStrategy) != "cnb" { return nil } switch re.Lang { case code.JavaMaven, code.Gradle, code.JaveWar: re.BuildEnvs["BUILD_RUNTIMES_MAVEN"] = defaultCNBMavenVersion server := firstNonEmptyEnv(re.BuildEnvs, "BP_JAVA_APP_SERVER", "BUILD_RUNTIMES_SERVER") if server == "" && re.Lang == code.JaveWar { server = "tomcat" } if normalized, err := normalizeJavaAppServer(server); err != nil { return err } else if normalized != "" { re.BuildEnvs["BP_JAVA_APP_SERVER"] = normalized re.BuildEnvs["BUILD_RUNTIMES_SERVER"] = normalized } case code.Python: manager, err := detectPythonPackageManager(re) if err != nil { return err } if manager != "" { re.BuildEnvs["BUILD_PYTHON_PACKAGE_MANAGER"] = manager } if manager == pythonPackageManagerConda { solver := firstNonEmptyEnv(re.BuildEnvs, "BP_CONDA_SOLVER", "BUILD_CONDA_SOLVER") if solver == "" { solver = "mamba" } re.BuildEnvs["BP_CONDA_SOLVER"] = solver re.BuildEnvs["BUILD_CONDA_SOLVER"] = solver } if version := strings.TrimSpace(re.BuildEnvs["BUILD_PYTHON_PACKAGE_MANAGER_VERSION"]); version != "" { envName, err := pythonPackageManagerVersionEnv(manager) if err != nil { return err } re.BuildEnvs[envName] = version } case code.PHP: server := firstNonEmptyEnv(re.BuildEnvs, "BUILD_RUNTIMES_SERVER") if server == "" { server = "nginx" } if normalized, err := normalizePHPServer(server); err != nil { return err } else if normalized != "" { re.BuildEnvs["BUILD_RUNTIMES_SERVER"] = normalized } re.BuildEnvs["BP_COMPOSER_VERSION"] = defaultCNBComposerVersion re.BuildEnvs["BUILD_COMPOSER_VERSION"] = defaultCNBComposerVersion if options := firstNonEmptyEnv(re.BuildEnvs, "BP_COMPOSER_INSTALL_OPTIONS", "BUILD_COMPOSER_INSTALL_OPTIONS"); options != "" { re.BuildEnvs["BP_COMPOSER_INSTALL_OPTIONS"] = options re.BuildEnvs["BUILD_COMPOSER_INSTALL_OPTIONS"] = options } if webDir := firstNonEmptyEnv(re.BuildEnvs, "BP_PHP_WEB_DIR", "BUILD_PHP_WEB_DIR"); webDir != "" { re.BuildEnvs["BP_PHP_WEB_DIR"] = webDir re.BuildEnvs["BUILD_PHP_WEB_DIR"] = webDir } redirect := firstNonEmptyEnv(re.BuildEnvs, "BP_PHP_ENABLE_HTTPS_REDIRECT", "BUILD_PHP_ENABLE_HTTPS_REDIRECT") if redirect == "" { redirect = "false" } re.BuildEnvs["BP_PHP_ENABLE_HTTPS_REDIRECT"] = redirect re.BuildEnvs["BUILD_PHP_ENABLE_HTTPS_REDIRECT"] = redirect } return nil } func normalizeJavaAppServer(server string) (string, error) { server = strings.ToLower(strings.TrimSpace(server)) if server == "" { return "", nil } switch { case strings.Contains(server, "tomcat"): return "tomcat", nil case strings.Contains(server, "tomee"): return "tomee", nil case strings.Contains(server, "liberty"): return "liberty", nil default: return "", fmt.Errorf("unsupported java app server %q", server) } } func normalizePHPServer(server string) (string, error) { server = strings.ToLower(strings.TrimSpace(server)) if server == "" { return "", nil } switch server { case "apache", "httpd": return "httpd", nil case "nginx", "php-server": return server, nil default: return "", fmt.Errorf("unsupported php server %q", server) } } func detectPythonPackageManager(re *build.Request) (string, error) { manager, err := normalizePythonPackageManager(re.BuildEnvs["BUILD_PYTHON_PACKAGE_MANAGER"]) if err != nil { return "", err } if manager != "" { return manager, nil } return code.DetectPythonPackageManager(re.SourceDir), nil } func normalizePythonPackageManager(manager string) (string, error) { switch strings.ToLower(strings.TrimSpace(manager)) { case "": return "", nil case pythonPackageManagerPip, pythonPackageManagerPipenv, pythonPackageManagerPoetry, pythonPackageManagerConda: return strings.ToLower(strings.TrimSpace(manager)), nil default: return "", fmt.Errorf("unsupported python package manager %q", manager) } } func pythonPackageManagerVersionEnv(manager string) (string, error) { switch manager { case pythonPackageManagerPip: return "BP_PIP_VERSION", nil case pythonPackageManagerPipenv: return "BP_PIPENV_VERSION", nil case pythonPackageManagerPoetry: return "BP_POETRY_VERSION", nil case pythonPackageManagerConda: return "", fmt.Errorf("conda package manager does not support BUILD_PYTHON_PACKAGE_MANAGER_VERSION") default: return "", fmt.Errorf("python package manager must be set before BUILD_PYTHON_PACKAGE_MANAGER_VERSION can be used") } } func fileExists(path string) bool { _, err := os.Stat(path) return err == nil } func bindingTypeAnnotationKey(bindingName string) string { bindingName = strings.ToLower(strings.TrimSpace(bindingName)) if bindingName == "" { return bindingTypeKeyPrefix + strings.TrimPrefix(bindingTypeKeySuffix, "-") } var normalized strings.Builder for _, r := range bindingName { switch { case r >= 'a' && r <= 'z': normalized.WriteRune(r) case r >= '0' && r <= '9': normalized.WriteRune(r) case r == '-': normalized.WriteRune(r) default: normalized.WriteRune('-') } } namePart := strings.Trim(normalized.String(), "-") if namePart == "" { return bindingTypeKeyPrefix + strings.TrimPrefix(bindingTypeKeySuffix, "-") } maxNamePartLength := k8sNamePartMaxLength - len(bindingTypeKeyPrefix) - len(bindingTypeKeySuffix) if len(namePart) > maxNamePartLength { sum := sha1.Sum([]byte(namePart)) hashSuffix := fmt.Sprintf("%x", sum[:bindingTypeHashHexLength/2]) prefixLength := maxNamePartLength - len(hashSuffix) - 1 if prefixLength < 1 { namePart = hashSuffix[:maxNamePartLength] } else { prefix := strings.TrimRight(namePart[:prefixLength], "-") if prefix == "" { namePart = hashSuffix[:maxNamePartLength] } else { namePart = prefix + "-" + hashSuffix } } } return bindingTypeKeyPrefix + namePart + bindingTypeKeySuffix }