Files
osmedeus/internal/core/dependency.go
T

206 lines
5.5 KiB
Go

package core
import (
"fmt"
"net"
"net/url"
"os"
"regexp"
"strings"
)
// Dependencies defines workflow requirements
type Dependencies struct {
Commands []string `yaml:"commands"`
Files []string `yaml:"files"`
Variables []VariableDep `yaml:"variables"`
TargetTypes []TargetType `yaml:"target_types"`
FunctionsConditions []string `yaml:"functions_conditions"`
}
// VariableDep defines variable requirements
type VariableDep struct {
Name string `yaml:"name"`
Type VariableType `yaml:"type"`
Required bool `yaml:"required"`
}
// HasCommandDeps returns true if there are command dependencies
func (d *Dependencies) HasCommandDeps() bool {
return d != nil && len(d.Commands) > 0
}
// HasFileDeps returns true if there are file dependencies
func (d *Dependencies) HasFileDeps() bool {
return d != nil && len(d.Files) > 0
}
// HasVariableDeps returns true if there are variable dependencies
func (d *Dependencies) HasVariableDeps() bool {
return d != nil && len(d.Variables) > 0
}
// HasFunctionConditions returns true if there are function-based condition dependencies
func (d *Dependencies) HasFunctionConditions() bool {
return d != nil && len(d.FunctionsConditions) > 0
}
// GetRequiredVariables returns all required variable dependencies
func (d *Dependencies) GetRequiredVariables() []VariableDep {
if d == nil {
return nil
}
var required []VariableDep
for _, v := range d.Variables {
if v.Required {
required = append(required, v)
}
}
return required
}
var domainRegex = regexp.MustCompile(`^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
var numericRegex = regexp.MustCompile(`^-?\d+(\.\d+)?$`)
var simpleRepoRegex = regexp.MustCompile(`^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$`)
var hostedRepoRegex = regexp.MustCompile(`^(?:(?:https?://)?(?:www\.)?(github\.com|gitlab\.com)/)([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+?)(?:\.git)?/?$`)
var sshRepoRegex = regexp.MustCompile(`^git@[^:]+:([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+?)(\.git)?$`)
func MatchesVariableType(value string, varType VariableType) (bool, error) {
switch varType {
case VarTypeDomain:
return domainRegex.MatchString(value), nil
case VarTypeSubdomain:
if !domainRegex.MatchString(value) {
return false, nil
}
return strings.Count(value, ".") >= 2, nil
case VarTypeURL:
u, err := url.Parse(value)
if err != nil {
return false, nil
}
if u.Scheme == "http" || u.Scheme == "https" {
return u.Host != "", nil
}
return false, nil
case VarTypeCIDR:
_, _, err := net.ParseCIDR(value)
return err == nil, nil
case VarTypeIP:
return net.ParseIP(value) != nil, nil
case VarTypeRepo:
return isRepo(value), nil
case VarTypePath, VarTypeFile, VarTypeFolder:
return value != "", nil
case VarTypeNumber:
return numericRegex.MatchString(value), nil
case VarTypeString:
return true, nil
default:
return false, fmt.Errorf("unknown variable type: %s", varType)
}
}
// MatchesAnyVariableType checks if value matches ANY of the comma-separated types.
// Returns true if value matches at least one type.
func MatchesAnyVariableType(value string, typeSpec VariableType) (bool, error) {
types := strings.Split(string(typeSpec), ",")
for _, t := range types {
t = strings.TrimSpace(t)
if t == "" {
continue
}
matches, err := MatchesVariableType(value, VariableType(t))
if err != nil {
return false, err
}
if matches {
return true, nil
}
}
return false, nil
}
// MatchesAnyTargetType checks if target matches ANY of the comma-separated types.
// Returns true if target matches at least one type.
func MatchesAnyTargetType(target string, typeSpec TargetType) (bool, error) {
types := strings.Split(string(typeSpec), ",")
for _, t := range types {
t = strings.TrimSpace(t)
if t == "" {
continue
}
matches, err := MatchesTargetType(target, TargetType(t))
if err != nil {
return false, err
}
if matches {
return true, nil
}
}
return false, nil
}
func MatchesTargetType(target string, targetType TargetType) (bool, error) {
switch targetType {
case TargetTypeDomain:
return MatchesVariableType(target, VarTypeDomain)
case TargetTypeSubdomain:
return MatchesVariableType(target, VarTypeSubdomain)
case TargetTypeURL:
return MatchesVariableType(target, VarTypeURL)
case TargetTypeCIDR:
return MatchesVariableType(target, VarTypeCIDR)
case TargetTypeRepo:
return MatchesVariableType(target, VarTypeRepo)
case TargetTypePath:
return MatchesVariableType(target, VarTypePath)
case TargetTypeNumber:
return MatchesVariableType(target, VarTypeNumber)
case TargetTypeString:
return true, nil
case TargetTypeIP:
return MatchesVariableType(target, VarTypeIP)
case TargetTypeFile:
info, err := os.Stat(target)
if err != nil {
return false, nil
}
return !info.IsDir(), nil
case TargetTypeFolder:
info, err := os.Stat(target)
if err != nil {
return false, nil
}
return info.IsDir(), nil
default:
return false, fmt.Errorf("unknown target type: %s", targetType)
}
}
func isRepo(value string) bool {
if simpleRepoRegex.MatchString(value) {
return true
}
if hostedRepoRegex.MatchString(value) {
return true
}
if sshRepoRegex.MatchString(value) {
return true
}
u, err := url.Parse(value)
if err == nil && (u.Scheme == "http" || u.Scheme == "https" || u.Scheme == "ssh" || u.Scheme == "git") {
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(parts) >= 2 {
owner := parts[0]
repo := strings.TrimSuffix(parts[1], ".git")
if owner != "" && repo != "" {
return true
}
}
}
return false
}