chore: keep host/client specifics out of the tree (env & config)
Release Please / Release Please (push) Skipped
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Skipped
Tests / Test (Go 1.25) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Tests / Secret scan (gitleaks) (pull_request) Successful in 4s
Tests / Test (Go 1.25) (pull_request) Successful in 59s
Tests / Test (Go stable) (pull_request) Successful in 1m7s
Release Please / Release Please (push) Skipped
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Skipped
Tests / Test (Go 1.25) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Tests / Secret scan (gitleaks) (pull_request) Successful in 4s
Tests / Test (Go 1.25) (pull_request) Successful in 59s
Tests / Test (Go stable) (pull_request) Successful in 1m7s
Showroom-safe: the tree no longer carries internal hosts, IPs, ports, personal names, client domains or client file names. Behaviour is unchanged and now supplied per deployment. - catalog: hardcoded mail-org / project classifiers become a YAML-driven Classifier (OO_CATALOG_CONFIG or --config); neutral default classifies nothing as work. New catalog/classify.go + example + tests. - storage_fallback: drop the baked-in MinIO endpoint IP; require MINIO_ENDPOINT (+ keys) from the env. - kontolink: build DocEditor links from $ONLYOFFICE_URL instead of a hardcoded portal host; kontoblatt: no client file id in the output name. - oo: load .env CLI-wide (bootstrap.LoadEnv in execute) so non-authenticating commands (catalog scans) also see config. - genericize comments/docs/fixtures (AGENTS, README, .env.example, crm-associations, catalog tests, ES/pdfattach tests, mails).
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
package catalog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Classifier maps project trees and mail identities to catalog org/zone/role.
|
||||
//
|
||||
// The library ships with neutral defaults: nothing is classified as work unless
|
||||
// the deployment supplies rules. Those rules are deployment-specific, so they
|
||||
// live in a YAML config file (path from OO_CATALOG_CONFIG or the --config flag),
|
||||
// not in the code. See catalog/classify.example.yaml.
|
||||
type Classifier struct {
|
||||
// WorkRemotes: a git remote containing any of these substrings → work/hot.
|
||||
WorkRemotes []string `yaml:"work_remotes,omitempty"`
|
||||
// WorkNames: a project name containing any of these substrings → work/warm.
|
||||
WorkNames []string `yaml:"work_names,omitempty"`
|
||||
// MailOrgs: ordered mail-identity rules; the first match wins.
|
||||
MailOrgs []MailRule `yaml:"mail_orgs,omitempty"`
|
||||
}
|
||||
|
||||
// MailRule maps an email domain and/or a display-name substring to an org with
|
||||
// a zone/role. At least one of Domain, Suffix or Name must be set.
|
||||
type MailRule struct {
|
||||
Domain string `yaml:"domain,omitempty"` // exact domain, case-insensitive
|
||||
Suffix string `yaml:"suffix,omitempty"` // domain suffix, e.g. ".example.com"
|
||||
Name string `yaml:"name,omitempty"` // substring of the display name
|
||||
Org string `yaml:"org"`
|
||||
Zone string `yaml:"zone,omitempty"` // default "hot"
|
||||
Role string `yaml:"role,omitempty"` // default "work"
|
||||
}
|
||||
|
||||
// DefaultClassifier returns the neutral classifier (no deployment rules).
|
||||
func DefaultClassifier() *Classifier { return &Classifier{} }
|
||||
|
||||
// LoadClassifier reads a classifier config from a YAML file.
|
||||
func LoadClassifier(path string) (*Classifier, error) {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var c Classifier
|
||||
if err := yaml.Unmarshal(b, &c); err != nil {
|
||||
return nil, fmt.Errorf("parse classifier config %s: %w", path, err)
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// LoadClassifierFromEnv loads the classifier named by OO_CATALOG_CONFIG. An
|
||||
// empty variable yields the neutral classifier.
|
||||
func LoadClassifierFromEnv() (*Classifier, error) {
|
||||
path := strings.TrimSpace(os.Getenv("OO_CATALOG_CONFIG"))
|
||||
if path == "" {
|
||||
return DefaultClassifier(), nil
|
||||
}
|
||||
return LoadClassifier(path)
|
||||
}
|
||||
|
||||
// ClassifyProject returns (role, zone) for a project name and git remote.
|
||||
// Generic name heuristics come first; deployment rules supply the work cases.
|
||||
func (c *Classifier) ClassifyProject(name, remote string) (role, zone string) {
|
||||
lower := strings.ToLower(name)
|
||||
remoteL := strings.ToLower(remote)
|
||||
switch {
|
||||
case strings.Contains(lower, "experiment") || strings.HasPrefix(lower, "test"):
|
||||
return "experiment", "cold"
|
||||
case lower == "mama" || lower == "personal" || strings.Contains(lower, "private"):
|
||||
return "personal", "private"
|
||||
}
|
||||
if c != nil {
|
||||
for _, r := range c.WorkRemotes {
|
||||
if r != "" && strings.Contains(remoteL, strings.ToLower(r)) {
|
||||
return "work", "hot"
|
||||
}
|
||||
}
|
||||
for _, n := range c.WorkNames {
|
||||
if n != "" && strings.Contains(lower, strings.ToLower(n)) {
|
||||
return "work", "warm"
|
||||
}
|
||||
}
|
||||
}
|
||||
return "unknown", "warm"
|
||||
}
|
||||
|
||||
// ClassifyMail returns (org, zone, role) for a mail identity. name is the
|
||||
// display name (may be empty); email is the address.
|
||||
func (c *Classifier) ClassifyMail(name, email string) (org, zone, role string) {
|
||||
em := NormalizeEmail(email)
|
||||
_, domain, _ := strings.Cut(em, "@")
|
||||
nameL := strings.ToLower(strings.TrimSpace(name))
|
||||
if c != nil {
|
||||
for _, r := range c.MailOrgs {
|
||||
if !mailRuleMatches(r, domain, nameL) {
|
||||
continue
|
||||
}
|
||||
z, ro := r.Zone, r.Role
|
||||
if z == "" {
|
||||
z = "hot"
|
||||
}
|
||||
if ro == "" {
|
||||
ro = "work"
|
||||
}
|
||||
return r.Org, z, ro
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(domain, ".de") && looksPublicSector(domain) {
|
||||
return domain, "warm", "work"
|
||||
}
|
||||
return "", "private", "unknown"
|
||||
}
|
||||
|
||||
func mailRuleMatches(r MailRule, domain, nameL string) bool {
|
||||
if r.Domain != "" && domain == strings.ToLower(strings.TrimSpace(r.Domain)) {
|
||||
return true
|
||||
}
|
||||
if r.Suffix != "" && strings.HasSuffix(domain, strings.ToLower(strings.TrimSpace(r.Suffix))) {
|
||||
return true
|
||||
}
|
||||
if r.Name != "" && nameL != "" && strings.Contains(nameL, strings.ToLower(strings.TrimSpace(r.Name))) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user