This is an automated email from the ASF dual-hosted git repository. lahirujayathilake pushed a commit to branch provisioner-integration in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
commit 14d9e76b64ffe92b54306c9763746f6b9db6df89 Author: lahiruj <[email protected]> AuthorDate: Tue Jun 2 17:58:45 2026 -0400 throw an error when the first and last names are empty when building the cluster username --- .../ACCESS/AMIE-Processor/config.yaml.example | 3 -- connectors/ACCESS/AMIE-Processor/config/config.go | 16 +++-------- pkg/posix/username.go | 16 +++++++---- pkg/posix/username_test.go | 33 ++++++++++++++++++++-- 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/connectors/ACCESS/AMIE-Processor/config.yaml.example b/connectors/ACCESS/AMIE-Processor/config.yaml.example index af0149ffd..528c9039f 100644 --- a/connectors/ACCESS/AMIE-Processor/config.yaml.example +++ b/connectors/ACCESS/AMIE-Processor/config.yaml.example @@ -19,6 +19,3 @@ amie: log: level: "info" format: "text" - -provisioner: - type: "noop" diff --git a/connectors/ACCESS/AMIE-Processor/config/config.go b/connectors/ACCESS/AMIE-Processor/config/config.go index 7daa093c7..a365aae1e 100644 --- a/connectors/ACCESS/AMIE-Processor/config/config.go +++ b/connectors/ACCESS/AMIE-Processor/config/config.go @@ -27,11 +27,10 @@ import ( ) type Config struct { - Server ServerConfig `yaml:"server"` - Database DatabaseConfig `yaml:"database"` - AMIE AMIEConfig `yaml:"amie"` - Log LogConfig `yaml:"log"` - Provisioner ProvisionerConfig `yaml:"provisioner"` + Server ServerConfig `yaml:"server"` + Database DatabaseConfig `yaml:"database"` + AMIE AMIEConfig `yaml:"amie"` + Log LogConfig `yaml:"log"` } type ServerConfig struct { @@ -60,10 +59,6 @@ type LogConfig struct { Format string `yaml:"format"` // "text" or "json" } -type ProvisionerConfig struct { - Type string `yaml:"type"` // "noop" or "slurm" -} - // Load reads config from a YAML file and applies environment variable overrides. func Load(path string) (*Config, error) { data, err := os.ReadFile(path) @@ -113,9 +108,6 @@ func applyDefaults(cfg *Config) { if cfg.Log.Format == "" { cfg.Log.Format = "text" } - if cfg.Provisioner.Type == "" { - cfg.Provisioner.Type = "noop" - } } func applyEnvOverrides(cfg *Config) { diff --git a/pkg/posix/username.go b/pkg/posix/username.go index 64b00b5b8..e5fbd34c9 100644 --- a/pkg/posix/username.go +++ b/pkg/posix/username.go @@ -20,6 +20,8 @@ package posix import ( + "errors" + "fmt" "os" "strings" "unicode" @@ -29,9 +31,11 @@ import ( const MaxCollisionSuffix = 999 -// BuildBase returns the unsuffixed username and a flag set when the name -// portion was truncated to fit the 32-char Unix login limit. -func BuildBase(u *models.User, prefix string) (string, bool) { +var ErrUnbuildableUsername = errors.New("posix: cannot build username from empty first and last name") + +// BuildBase returns the unsuffixed username. 'truncated' is set when the name +// portion was shortened to fit the 32-char POSIX login cap. +func BuildBase(u *models.User, prefix string) (string, bool, error) { first := Normalize(u.FirstName) last := Normalize(u.LastName) @@ -44,17 +48,17 @@ func BuildBase(u *models.User, prefix string) (string, bool) { case first != "": local = first default: - local = "user" + return "", false, fmt.Errorf("%w: user %q (first=%q last=%q)", ErrUnbuildableUsername, u.ID, u.FirstName, u.LastName) } - // Reserve 3 chars for a numeric collision suffix (up to "999"). + // 32 = POSIX login cap; -1 separator, -3 reserved for collision suffix (up to "999"). maxLocal := 32 - len(prefix) - 1 - 3 truncated := false if len(local) > maxLocal { local = local[:maxLocal] truncated = true } - return prefix + "-" + local, truncated + return prefix + "-" + local, truncated, nil } func Normalize(s string) string { diff --git a/pkg/posix/username_test.go b/pkg/posix/username_test.go index a8601dad2..57b4f28f4 100644 --- a/pkg/posix/username_test.go +++ b/pkg/posix/username_test.go @@ -18,6 +18,7 @@ package posix import ( + "errors" "strings" "testing" @@ -55,8 +56,7 @@ func TestBuildBase(t *testing.T) { wantBase: "custos-alice", wantTrunc: false, }, { - name: "non-ASCII stripped", - // "Aña" normalizes to "aa"; first letter 'a' + "kili" (from "Şəkili") + name: "non-ASCII stripped", first: "Aña", last: "Şəkili", prefix: "custos", wantBase: "custos-akili", wantTrunc: false, }, @@ -75,7 +75,10 @@ func TestBuildBase(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { u := &models.User{FirstName: tc.first, MiddleName: tc.middle, LastName: tc.last} - got, trunc := BuildBase(u, tc.prefix) + got, trunc, err := BuildBase(u, tc.prefix) + if err != nil { + t.Fatalf("BuildBase: %v", err) + } if trunc != tc.wantTrunc { t.Errorf("truncated = %v, want %v", trunc, tc.wantTrunc) @@ -93,6 +96,30 @@ func TestBuildBase(t *testing.T) { } } +func TestBuildBase_UnbuildableReturnsError(t *testing.T) { + cases := []struct { + name string + first string + last string + }{ + {"both empty", "", ""}, + {"both non-ASCII only", "李", "王"}, + {"both punctuation only", "...", "---"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + u := &models.User{ID: "u-1", FirstName: tc.first, LastName: tc.last} + got, _, err := BuildBase(u, "custos") + if !errors.Is(err, ErrUnbuildableUsername) { + t.Fatalf("err = %v, want ErrUnbuildableUsername", err) + } + if got != "" { + t.Errorf("expected empty username on error, got %q", got) + } + }) + } +} + func TestNormalize(t *testing.T) { tests := []struct { in string
