From 3615c7c0fb0d01f6b8dcf24477aac4c6362e8ae6 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Fri, 2 Oct 2026 09:33:16 +0000 Subject: [PATCH] style(frontend): gofmt the apiserver and xcecli sources Formatting only (gofmt -w on the 29 non-vendored, non-generated files gofmt -l reported); swag-generated apiserver/docs/docs.go left as is. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../backend/auth/config_file_test.go | 8 +- .../apiserver/backend/integration_test.go | 2 +- src/frontend/apiserver/cmd/main.go | 2 +- src/frontend/apiserver/common/common.go | 20 +- .../apiserver/controllers/integration_test.go | 2 +- src/frontend/apiserver/controllers/jobs.go | 3 +- .../apiserver/controllers/jobs_test.go | 2 +- src/frontend/apiserver/controllers/output.go | 1 - .../apiserver/controllers/output_test.go | 3 +- .../apiserver/models/clusters_test.go | 42 +- src/frontend/apiserver/models/common.go | 34 +- src/frontend/apiserver/models/hosts.go | 6 +- src/frontend/apiserver/models/hosts_test.go | 2 +- src/frontend/apiserver/models/jobs.go | 44 +- src/frontend/apiserver/models/resources.go | 278 ++++++------- .../apiserver/store/concurrency_test.go | 4 +- src/frontend/apiserver/store/stores.go | 14 +- src/frontend/xcecli/cmd/auth/list.go | 100 ++--- src/frontend/xcecli/cmd/jobs/jobs.go | 76 ++-- src/frontend/xcecli/cmd/main.go | 222 +++++----- .../xcecli/cmd/resources/resources.go | 4 +- .../xcecli/common/output/output_clusters.go | 90 ++--- src/frontend/xcecli/config/logger.go | 378 +++++++++--------- src/frontend/xcecli/config/schema.go | 184 ++++----- src/frontend/xcecli/models/cluster.go | 44 +- src/frontend/xcecli/models/hosts.go | 344 ++++++++-------- src/frontend/xcecli/models/jobs.go | 180 +++++---- src/frontend/xcecli/models/queues.go | 242 +++++------ src/frontend/xcecli/router/router.go | 234 +++++------ 29 files changed, 1280 insertions(+), 1285 deletions(-) diff --git a/src/frontend/apiserver/backend/auth/config_file_test.go b/src/frontend/apiserver/backend/auth/config_file_test.go index 99873327..787aff8a 100644 --- a/src/frontend/apiserver/backend/auth/config_file_test.go +++ b/src/frontend/apiserver/backend/auth/config_file_test.go @@ -23,10 +23,10 @@ config_file: } tests := []struct { - name string - user string - pass string - wantErr bool + name string + user string + pass string + wantErr bool }{ {"valid admin", "admin", "secret", false}, {"valid viewer", "viewer", "pass", false}, diff --git a/src/frontend/apiserver/backend/integration_test.go b/src/frontend/apiserver/backend/integration_test.go index f7a21075..baa1cd98 100644 --- a/src/frontend/apiserver/backend/integration_test.go +++ b/src/frontend/apiserver/backend/integration_test.go @@ -515,4 +515,4 @@ func TestIntegration_CrossFeature_SequentialConfigPushes(t *testing.T) { if hb := atomic.LoadInt32(&heartbeatIntervalSecs); hb != 60 { t.Errorf("heartbeatIntervalSecs = %d, want 60", hb) } -} \ No newline at end of file +} diff --git a/src/frontend/apiserver/cmd/main.go b/src/frontend/apiserver/cmd/main.go index 7499ad26..fb5e4073 100644 --- a/src/frontend/apiserver/cmd/main.go +++ b/src/frontend/apiserver/cmd/main.go @@ -1,9 +1,9 @@ package main import ( - _ "apiserver/docs" "apiserver/backend" "apiserver/backend/auth" + _ "apiserver/docs" "apiserver/middleware" "apiserver/router" "apiserver/store" diff --git a/src/frontend/apiserver/common/common.go b/src/frontend/apiserver/common/common.go index 9f14cd61..043e4995 100644 --- a/src/frontend/apiserver/common/common.go +++ b/src/frontend/apiserver/common/common.go @@ -44,16 +44,16 @@ const ( ) const ( - SUCCESS_CODE = 200 - CREATED = 201 - ACCEPTED = 202 - StatusPartialContent = 206 - ERROR_CODE = 500 - INVALID_PARAMS_CODE = 400 - ERROR_AUTH_CHECK_TOKEN_CODE = 401 - ERROR_AUTH_CODE = 403 - ERROR_NOT_FOUND_CODE = 404 - ERROR_TOO_MANY_REQUESTS_CODE = 429 + SUCCESS_CODE = 200 + CREATED = 201 + ACCEPTED = 202 + StatusPartialContent = 206 + ERROR_CODE = 500 + INVALID_PARAMS_CODE = 400 + ERROR_AUTH_CHECK_TOKEN_CODE = 401 + ERROR_AUTH_CODE = 403 + ERROR_NOT_FOUND_CODE = 404 + ERROR_TOO_MANY_REQUESTS_CODE = 429 //read file error ERROR_READ_FILE_CODE = 501 diff --git a/src/frontend/apiserver/controllers/integration_test.go b/src/frontend/apiserver/controllers/integration_test.go index ab238fb1..1efcc8d8 100644 --- a/src/frontend/apiserver/controllers/integration_test.go +++ b/src/frontend/apiserver/controllers/integration_test.go @@ -675,4 +675,4 @@ func TestIntegration_SpecialCharactersInHostname(t *testing.T) { if hostnameVal != xssHostname { t.Errorf("hostname round-trip: got %q, want %q", hostnameVal, xssHostname) } -} \ No newline at end of file +} diff --git a/src/frontend/apiserver/controllers/jobs.go b/src/frontend/apiserver/controllers/jobs.go index 5709166b..26db72ed 100644 --- a/src/frontend/apiserver/controllers/jobs.go +++ b/src/frontend/apiserver/controllers/jobs.go @@ -27,7 +27,7 @@ import ( const ( //TODO: Need assign clusterID globally. - clusterID = 1 + clusterID = 1 apiserverID = 0 ) @@ -3190,4 +3190,3 @@ func GetHostList(hosts []string) ([]string, error) { } return execHosts, nil } - diff --git a/src/frontend/apiserver/controllers/jobs_test.go b/src/frontend/apiserver/controllers/jobs_test.go index b9b5a88b..9332c41e 100644 --- a/src/frontend/apiserver/controllers/jobs_test.go +++ b/src/frontend/apiserver/controllers/jobs_test.go @@ -1,11 +1,11 @@ package controllers import ( - "errors" "apiserver/backend" "apiserver/models" "apiserver/store" "apiserver/utils" + "errors" "strings" "testing" diff --git a/src/frontend/apiserver/controllers/output.go b/src/frontend/apiserver/controllers/output.go index ad793bdd..312349f7 100644 --- a/src/frontend/apiserver/controllers/output.go +++ b/src/frontend/apiserver/controllers/output.go @@ -242,4 +242,3 @@ func parseJobIDParam(jobidStr string) (models.JobInfo, error) { ArrayIdx: 0, // peek defaults to base job; array idx can be added later }, nil } - diff --git a/src/frontend/apiserver/controllers/output_test.go b/src/frontend/apiserver/controllers/output_test.go index 30119edb..47633f04 100644 --- a/src/frontend/apiserver/controllers/output_test.go +++ b/src/frontend/apiserver/controllers/output_test.go @@ -237,7 +237,7 @@ func TestGetJobOutput_DefaultPathFallback(t *testing.T) { // Use a real-ish packed job ID: cluster=1, apiserver=0, ts=1778311965, seq=0 // FormatJobID → "1-0-1778311965-0" // WorkerDefaultOutputPath → "/tmp/job_1-0-1778311965-0_task0.out" - const uniqueID = int64(0x0100000006A04BDD<<0) // MakeJobID(1,0,1778311965,0,0) + const uniqueID = int64(0x0100000006A04BDD << 0) // MakeJobID(1,0,1778311965,0,0) // Use backend.MakeJobID directly for clarity; compute inline: // clusterID=1 at bit 56, timestamp=1778311965 at bit 20 // packed = (1 << 56) | (1778311965 << 20) @@ -512,4 +512,3 @@ func TestGetJobOutput_DoneJob(t *testing.T) { t.Errorf("job_status = %q, want DONE", resp.Data.JobStatus) } } - diff --git a/src/frontend/apiserver/models/clusters_test.go b/src/frontend/apiserver/models/clusters_test.go index 34f57282..1dce9334 100644 --- a/src/frontend/apiserver/models/clusters_test.go +++ b/src/frontend/apiserver/models/clusters_test.go @@ -7,46 +7,46 @@ import ( func TestClusterInfo_NewFields(t *testing.T) { tests := []struct { - name string + name string cluster ClusterInfo - field string + field string want interface{} }{ { - name: "HeartbeatInterval field exists and serializes", + name: "HeartbeatInterval field exists and serializes", cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 30}, - field: "heartbeat_interval", - want: float64(30), + field: "heartbeat_interval", + want: float64(30), }, { - name: "Vcpus field exists and serializes", + name: "Vcpus field exists and serializes", cluster: ClusterInfo{ClusterName: "test", Vcpus: 16}, - field: "vcpus", - want: float64(16), + field: "vcpus", + want: float64(16), }, { - name: "InactiveHeartbeatCount field exists and serializes", + name: "InactiveHeartbeatCount field exists and serializes", cluster: ClusterInfo{ClusterName: "test", InactiveHeartbeatCount: 3}, - field: "inactive_heartbeat_count", - want: float64(3), + field: "inactive_heartbeat_count", + want: float64(3), }, { - name: "HeartbeatInterval zero value serializes as 0 not omitted", + name: "HeartbeatInterval zero value serializes as 0 not omitted", cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0}, - field: "heartbeat_interval", - want: float64(0), + field: "heartbeat_interval", + want: float64(0), }, { - name: "Vcpus zero value serializes as 0 not omitted", + name: "Vcpus zero value serializes as 0 not omitted", cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0}, - field: "vcpus", - want: float64(0), + field: "vcpus", + want: float64(0), }, { - name: "InactiveHeartbeatCount zero value serializes as 0 not omitted", + name: "InactiveHeartbeatCount zero value serializes as 0 not omitted", cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0}, - field: "inactive_heartbeat_count", - want: float64(0), + field: "inactive_heartbeat_count", + want: float64(0), }, } @@ -122,4 +122,4 @@ func mapKeys(m map[string]interface{}) []string { keys = append(keys, k) } return keys -} \ No newline at end of file +} diff --git a/src/frontend/apiserver/models/common.go b/src/frontend/apiserver/models/common.go index 9a5fd08a..3700f8d6 100644 --- a/src/frontend/apiserver/models/common.go +++ b/src/frontend/apiserver/models/common.go @@ -1,17 +1,17 @@ -package models - -const ( - CONTROL_JOB = 1 - CONTROL_HOST = 2 - CONTROL_QUEUE = 3 - CONTROL_CLUSTER = 4 - CONTROL_DEBUG = 5 -) - -type ActionControl struct { - TargetType int `json:"target_type"` - ActionType int `json:"action_type"` - Content Jsonb `json:"content"` - SubmitTime int64 `json:"submit_time"` - Submitter string `json:"submitter"` -} +package models + +const ( + CONTROL_JOB = 1 + CONTROL_HOST = 2 + CONTROL_QUEUE = 3 + CONTROL_CLUSTER = 4 + CONTROL_DEBUG = 5 +) + +type ActionControl struct { + TargetType int `json:"target_type"` + ActionType int `json:"action_type"` + Content Jsonb `json:"content"` + SubmitTime int64 `json:"submit_time"` + Submitter string `json:"submitter"` +} diff --git a/src/frontend/apiserver/models/hosts.go b/src/frontend/apiserver/models/hosts.go index 0b47273f..d526d880 100644 --- a/src/frontend/apiserver/models/hosts.go +++ b/src/frontend/apiserver/models/hosts.go @@ -88,9 +88,9 @@ type HostInfo struct { NumSSusp int `json:"num_ssusp"` NumUSusp int `json:"num_ususp"` //NumRsv int `json:"num_rsv"` - NumDone int `json:"num_done"` - Topology string `json:"topology"` // JSONB - SchedStop string `json:"sched_stop"` // JSONB + NumDone int `json:"num_done"` + Topology string `json:"topology"` // JSONB + SchedStop string `json:"sched_stop"` // JSONB ResourceValues []ResourceDistributionInfo `json:"resource_values"` // per-worker resource values //RealLoad string `json:"real_load"` // JSONB } diff --git a/src/frontend/apiserver/models/hosts_test.go b/src/frontend/apiserver/models/hosts_test.go index f9d36292..9d7d75c6 100644 --- a/src/frontend/apiserver/models/hosts_test.go +++ b/src/frontend/apiserver/models/hosts_test.go @@ -68,4 +68,4 @@ func TestHostStateStr(t *testing.T) { } }) } -} \ No newline at end of file +} diff --git a/src/frontend/apiserver/models/jobs.go b/src/frontend/apiserver/models/jobs.go index 4dc0359a..93e60427 100644 --- a/src/frontend/apiserver/models/jobs.go +++ b/src/frontend/apiserver/models/jobs.go @@ -591,32 +591,32 @@ type ArrayJobInfo struct { } type JobAdditionalFields struct { - JobID int64 `json:"job_id"` - SubmitUser string `json:"submit_user"` - JobStatus int `json:"job_status"` + JobID int64 `json:"job_id"` + SubmitUser string `json:"submit_user"` + JobStatus int `json:"job_status"` SubmitTime int64 `json:"submit_time"` FromHost string `json:"from_host"` ExecHosts string `json:"exec_hosts"` - StartTime int64 `json:"start_time"` - EndTime int64 `json:"end_time"` - JobRusage Jsonb `json:"job_rusage"` - MaxMem float64 `json:"max_mem"` - AvgMem float64 `json:"avg_mem"` - UsedMem float64 `json:"used_mem"` - CPUTime int `json:"cpu_time"` - PeakCPUTime int `json:"peak_cpu_time"` - PeakCPUDuration float64 `json:"peak_cpu_duration"` - MemEfficiency float64 `json:"mem_efficiency"` // Memory efficiency in percentage - AvgCPUEfficiency float64 `json:"avg_cpu_efficiency"` // Average CPU efficiency in percentage - PeakCPUEfficiency float64 `json:"peak_cpu_efficiency"` // Peak CPU efficiency in percentage - NumHosts int `json:"num_hosts"` // Number of hosts used by the job - FirstHost string `json:"first_host"` // First host where the job ran - AllocSlot string `json:"alloc_slots"` // Allocated slot information - PIDInfo Jsonb `json:"pid_info"` - Pids []int `json:"pids"` // List of process IDs associated with the job - Pgids []int `json:"pgids"` // List of process group IDs associated with the job - InteractiveJobURL string `json:"interactive_job_url"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` + JobRusage Jsonb `json:"job_rusage"` + MaxMem float64 `json:"max_mem"` + AvgMem float64 `json:"avg_mem"` + UsedMem float64 `json:"used_mem"` + CPUTime int `json:"cpu_time"` + PeakCPUTime int `json:"peak_cpu_time"` + PeakCPUDuration float64 `json:"peak_cpu_duration"` + MemEfficiency float64 `json:"mem_efficiency"` // Memory efficiency in percentage + AvgCPUEfficiency float64 `json:"avg_cpu_efficiency"` // Average CPU efficiency in percentage + PeakCPUEfficiency float64 `json:"peak_cpu_efficiency"` // Peak CPU efficiency in percentage + NumHosts int `json:"num_hosts"` // Number of hosts used by the job + FirstHost string `json:"first_host"` // First host where the job ran + AllocSlot string `json:"alloc_slots"` // Allocated slot information + PIDInfo Jsonb `json:"pid_info"` + Pids []int `json:"pids"` // List of process IDs associated with the job + Pgids []int `json:"pgids"` // List of process group IDs associated with the job + InteractiveJobURL string `json:"interactive_job_url"` // CoreLimit/MemLimit: JobInfo has no equivalent field (these are // submission-time resource limits, not job-state fields), but xcecli's // JobFieldsMap accepts them as valid -o/-l display fields (bug-1674) -- diff --git a/src/frontend/apiserver/models/resources.go b/src/frontend/apiserver/models/resources.go index f2cf868c..e6afde0c 100644 --- a/src/frontend/apiserver/models/resources.go +++ b/src/frontend/apiserver/models/resources.go @@ -1,139 +1,139 @@ -package models - -type ValueType int - -const ( - STRING ValueType = iota // Bool value - NUMERIC // Numeric value - BOOLEAN // String value -) - -// String returns the string representation of ValueType -func (v ValueType) String() string { - switch v { - case BOOLEAN: - return "Boolean" - case NUMERIC: - return "Numeric" - case STRING: - return "String" - default: - return "Unknown" - } -} - -type OrderType int - -const ( - INCR OrderType = iota - DECR - NA -) - -// String returns the string representation of OrderType -func (o OrderType) String() string { - switch o { - case INCR: - return "Incr" - case DECR: - return "Decr" - case NA: - return "NA" - default: - return "Unknown" - } -} - -var RESOURCE_DEFAULT_COLUMN = []string{ - "res_name", - "description", - "value_type", - "order_type", - "flag", - "interval", -} - -var RESOURCE_DISTRIBUTION_DEFAULT_COLUMN = []string{ - "res_name", - "numeric_value", - "location", - "is_dynamic", -} - -var RESOURCE_DEFINITION_DEFAULT_COLUMN = []string{ - "res_name", - "description", - "value_type", - "order_type", - "flag", - "interval", -} - -var ResourceFieldMappings = map[string]string{ - // API field name → struct field name - "value": "numeric_value", -} - -var ResourcesAvailableTypes = []string{"static", "dynamic"} - -const ( - BUILTIN = 0x01 - DYNAMIC = 0x02 - RELEASE = 0x20 - CONSUMABLE = 0x80 -) - -type FlagValue int - -// ToJSON returns the JSON object representation of FlagValue -func (f FlagValue) ToJSON() map[string]bool { - return map[string]bool{ - "builtin": f&BUILTIN != 0, - "dynamic": f&DYNAMIC != 0, - "release": f&RELEASE != 0, - "consumable": f&CONSUMABLE != 0, - } -} - -type ResourceInfo struct { - ResName string - Description string - ValueType ValueType - OrderType OrderType - Flag int - Interval int -} - -// queues info query supported filter -var RESOURCE_DISTRIBUTION_SUPPORT_FILTERS = []string{ - "res_name", - "is_dynamic", - "location", -} - -var RESOURCE_DEFINITION_SUPPORT_FILTERS = []string{ - "res_name", -} - -type ResourceDistributionInfo struct { - ResourceName string `json:"res_name"` - IsDynamic bool `json:"is_dynamic"` - ValueType int `json:"value_type"` - BooleanValue bool `json:"boolean_value"` - StringValue string `json:"string_value"` - NumericValue float64 `json:"numeric_value"` - Location string `json:"location"` -} - -type ResourceDefinitionInfo struct { - ResourceName string `json:"res_name"` - Description string `json:"description"` - ValueType int `json:"value_type"` - OrderType int `json:"order_type"` - Flag int `json:"flag"` - Interval int `json:"interval"` -} - -var RESOURCE_DISTRIBUTION_SUPPORT_SORT = []string{ - "res_name", -} +package models + +type ValueType int + +const ( + STRING ValueType = iota // Bool value + NUMERIC // Numeric value + BOOLEAN // String value +) + +// String returns the string representation of ValueType +func (v ValueType) String() string { + switch v { + case BOOLEAN: + return "Boolean" + case NUMERIC: + return "Numeric" + case STRING: + return "String" + default: + return "Unknown" + } +} + +type OrderType int + +const ( + INCR OrderType = iota + DECR + NA +) + +// String returns the string representation of OrderType +func (o OrderType) String() string { + switch o { + case INCR: + return "Incr" + case DECR: + return "Decr" + case NA: + return "NA" + default: + return "Unknown" + } +} + +var RESOURCE_DEFAULT_COLUMN = []string{ + "res_name", + "description", + "value_type", + "order_type", + "flag", + "interval", +} + +var RESOURCE_DISTRIBUTION_DEFAULT_COLUMN = []string{ + "res_name", + "numeric_value", + "location", + "is_dynamic", +} + +var RESOURCE_DEFINITION_DEFAULT_COLUMN = []string{ + "res_name", + "description", + "value_type", + "order_type", + "flag", + "interval", +} + +var ResourceFieldMappings = map[string]string{ + // API field name → struct field name + "value": "numeric_value", +} + +var ResourcesAvailableTypes = []string{"static", "dynamic"} + +const ( + BUILTIN = 0x01 + DYNAMIC = 0x02 + RELEASE = 0x20 + CONSUMABLE = 0x80 +) + +type FlagValue int + +// ToJSON returns the JSON object representation of FlagValue +func (f FlagValue) ToJSON() map[string]bool { + return map[string]bool{ + "builtin": f&BUILTIN != 0, + "dynamic": f&DYNAMIC != 0, + "release": f&RELEASE != 0, + "consumable": f&CONSUMABLE != 0, + } +} + +type ResourceInfo struct { + ResName string + Description string + ValueType ValueType + OrderType OrderType + Flag int + Interval int +} + +// queues info query supported filter +var RESOURCE_DISTRIBUTION_SUPPORT_FILTERS = []string{ + "res_name", + "is_dynamic", + "location", +} + +var RESOURCE_DEFINITION_SUPPORT_FILTERS = []string{ + "res_name", +} + +type ResourceDistributionInfo struct { + ResourceName string `json:"res_name"` + IsDynamic bool `json:"is_dynamic"` + ValueType int `json:"value_type"` + BooleanValue bool `json:"boolean_value"` + StringValue string `json:"string_value"` + NumericValue float64 `json:"numeric_value"` + Location string `json:"location"` +} + +type ResourceDefinitionInfo struct { + ResourceName string `json:"res_name"` + Description string `json:"description"` + ValueType int `json:"value_type"` + OrderType int `json:"order_type"` + Flag int `json:"flag"` + Interval int `json:"interval"` +} + +var RESOURCE_DISTRIBUTION_SUPPORT_SORT = []string{ + "res_name", +} diff --git a/src/frontend/apiserver/store/concurrency_test.go b/src/frontend/apiserver/store/concurrency_test.go index bd41e638..1b36f28d 100644 --- a/src/frontend/apiserver/store/concurrency_test.go +++ b/src/frontend/apiserver/store/concurrency_test.go @@ -102,7 +102,7 @@ func TestIntegration_ConcurrentResourceValuePushes(t *testing.T) { ResourceName: "mem", Location: "node01", NumericValue: float64(idx + 1), - ValueType: 1, // NUMERIC + ValueType: 1, // NUMERIC IsDynamic: true, }) count.Add(1) @@ -129,4 +129,4 @@ func TestIntegration_ConcurrentResourceValuePushes(t *testing.T) { if ResourceLocations.Count() != 1 { t.Errorf("ResourceLocations count = %d, want 1 (key exists exactly once)", ResourceLocations.Count()) } -} \ No newline at end of file +} diff --git a/src/frontend/apiserver/store/stores.go b/src/frontend/apiserver/store/stores.go index 81b7a3a1..6fd60524 100644 --- a/src/frontend/apiserver/store/stores.go +++ b/src/frontend/apiserver/store/stores.go @@ -4,16 +4,16 @@ import "apiserver/models" // Global store instances. var ( - Jobs *DataStore[uint64, models.JobInfo] - ArrayJobs *DataStore[uint64, models.ArrayJobInfo] - Queues *DataStore[string, models.QueueInfo] - Hosts *DataStore[string, models.HostInfo] + Jobs *DataStore[uint64, models.JobInfo] + ArrayJobs *DataStore[uint64, models.ArrayJobInfo] + Queues *DataStore[string, models.QueueInfo] + Hosts *DataStore[string, models.HostInfo] Resources *DataStore[string, models.ResourceDefinitionInfo] ResourceLocations *DataStore[string, models.ResourceDistributionInfo] Clusters *DataStore[string, models.ClusterInfo] - JobExecHosts *DataStore[int64, models.JobExecHosts] - Roles *DataStore[string, RoleEntry] - UserRoles *DataStore[string, []string] // user_name → role_names + JobExecHosts *DataStore[int64, models.JobExecHosts] + Roles *DataStore[string, RoleEntry] + UserRoles *DataStore[string, []string] // user_name → role_names ) // RoleEntry holds a role's permissions. diff --git a/src/frontend/xcecli/cmd/auth/list.go b/src/frontend/xcecli/cmd/auth/list.go index 58324f69..cdc7853b 100755 --- a/src/frontend/xcecli/cmd/auth/list.go +++ b/src/frontend/xcecli/cmd/auth/list.go @@ -1,50 +1,50 @@ -package cmd - -import ( - "fmt" - "os" - "text/tabwriter" - "xcecli/common/request" - "xcecli/config" - - "github.com/spf13/cobra" -) - -// NewListCmd create the list command -func NewListCmd(configManager *config.ConfigManager) *cobra.Command { - return &cobra.Command{ - Use: "list", - GroupID: "api", - Short: "List the API Servers", - Long: "List all the configured API Servers and the default API Server", - RunE: func(cmd *cobra.Command, args []string) error { - return runList(configManager) - - }, - } -} - -func runList(cm *config.ConfigManager) error { - cfg, err := cm.GetConfig() - if err != nil { - errMsg := request.ExtractErrorMessage(err) - return fmt.Errorf("failed to get the config: [%s]", errMsg) - } - - // use tabwriter to format the output - w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) - fmt.Fprintln(w, "Default\tVersion\tURL") - - for _, server := range cfg.APIServerInfo { - isDefault := " " - if server.Token == "" { - continue - } - - fmt.Fprintf(w, "%s\t%s\n", - isDefault, - server.URL) - } - - return w.Flush() -} +package cmd + +import ( + "fmt" + "os" + "text/tabwriter" + "xcecli/common/request" + "xcecli/config" + + "github.com/spf13/cobra" +) + +// NewListCmd create the list command +func NewListCmd(configManager *config.ConfigManager) *cobra.Command { + return &cobra.Command{ + Use: "list", + GroupID: "api", + Short: "List the API Servers", + Long: "List all the configured API Servers and the default API Server", + RunE: func(cmd *cobra.Command, args []string) error { + return runList(configManager) + + }, + } +} + +func runList(cm *config.ConfigManager) error { + cfg, err := cm.GetConfig() + if err != nil { + errMsg := request.ExtractErrorMessage(err) + return fmt.Errorf("failed to get the config: [%s]", errMsg) + } + + // use tabwriter to format the output + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + fmt.Fprintln(w, "Default\tVersion\tURL") + + for _, server := range cfg.APIServerInfo { + isDefault := " " + if server.Token == "" { + continue + } + + fmt.Fprintf(w, "%s\t%s\n", + isDefault, + server.URL) + } + + return w.Flush() +} diff --git a/src/frontend/xcecli/cmd/jobs/jobs.go b/src/frontend/xcecli/cmd/jobs/jobs.go index 2ae57cc0..1d419563 100755 --- a/src/frontend/xcecli/cmd/jobs/jobs.go +++ b/src/frontend/xcecli/cmd/jobs/jobs.go @@ -1,38 +1,38 @@ -package jobs - -import ( - "xcecli/config" - - "github.com/spf13/cobra" -) - -// NewJobsCmd creates a jobs query command -func NewJobsCmd(configManager *config.ConfigManager) *cobra.Command { - - // Create the parent jobs command - cmd := &cobra.Command{ - Use: "jobs", - GroupID: "query", - Short: "Manage jobs", - Long: `Manage jobs in the system. -Use 'xce jobs list' to query jobs information. -Use 'xce jobs ' to submit/kill/stop/resume/checkpoint/restart jobs.`, - Run: func(cmd *cobra.Command, args []string) { - // Display help when no subcommand is specified - cmd.Help() - }, - } - cmd.AddCommand(NewListJobsCmd(configManager)) - // Add submit command to jobs command - cmd.AddCommand(NewSubmitJobsCmd(configManager)) - // Add job control sub-commands to jobs command - cmd.AddCommand(NewKillJobsCmd(configManager)) - cmd.AddCommand(NewStopJobsCmd(configManager)) - cmd.AddCommand(NewResumeJobsCmd(configManager)) - cmd.AddCommand(NewCheckPointJobsCmd(configManager)) - cmd.AddCommand(NewRestartJobsCmd(configManager)) - // Add peek command to view job output - cmd.AddCommand(NewPeekJobsCmd(configManager)) - - return cmd -} +package jobs + +import ( + "xcecli/config" + + "github.com/spf13/cobra" +) + +// NewJobsCmd creates a jobs query command +func NewJobsCmd(configManager *config.ConfigManager) *cobra.Command { + + // Create the parent jobs command + cmd := &cobra.Command{ + Use: "jobs", + GroupID: "query", + Short: "Manage jobs", + Long: `Manage jobs in the system. +Use 'xce jobs list' to query jobs information. +Use 'xce jobs ' to submit/kill/stop/resume/checkpoint/restart jobs.`, + Run: func(cmd *cobra.Command, args []string) { + // Display help when no subcommand is specified + cmd.Help() + }, + } + cmd.AddCommand(NewListJobsCmd(configManager)) + // Add submit command to jobs command + cmd.AddCommand(NewSubmitJobsCmd(configManager)) + // Add job control sub-commands to jobs command + cmd.AddCommand(NewKillJobsCmd(configManager)) + cmd.AddCommand(NewStopJobsCmd(configManager)) + cmd.AddCommand(NewResumeJobsCmd(configManager)) + cmd.AddCommand(NewCheckPointJobsCmd(configManager)) + cmd.AddCommand(NewRestartJobsCmd(configManager)) + // Add peek command to view job output + cmd.AddCommand(NewPeekJobsCmd(configManager)) + + return cmd +} diff --git a/src/frontend/xcecli/cmd/main.go b/src/frontend/xcecli/cmd/main.go index 7c1bdd98..bc9408d3 100755 --- a/src/frontend/xcecli/cmd/main.go +++ b/src/frontend/xcecli/cmd/main.go @@ -1,111 +1,111 @@ -/* -The MIT License (MIT) - -Copyright © 2025 xce - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -package main - -import ( - "fmt" - "os" - "strings" - "xcecli/config" - logger "xcecli/config" - cmd "xcecli/router" - - "go.uber.org/zap" -) - -// GitCommit and BuildTime are injected at link time via -// `-ldflags "-X main.GitCommit=... -X main.BuildTime=..."` (see -// src/tools/go_build). The -// "unknown" defaults matter: a bare `go build`/`go run` that bypasses that -// ldflags injection must still report "unknown" rather than an empty -// string, matching this feature's unknown-fallback requirement (see -// xce_version_field() in src/lib/utils.c for the equivalent C-side -// convention). -// -// DirtyFingerprint is injected the same way (`-X main.DirtyFingerprint=...`, -// see src/tools/go_build task 4.1) but its default is a deliberately EMPTY -// string, not "unknown" -- matching dirty_fingerprint's convention -// everywhere else in the dirty-content-fingerprint feature (empty means -// "no fingerprint available", distinct from GitCommit/BuildTime's -// "unknown" sentinel for "not injected at all"). -var ( - GitCommit = "unknown" - BuildTime = "unknown" - DirtyFingerprint = "" -) - -// composeVersionDisplay applies the dirty-content-fingerprint feature's -// strip-and-append rule to gitCommit: if dirtyFingerprint is non-empty, a -// trailing "-dirty" suffix on gitCommit is stripped (an anchored suffix -// check via strings.TrimSuffix, not a blind substring replace) and -// "-" is appended in its place; otherwise gitCommit is -// returned exactly as-is (already correctly showing "-dirty" or not, per -// the original build-time-versioning feature). Mirrors -// xce_compose_version_ex() in src/lib/utils.c (task 3.1) and apiserver's -// own composeVersionDisplay (task 4.4), the equivalent implementations -// already approved for the other version-displaying components. -// -// xcecli has no startup log to protect (already established as out of -// scope by the original build-time-versioning feature, see that feature's -// own task 4.5 commit fb09afe), so this result feeds the ONLY consumption -// point below: the version string passed into router.Execute(cm, version), -// which sets rootCmd.Version for Cobra's native --version/-v handling. -func composeVersionDisplay(gitCommit, dirtyFingerprint string) string { - if dirtyFingerprint == "" { - return gitCommit - } - return strings.TrimSuffix(gitCommit, "-dirty") + "-" + dirtyFingerprint -} - -func main() { - - // init logger - if err := logger.InitLogger(); err != nil { - fmt.Println(strings.ToLower(err.Error())) - os.Exit(1) - } - logger.Info("********** Initialize Logger on client side done!") - - // initialize the config manager - configManager, err := config.NewConfigManager() - if err != nil { - fmt.Println("failed to initialize the config manager", zap.Error(err)) - os.Exit(1) - } - - // ensure the config manager is initialized correctly - if configManager == nil { - fmt.Println("failed to initialize the config manager") - os.Exit(1) - } - - // execute the command - version := fmt.Sprintf("%s built %s", composeVersionDisplay(GitCommit, DirtyFingerprint), BuildTime) - if err := cmd.Execute(configManager, version); err != nil { - fmt.Println(strings.ToLower(err.Error())) - os.Exit(1) - } -} +/* +The MIT License (MIT) + +Copyright © 2025 xce + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +package main + +import ( + "fmt" + "os" + "strings" + "xcecli/config" + logger "xcecli/config" + cmd "xcecli/router" + + "go.uber.org/zap" +) + +// GitCommit and BuildTime are injected at link time via +// `-ldflags "-X main.GitCommit=... -X main.BuildTime=..."` (see +// src/tools/go_build). The +// "unknown" defaults matter: a bare `go build`/`go run` that bypasses that +// ldflags injection must still report "unknown" rather than an empty +// string, matching this feature's unknown-fallback requirement (see +// xce_version_field() in src/lib/utils.c for the equivalent C-side +// convention). +// +// DirtyFingerprint is injected the same way (`-X main.DirtyFingerprint=...`, +// see src/tools/go_build task 4.1) but its default is a deliberately EMPTY +// string, not "unknown" -- matching dirty_fingerprint's convention +// everywhere else in the dirty-content-fingerprint feature (empty means +// "no fingerprint available", distinct from GitCommit/BuildTime's +// "unknown" sentinel for "not injected at all"). +var ( + GitCommit = "unknown" + BuildTime = "unknown" + DirtyFingerprint = "" +) + +// composeVersionDisplay applies the dirty-content-fingerprint feature's +// strip-and-append rule to gitCommit: if dirtyFingerprint is non-empty, a +// trailing "-dirty" suffix on gitCommit is stripped (an anchored suffix +// check via strings.TrimSuffix, not a blind substring replace) and +// "-" is appended in its place; otherwise gitCommit is +// returned exactly as-is (already correctly showing "-dirty" or not, per +// the original build-time-versioning feature). Mirrors +// xce_compose_version_ex() in src/lib/utils.c (task 3.1) and apiserver's +// own composeVersionDisplay (task 4.4), the equivalent implementations +// already approved for the other version-displaying components. +// +// xcecli has no startup log to protect (already established as out of +// scope by the original build-time-versioning feature, see that feature's +// own task 4.5 commit fb09afe), so this result feeds the ONLY consumption +// point below: the version string passed into router.Execute(cm, version), +// which sets rootCmd.Version for Cobra's native --version/-v handling. +func composeVersionDisplay(gitCommit, dirtyFingerprint string) string { + if dirtyFingerprint == "" { + return gitCommit + } + return strings.TrimSuffix(gitCommit, "-dirty") + "-" + dirtyFingerprint +} + +func main() { + + // init logger + if err := logger.InitLogger(); err != nil { + fmt.Println(strings.ToLower(err.Error())) + os.Exit(1) + } + logger.Info("********** Initialize Logger on client side done!") + + // initialize the config manager + configManager, err := config.NewConfigManager() + if err != nil { + fmt.Println("failed to initialize the config manager", zap.Error(err)) + os.Exit(1) + } + + // ensure the config manager is initialized correctly + if configManager == nil { + fmt.Println("failed to initialize the config manager") + os.Exit(1) + } + + // execute the command + version := fmt.Sprintf("%s built %s", composeVersionDisplay(GitCommit, DirtyFingerprint), BuildTime) + if err := cmd.Execute(configManager, version); err != nil { + fmt.Println(strings.ToLower(err.Error())) + os.Exit(1) + } +} diff --git a/src/frontend/xcecli/cmd/resources/resources.go b/src/frontend/xcecli/cmd/resources/resources.go index 98917797..5d3b0e9f 100644 --- a/src/frontend/xcecli/cmd/resources/resources.go +++ b/src/frontend/xcecli/cmd/resources/resources.go @@ -26,7 +26,7 @@ Use 'xce resources definition list' to query resources definition information.`, distributionCmd := &cobra.Command{ Use: "distribution", Short: "Manage resource distribution", - Long: `Manage resource distribution information. + Long: `Manage resource distribution information. Use 'xce resources distribution list' to query resources distribution information.`, Run: func(cmd *cobra.Command, args []string) { // Display help when no subcommand is specified @@ -39,7 +39,7 @@ Use 'xce resources distribution list' to query resources distribution informatio definitionCmd := &cobra.Command{ Use: "definition", Short: "Manage resource definition", - Long: `Manage resource definition information. + Long: `Manage resource definition information. Use 'xce resources definition list' to query resources definition information.`, Run: func(cmd *cobra.Command, args []string) { // Display help when no subcommand is specified diff --git a/src/frontend/xcecli/common/output/output_clusters.go b/src/frontend/xcecli/common/output/output_clusters.go index 17cc9d86..de48ff48 100755 --- a/src/frontend/xcecli/common/output/output_clusters.go +++ b/src/frontend/xcecli/common/output/output_clusters.go @@ -1,45 +1,45 @@ -package output - -import ( - "fmt" - "os" - "text/tabwriter" - "xcecli/models" -) - -type Output struct { - fields string - formatJson bool - noheader bool - verbose bool - wideFormat bool - jobStatusFilter []string -} - -func NewOutput(fields string, formatJson bool, noheader bool) *Output { - return &Output{fields: fields, formatJson: formatJson, noheader: noheader} -} - -func (o *Output) SetVerbose(verbose bool) { - o.verbose = verbose -} - -func (o *Output) SetNoHeader(noheader bool) { - o.noheader = noheader -} - -func (o *Output) SetWideFormat(wideFormat bool) { - o.wideFormat = wideFormat -} - -func (o *Output) ClusterPrintData(cluster *models.ClusterInfo) { - printClustersVerboseOutput(cluster) -} - -func printClustersVerboseOutput(clusters *models.ClusterInfo) { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) - defer w.Flush() - fmt.Fprintf(w, "XCE Standard Cluster\t%s\t%s\n", clusters.Version, "Jul 08 2025") - fmt.Fprintf(w, "My cluster name: <%s>\n", clusters.ClusterName) - fmt.Fprintf(w, "The cluster status: <%s>\n", clusters.Status) -} +package output + +import ( + "fmt" + "os" + "text/tabwriter" + "xcecli/models" +) + +type Output struct { + fields string + formatJson bool + noheader bool + verbose bool + wideFormat bool + jobStatusFilter []string +} + +func NewOutput(fields string, formatJson bool, noheader bool) *Output { + return &Output{fields: fields, formatJson: formatJson, noheader: noheader} +} + +func (o *Output) SetVerbose(verbose bool) { + o.verbose = verbose +} + +func (o *Output) SetNoHeader(noheader bool) { + o.noheader = noheader +} + +func (o *Output) SetWideFormat(wideFormat bool) { + o.wideFormat = wideFormat +} + +func (o *Output) ClusterPrintData(cluster *models.ClusterInfo) { + printClustersVerboseOutput(cluster) +} + +func printClustersVerboseOutput(clusters *models.ClusterInfo) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + defer w.Flush() + fmt.Fprintf(w, "XCE Standard Cluster\t%s\t%s\n", clusters.Version, "Jul 08 2025") + fmt.Fprintf(w, "My cluster name: <%s>\n", clusters.ClusterName) + fmt.Fprintf(w, "The cluster status: <%s>\n", clusters.Status) +} diff --git a/src/frontend/xcecli/config/logger.go b/src/frontend/xcecli/config/logger.go index e89e1ebd..3d29f390 100755 --- a/src/frontend/xcecli/config/logger.go +++ b/src/frontend/xcecli/config/logger.go @@ -1,189 +1,189 @@ -package config - -import ( - "fmt" - "os" - "os/user" - "path/filepath" - - "github.com/spf13/viper" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "gopkg.in/natefinch/lumberjack.v2" -) - -var ( - Log *zap.Logger -) - -const ( - defaultLoggerConfigFile = "logger.yaml" - defaultLoggerOutputFile = "xcecli_out.log" -) - -type LoggerConfig struct { - Logging struct { - Level string `yaml:"level"` - Encoding string `yaml:"encoding"` - OutputPath string `yaml:"outputPath"` - File struct { - MaxSize int `yaml:"maxSize"` - MaxBackups int `yaml:"maxBackups"` - MaxAge int `yaml:"maxAge"` - Compress bool `yaml:"compress"` - } `yaml:"file"` - EncoderConfig struct { - MessageKey string `yaml:"messageKey"` - LevelKey string `yaml:"levelKey"` - TimeKey string `yaml:"timeKey"` - NameKey string `yaml:"nameKey"` - CallerKey string `yaml:"callerKey"` - FunctionKey string `yaml:"functionKey"` - StacktraceKey string `yaml:"stacktraceKey"` - EncodeTime string `yaml:"timeEncoder"` - EncodeLevel string `yaml:"levelEncoder"` - } `yaml:"encoderConfig"` - } `yaml:"logging"` -} - -func InitLogger() error { - var err error - err = loadLoggerConfig() - return err -} - -func loadLoggerConfig() error { - usr, err := user.Current() - if err != nil { - return fmt.Errorf("failed to get the user info: [%v]", err) - } - // read logger configuration file - configPath := filepath.Join(usr.HomeDir, ".xce") - - var loggerConfig LoggerConfig - v := viper.New() - - v.SetDefault("logging.level", "info") // debug, info, warn, error, fatal - v.SetDefault("logging.encoding", "console") // console, json - v.SetDefault("logging.outputPath", defaultLoggerOutputFile) // xcecli_out.log - v.SetDefault("logging.file.maxSize", 100) // 100MB - v.SetDefault("logging.file.maxBackups", 10) // 10 - v.SetDefault("logging.file.maxAge", 14) // 14 days - v.SetDefault("logging.file.compress", true) // true - v.SetDefault("logging.encoderConfig.messageKey", "message") // message - v.SetDefault("logging.encoderConfig.levelKey", "level") // level - v.SetDefault("logging.encoderConfig.timeKey", "timestamp") // timestamp - v.SetDefault("logging.encoderConfig.nameKey", "logger") // logger - v.SetDefault("logging.encoderConfig.callerKey", "caller") // caller - v.SetDefault("logging.encoderConfig.functionKey", "func") // func - v.SetDefault("logging.encoderConfig.stacktraceKey", "stacktrace") // stacktrace - v.SetDefault("logging.encoderConfig.timeEncoder", "ISO8601") // ISO8601 - v.SetDefault("logging.encoderConfig.levelEncoder", "capital") // capital - v.SetConfigName(defaultLoggerConfigFile) - v.SetConfigType("yaml") - v.AddConfigPath(configPath) - // yamlContent := `# log level: debug, info, warn, error, fatal - // # debug: detailed log - // # info: normal log - // # warn: warning log - // # error: error log - // # fatal: fatal log` + v.GetString("logging.level") - - err = v.ReadInConfig() - if err != nil { - os.MkdirAll(configPath, 0755) - - if err = v.WriteConfigAs(filepath.Join(configPath, "logger.yaml")); err != nil { - fmt.Printf("failed to write default logger configuration file: [%v]", err) - } - } - err = v.Unmarshal(&loggerConfig) - if err != nil { - return fmt.Errorf("unable to decode into struct: [%v]", err) - } - // Handle both formats for OutputPath - if !filepath.IsAbs(loggerConfig.Logging.OutputPath) { - if loggerConfig.Logging.OutputPath == "" { - loggerConfig.Logging.OutputPath = defaultLoggerOutputFile - } - // If OutputPath is relative, prepend the logs directory - loggerConfig.Logging.OutputPath = filepath.Join(configPath, "logs", loggerConfig.Logging.OutputPath) - } - logDir := filepath.Dir(loggerConfig.Logging.OutputPath) // Extract the directory - // Create the directory if it does not exist - if err = os.MkdirAll(logDir, 0755); err != nil { - return fmt.Errorf("failed to create client logs directory: %w", err) - } - Log, err = createLogger(loggerConfig) - if err != nil { - return fmt.Errorf("failed to create client logger: %w", err) - } - return nil -} - -func createLogger(loggerConfig LoggerConfig) (*zap.Logger, error) { - configLevel, err := zapcore.ParseLevel(loggerConfig.Logging.Level) - if err != nil { - return nil, err - } - - // create log file and set the permission - file, err := os.OpenFile(loggerConfig.Logging.OutputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - return nil, err - } - file.Close() - - // configure lumberjack for log rotation - logWriter := &lumberjack.Logger{ - Filename: loggerConfig.Logging.OutputPath, - MaxSize: loggerConfig.Logging.File.MaxSize, - MaxBackups: loggerConfig.Logging.File.MaxBackups, - MaxAge: loggerConfig.Logging.File.MaxAge, - Compress: loggerConfig.Logging.File.Compress, - } - - // Create cores for different log levels - core := zapcore.NewCore( - zapcore.NewConsoleEncoder(zapcore.EncoderConfig{ - MessageKey: loggerConfig.Logging.EncoderConfig.MessageKey, - LevelKey: loggerConfig.Logging.EncoderConfig.LevelKey, - TimeKey: loggerConfig.Logging.EncoderConfig.TimeKey, - NameKey: loggerConfig.Logging.EncoderConfig.NameKey, - CallerKey: loggerConfig.Logging.EncoderConfig.CallerKey, - StacktraceKey: "", // disable stacktrace - EncodeTime: zapcore.ISO8601TimeEncoder, - EncodeLevel: zapcore.CapitalLevelEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, - ConsoleSeparator: " ", - }), - zapcore.AddSync(logWriter), - configLevel, - ) - logger := zap.New(core, - zap.AddCaller(), // caller info - zap.AddStacktrace(zap.ErrorLevel), // show error stacktrace - zap.AddCallerSkip(1), // skip one layer of caller - ) - return logger, nil - -} - -func Info(msg string, fields ...zap.Field) { - Log.WithOptions(zap.WithCaller(false)).Info(msg, fields...) -} -func Error(msg string, fields ...zap.Field) { - Log.WithOptions(zap.WithCaller(false)).Error(msg, fields...) -} - -func Warn(msg string, fields ...zap.Field) { - Log.WithOptions(zap.WithCaller(false)).Warn(msg, fields...) -} - -func Debug(msg string, fields ...zap.Field) { - Log.WithOptions(zap.WithCaller(true)).Debug(msg, fields...) -} - -func Fatal(msg string, fields ...zap.Field) { - Log.WithOptions(zap.WithCaller(false)).Fatal(msg, fields...) -} +package config + +import ( + "fmt" + "os" + "os/user" + "path/filepath" + + "github.com/spf13/viper" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +var ( + Log *zap.Logger +) + +const ( + defaultLoggerConfigFile = "logger.yaml" + defaultLoggerOutputFile = "xcecli_out.log" +) + +type LoggerConfig struct { + Logging struct { + Level string `yaml:"level"` + Encoding string `yaml:"encoding"` + OutputPath string `yaml:"outputPath"` + File struct { + MaxSize int `yaml:"maxSize"` + MaxBackups int `yaml:"maxBackups"` + MaxAge int `yaml:"maxAge"` + Compress bool `yaml:"compress"` + } `yaml:"file"` + EncoderConfig struct { + MessageKey string `yaml:"messageKey"` + LevelKey string `yaml:"levelKey"` + TimeKey string `yaml:"timeKey"` + NameKey string `yaml:"nameKey"` + CallerKey string `yaml:"callerKey"` + FunctionKey string `yaml:"functionKey"` + StacktraceKey string `yaml:"stacktraceKey"` + EncodeTime string `yaml:"timeEncoder"` + EncodeLevel string `yaml:"levelEncoder"` + } `yaml:"encoderConfig"` + } `yaml:"logging"` +} + +func InitLogger() error { + var err error + err = loadLoggerConfig() + return err +} + +func loadLoggerConfig() error { + usr, err := user.Current() + if err != nil { + return fmt.Errorf("failed to get the user info: [%v]", err) + } + // read logger configuration file + configPath := filepath.Join(usr.HomeDir, ".xce") + + var loggerConfig LoggerConfig + v := viper.New() + + v.SetDefault("logging.level", "info") // debug, info, warn, error, fatal + v.SetDefault("logging.encoding", "console") // console, json + v.SetDefault("logging.outputPath", defaultLoggerOutputFile) // xcecli_out.log + v.SetDefault("logging.file.maxSize", 100) // 100MB + v.SetDefault("logging.file.maxBackups", 10) // 10 + v.SetDefault("logging.file.maxAge", 14) // 14 days + v.SetDefault("logging.file.compress", true) // true + v.SetDefault("logging.encoderConfig.messageKey", "message") // message + v.SetDefault("logging.encoderConfig.levelKey", "level") // level + v.SetDefault("logging.encoderConfig.timeKey", "timestamp") // timestamp + v.SetDefault("logging.encoderConfig.nameKey", "logger") // logger + v.SetDefault("logging.encoderConfig.callerKey", "caller") // caller + v.SetDefault("logging.encoderConfig.functionKey", "func") // func + v.SetDefault("logging.encoderConfig.stacktraceKey", "stacktrace") // stacktrace + v.SetDefault("logging.encoderConfig.timeEncoder", "ISO8601") // ISO8601 + v.SetDefault("logging.encoderConfig.levelEncoder", "capital") // capital + v.SetConfigName(defaultLoggerConfigFile) + v.SetConfigType("yaml") + v.AddConfigPath(configPath) + // yamlContent := `# log level: debug, info, warn, error, fatal + // # debug: detailed log + // # info: normal log + // # warn: warning log + // # error: error log + // # fatal: fatal log` + v.GetString("logging.level") + + err = v.ReadInConfig() + if err != nil { + os.MkdirAll(configPath, 0755) + + if err = v.WriteConfigAs(filepath.Join(configPath, "logger.yaml")); err != nil { + fmt.Printf("failed to write default logger configuration file: [%v]", err) + } + } + err = v.Unmarshal(&loggerConfig) + if err != nil { + return fmt.Errorf("unable to decode into struct: [%v]", err) + } + // Handle both formats for OutputPath + if !filepath.IsAbs(loggerConfig.Logging.OutputPath) { + if loggerConfig.Logging.OutputPath == "" { + loggerConfig.Logging.OutputPath = defaultLoggerOutputFile + } + // If OutputPath is relative, prepend the logs directory + loggerConfig.Logging.OutputPath = filepath.Join(configPath, "logs", loggerConfig.Logging.OutputPath) + } + logDir := filepath.Dir(loggerConfig.Logging.OutputPath) // Extract the directory + // Create the directory if it does not exist + if err = os.MkdirAll(logDir, 0755); err != nil { + return fmt.Errorf("failed to create client logs directory: %w", err) + } + Log, err = createLogger(loggerConfig) + if err != nil { + return fmt.Errorf("failed to create client logger: %w", err) + } + return nil +} + +func createLogger(loggerConfig LoggerConfig) (*zap.Logger, error) { + configLevel, err := zapcore.ParseLevel(loggerConfig.Logging.Level) + if err != nil { + return nil, err + } + + // create log file and set the permission + file, err := os.OpenFile(loggerConfig.Logging.OutputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return nil, err + } + file.Close() + + // configure lumberjack for log rotation + logWriter := &lumberjack.Logger{ + Filename: loggerConfig.Logging.OutputPath, + MaxSize: loggerConfig.Logging.File.MaxSize, + MaxBackups: loggerConfig.Logging.File.MaxBackups, + MaxAge: loggerConfig.Logging.File.MaxAge, + Compress: loggerConfig.Logging.File.Compress, + } + + // Create cores for different log levels + core := zapcore.NewCore( + zapcore.NewConsoleEncoder(zapcore.EncoderConfig{ + MessageKey: loggerConfig.Logging.EncoderConfig.MessageKey, + LevelKey: loggerConfig.Logging.EncoderConfig.LevelKey, + TimeKey: loggerConfig.Logging.EncoderConfig.TimeKey, + NameKey: loggerConfig.Logging.EncoderConfig.NameKey, + CallerKey: loggerConfig.Logging.EncoderConfig.CallerKey, + StacktraceKey: "", // disable stacktrace + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + ConsoleSeparator: " ", + }), + zapcore.AddSync(logWriter), + configLevel, + ) + logger := zap.New(core, + zap.AddCaller(), // caller info + zap.AddStacktrace(zap.ErrorLevel), // show error stacktrace + zap.AddCallerSkip(1), // skip one layer of caller + ) + return logger, nil + +} + +func Info(msg string, fields ...zap.Field) { + Log.WithOptions(zap.WithCaller(false)).Info(msg, fields...) +} +func Error(msg string, fields ...zap.Field) { + Log.WithOptions(zap.WithCaller(false)).Error(msg, fields...) +} + +func Warn(msg string, fields ...zap.Field) { + Log.WithOptions(zap.WithCaller(false)).Warn(msg, fields...) +} + +func Debug(msg string, fields ...zap.Field) { + Log.WithOptions(zap.WithCaller(true)).Debug(msg, fields...) +} + +func Fatal(msg string, fields ...zap.Field) { + Log.WithOptions(zap.WithCaller(false)).Fatal(msg, fields...) +} diff --git a/src/frontend/xcecli/config/schema.go b/src/frontend/xcecli/config/schema.go index 99c73456..31bca849 100755 --- a/src/frontend/xcecli/config/schema.go +++ b/src/frontend/xcecli/config/schema.go @@ -1,92 +1,92 @@ -package config - -import ( - "errors" - "fmt" - "os" - "path/filepath" - - "github.com/xeipuuv/gojsonschema" - "gopkg.in/yaml.v2" -) - -// validate the specified yaml file format whether is correct -// return the content of the yaml file if the format is correct -// return error if the format is incorrect -func ValidateYamlFormat(yamlFilePath string) ([]byte, error) { - // find the file path - yamlFilePath, err := filepath.Abs(yamlFilePath) - if err != nil { - return nil, err - } - // get scheme file path, suppose schema file is in the same directory as yaml file - yamlFileRootPath := filepath.Dir(yamlFilePath) - realFileName := filepath.Base(yamlFilePath) - preRealFileName := realFileName[:len(realFileName)-5] + "_schema.json" - schemaFilePath := filepath.Join(yamlFileRootPath, "schema", preRealFileName) - // checking if the schema file exists - if _, err := os.Stat(schemaFilePath); os.IsNotExist(err) { - err = fmt.Errorf("schema file %s does not exist ", schemaFilePath) - return nil, err - } - data, err := ValidateYAML(yamlFilePath, schemaFilePath) - if err != nil { - return nil, err - } - return data, nil -} - -// ValidateYAML validates the YAML content against the provided JSON schema -func ValidateYAML(yamlPath, schemaPath string) ([]byte, error) { - // schemaLoader is a absolute file path - schemaLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(schemaPath)) - // get yaml file content by reading the file - data, err := os.ReadFile(yamlPath) - if err != nil { - return nil, err - } - // get the yaml file into object - var raw interface{} - err = yaml.Unmarshal(data, &raw) - if err != nil { - return nil, err - } - // transform into map[string]interface{} - yamlContent := convertYAML(raw) - - // create documentLoader - documentLoader := gojsonschema.NewGoLoader(yamlContent) - result, err := gojsonschema.Validate(schemaLoader, documentLoader) - if err != nil { - return nil, err - } - - if !result.Valid() { - var errorMessages string - for _, desc := range result.Errors() { - errorMessages += fmt.Sprintf("- %s\n", desc) - } - err = errors.New(errorMessages) - return nil, err - } - - // return the yaml content - return data, nil -} - -// convertYAML recursively converts map[interface{}]interface{} to map[string]interface{} -func convertYAML(i interface{}) interface{} { - switch x := i.(type) { - case map[interface{}]interface{}: - m2 := make(map[string]interface{}) - for k, v := range x { - m2[fmt.Sprintf("%v", k)] = convertYAML(v) - } - return m2 - case []interface{}: - for idx, v := range x { - x[idx] = convertYAML(v) - } - } - return i -} +package config + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/xeipuuv/gojsonschema" + "gopkg.in/yaml.v2" +) + +// validate the specified yaml file format whether is correct +// return the content of the yaml file if the format is correct +// return error if the format is incorrect +func ValidateYamlFormat(yamlFilePath string) ([]byte, error) { + // find the file path + yamlFilePath, err := filepath.Abs(yamlFilePath) + if err != nil { + return nil, err + } + // get scheme file path, suppose schema file is in the same directory as yaml file + yamlFileRootPath := filepath.Dir(yamlFilePath) + realFileName := filepath.Base(yamlFilePath) + preRealFileName := realFileName[:len(realFileName)-5] + "_schema.json" + schemaFilePath := filepath.Join(yamlFileRootPath, "schema", preRealFileName) + // checking if the schema file exists + if _, err := os.Stat(schemaFilePath); os.IsNotExist(err) { + err = fmt.Errorf("schema file %s does not exist ", schemaFilePath) + return nil, err + } + data, err := ValidateYAML(yamlFilePath, schemaFilePath) + if err != nil { + return nil, err + } + return data, nil +} + +// ValidateYAML validates the YAML content against the provided JSON schema +func ValidateYAML(yamlPath, schemaPath string) ([]byte, error) { + // schemaLoader is a absolute file path + schemaLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(schemaPath)) + // get yaml file content by reading the file + data, err := os.ReadFile(yamlPath) + if err != nil { + return nil, err + } + // get the yaml file into object + var raw interface{} + err = yaml.Unmarshal(data, &raw) + if err != nil { + return nil, err + } + // transform into map[string]interface{} + yamlContent := convertYAML(raw) + + // create documentLoader + documentLoader := gojsonschema.NewGoLoader(yamlContent) + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + if err != nil { + return nil, err + } + + if !result.Valid() { + var errorMessages string + for _, desc := range result.Errors() { + errorMessages += fmt.Sprintf("- %s\n", desc) + } + err = errors.New(errorMessages) + return nil, err + } + + // return the yaml content + return data, nil +} + +// convertYAML recursively converts map[interface{}]interface{} to map[string]interface{} +func convertYAML(i interface{}) interface{} { + switch x := i.(type) { + case map[interface{}]interface{}: + m2 := make(map[string]interface{}) + for k, v := range x { + m2[fmt.Sprintf("%v", k)] = convertYAML(v) + } + return m2 + case []interface{}: + for idx, v := range x { + x[idx] = convertYAML(v) + } + } + return i +} diff --git a/src/frontend/xcecli/models/cluster.go b/src/frontend/xcecli/models/cluster.go index 5fb69dc2..5d808290 100755 --- a/src/frontend/xcecli/models/cluster.go +++ b/src/frontend/xcecli/models/cluster.go @@ -1,22 +1,22 @@ -package models - -type ClusterResponse struct { - Code int `json:"code"` - Msg string `json:"msg"` - Data struct { - Count int `json:"count"` - Rows []ClusterInfo `json:"rows"` - Page_token string `json:"page_token"` - } `json:"data"` -} - -type ClusterInfo struct { - ClusterName string `json:"cluster_name"` - Version string `json:"version"` - Status string `json:"status"` - ControllerName string `json:"controller_name"` - Resources interface{} `json:"resources"` - HostTypes interface{} `json:"host_types"` - HostModels interface{} `json:"host_models"` - ClusterAdmins interface{} `json:"cluster_admins"` -} +package models + +type ClusterResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data struct { + Count int `json:"count"` + Rows []ClusterInfo `json:"rows"` + Page_token string `json:"page_token"` + } `json:"data"` +} + +type ClusterInfo struct { + ClusterName string `json:"cluster_name"` + Version string `json:"version"` + Status string `json:"status"` + ControllerName string `json:"controller_name"` + Resources interface{} `json:"resources"` + HostTypes interface{} `json:"host_types"` + HostModels interface{} `json:"host_models"` + ClusterAdmins interface{} `json:"cluster_admins"` +} diff --git a/src/frontend/xcecli/models/hosts.go b/src/frontend/xcecli/models/hosts.go index 8c1842ec..09e41fb6 100755 --- a/src/frontend/xcecli/models/hosts.go +++ b/src/frontend/xcecli/models/hosts.go @@ -1,172 +1,172 @@ -package models - -const ( - HEADER_HOST_NAME = "HOST_NAME" - HEADER_HOST_STATUS = "STATUS" - HEADER_HOST_NCPUS = "NCPUS" - HEADER_HOST_MAX_SLOT = "MAX_SLOT" - HEADER_HOST_NUM_RUN = "NUM_RUN" - HEADER_HOST_NUM_SSUSP = "NUM_SSUSP" - HEADER_HOST_NUM_USUSP = "NUM_USUSP" - HEADER_HOST_HOST_TYPE = "HOST_TYPE" - // host details info - HEADER_HOST_CPUF = "CPUF" - HEADER_HOST_JLU = "JL/U" - HEADER_HOST_MAX = "MAX" - HEADER_HOST_NJOBS = "NJOBS" - HEADER_HOST_RUN = "RUN" - HEADER_HOST_SSUSP = "SSUSP" - HEADER_HOST_USUSP = "USUSP" - HEADER_HOST_RSV = "RSV" - HEADER_HOST_DISPT_WINDOW = "DISPATCH_WINDOW" - // second line - HEADER_HOST_R15S = "r15s" - HEADER_HOST_R1M = "r1m" - HEADER_HOST_R15M = "r15m" - HEADER_HOST_UT = "ut" - HEADER_HOST_PG = "pg" - HEADER_HOST_IO = "io" - HEADER_HOST_LS = "ls" - HEADER_HOST_IT = "it" - HEADER_HOST_TMP = "tmp" - HEADER_HOST_SWP = "swp" - HEADER_HOST_MEM = "mem" - HEADER_HOST_SLOTS = "slots" -) - -var HostDetailHeaders = []string{ - HEADER_HOST_STATUS, - HEADER_HOST_CPUF, - HEADER_HOST_JLU, - HEADER_HOST_MAX, - HEADER_HOST_NJOBS, - HEADER_HOST_RUN, - HEADER_HOST_SSUSP, - HEADER_HOST_USUSP, - HEADER_HOST_RSV, - HEADER_HOST_DISPT_WINDOW, -} - -var HostLoadSchedulingHeaders = []string{ - HEADER_HOST_R15S, - HEADER_HOST_R1M, - HEADER_HOST_R15M, - HEADER_HOST_UT, - HEADER_HOST_PG, - HEADER_HOST_IO, - HEADER_HOST_LS, - HEADER_HOST_IT, - HEADER_HOST_TMP, - HEADER_HOST_SWP, - HEADER_HOST_MEM, - HEADER_HOST_SLOTS, -} - -// HostsResponse define the data structure in the host query response -type HostsResponse struct { - Code int `json:"code"` - Msg string `json:"msg"` - Data struct { - Count int `json:"count"` - Rows []XCEHost `json:"rows"` - Page_token string `json:"page_token"` - } `json:"data"` -} - -// Host define the data structure in the host info\ -type XCEHost struct { - HostID int64 `json:"host_id"` - HostName string `json:"host_name"` - HostStatus string `json:"host_status"` - HostModel string `json:"host_model"` - HostType string `json:"host_type"` - HostGroup string `json:"host_group"` - Flag int `json:"flag"` - CPUFactor float64 `json:"cpu_factor"` - DisptWindow interface{} `json:"dispt_window"` - NumCPUs int `json:"num_cpus"` - MaxSlot int `json:"max_slot"` - MaxMem int `json:"max_mem"` - MaxSwap int `json:"max_swap"` - MaxTmp int `json:"max_tmp"` - NumDisk int `json:"num_disk"` - NumRun int `json:"num_run"` - NumSSusp int `json:"num_ssusp"` - NumUSusp int `json:"num_ususp"` - NumDone int `json:"num_done"` - AffTopo interface{} `json:"aff_topo"` // JSONB - GPUTopo interface{} `json:"gpu_topo"` // JSONB - SchedStop SchedStop `json:"sched_stop"` // JSONB - RealLoad interface{} `json:"real_load"` - - // ResourceValues carries this host's live per-resource wire values - // (r15s/r1m/r15m/ut/pg/io/ls/it/tmp/swp/mem/slots, among others), the - // same set the C scheduler's own "info worker" REPL command shows -- - // populated by the apiserver from the worker's own resval pushes. - ResourceValues []ResourceDistributionInfo `json:"resource_values"` -} - -// ResourceDistributionInfo mirrors apiserver/models.ResourceDistributionInfo's -// JSON shape -- one resource's current value for one host/worker. -type ResourceDistributionInfo struct { - ResourceName string `json:"res_name"` - IsDynamic bool `json:"is_dynamic"` - ValueType int `json:"value_type"` - BooleanValue bool `json:"boolean_value"` - StringValue string `json:"string_value"` - NumericValue float64 `json:"numeric_value"` - Location string `json:"location"` -} - -var HostFieldsMap = map[string]string{ - HEADER_HOST_NAME: "host_name", - HEADER_HOST_STATUS: "host_status", - HEADER_HOST_NCPUS: "num_cores", - HEADER_HOST_MAX_SLOT: "max_slot", - HEADER_HOST_NUM_RUN: "num_run", - HEADER_HOST_NUM_SSUSP: "num_ssusp", - HEADER_HOST_NUM_USUSP: "num_ususp", - HEADER_HOST_HOST_TYPE: "host_type", -} - -type SchedStop struct { - Ls string `json:"ls"` // Load Sharing - Pg string `json:"pg"` // Page - R1m string `json:"r1m"` // Recent 1 minute - Tmp string `json:"tmp"` // Temporary -} - -type HostControlRequest struct { - ControlAction string `json:"action" binding:"required"` //control action - Hostname []string `json:"host_name" binding:"required"` //host name - Comment string `json:"comment"` //comment -} - -type HostControlResponse struct { - Data struct { - Accepted []*HostControlResponseItem `json:"accepted"` - Failed []*HostControlResponseItem `json:"failed"` - } `json:"data"` -} - -type HostControlResponseItem struct { - HostName string `json:"host_name"` - Msg string `json:"msg"` -} - -func (h *XCEHost) UnmarshalJSON(data []byte) error { - type Alias XCEHost - var a Alias - if err := UnmarshalOrderMapRow(data, &a); err != nil { - return err - } - *h = XCEHost(a) - return nil -} - -type ResReqSelectRequest struct { - Select string `json:"select"` - Order string `json:"order,omitempty"` - Limit int `json:"limit,omitempty"` - PageToken string `json:"page_token,omitempty"` -} +package models + +const ( + HEADER_HOST_NAME = "HOST_NAME" + HEADER_HOST_STATUS = "STATUS" + HEADER_HOST_NCPUS = "NCPUS" + HEADER_HOST_MAX_SLOT = "MAX_SLOT" + HEADER_HOST_NUM_RUN = "NUM_RUN" + HEADER_HOST_NUM_SSUSP = "NUM_SSUSP" + HEADER_HOST_NUM_USUSP = "NUM_USUSP" + HEADER_HOST_HOST_TYPE = "HOST_TYPE" + // host details info + HEADER_HOST_CPUF = "CPUF" + HEADER_HOST_JLU = "JL/U" + HEADER_HOST_MAX = "MAX" + HEADER_HOST_NJOBS = "NJOBS" + HEADER_HOST_RUN = "RUN" + HEADER_HOST_SSUSP = "SSUSP" + HEADER_HOST_USUSP = "USUSP" + HEADER_HOST_RSV = "RSV" + HEADER_HOST_DISPT_WINDOW = "DISPATCH_WINDOW" + // second line + HEADER_HOST_R15S = "r15s" + HEADER_HOST_R1M = "r1m" + HEADER_HOST_R15M = "r15m" + HEADER_HOST_UT = "ut" + HEADER_HOST_PG = "pg" + HEADER_HOST_IO = "io" + HEADER_HOST_LS = "ls" + HEADER_HOST_IT = "it" + HEADER_HOST_TMP = "tmp" + HEADER_HOST_SWP = "swp" + HEADER_HOST_MEM = "mem" + HEADER_HOST_SLOTS = "slots" +) + +var HostDetailHeaders = []string{ + HEADER_HOST_STATUS, + HEADER_HOST_CPUF, + HEADER_HOST_JLU, + HEADER_HOST_MAX, + HEADER_HOST_NJOBS, + HEADER_HOST_RUN, + HEADER_HOST_SSUSP, + HEADER_HOST_USUSP, + HEADER_HOST_RSV, + HEADER_HOST_DISPT_WINDOW, +} + +var HostLoadSchedulingHeaders = []string{ + HEADER_HOST_R15S, + HEADER_HOST_R1M, + HEADER_HOST_R15M, + HEADER_HOST_UT, + HEADER_HOST_PG, + HEADER_HOST_IO, + HEADER_HOST_LS, + HEADER_HOST_IT, + HEADER_HOST_TMP, + HEADER_HOST_SWP, + HEADER_HOST_MEM, + HEADER_HOST_SLOTS, +} + +// HostsResponse define the data structure in the host query response +type HostsResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data struct { + Count int `json:"count"` + Rows []XCEHost `json:"rows"` + Page_token string `json:"page_token"` + } `json:"data"` +} + +// Host define the data structure in the host info\ +type XCEHost struct { + HostID int64 `json:"host_id"` + HostName string `json:"host_name"` + HostStatus string `json:"host_status"` + HostModel string `json:"host_model"` + HostType string `json:"host_type"` + HostGroup string `json:"host_group"` + Flag int `json:"flag"` + CPUFactor float64 `json:"cpu_factor"` + DisptWindow interface{} `json:"dispt_window"` + NumCPUs int `json:"num_cpus"` + MaxSlot int `json:"max_slot"` + MaxMem int `json:"max_mem"` + MaxSwap int `json:"max_swap"` + MaxTmp int `json:"max_tmp"` + NumDisk int `json:"num_disk"` + NumRun int `json:"num_run"` + NumSSusp int `json:"num_ssusp"` + NumUSusp int `json:"num_ususp"` + NumDone int `json:"num_done"` + AffTopo interface{} `json:"aff_topo"` // JSONB + GPUTopo interface{} `json:"gpu_topo"` // JSONB + SchedStop SchedStop `json:"sched_stop"` // JSONB + RealLoad interface{} `json:"real_load"` + + // ResourceValues carries this host's live per-resource wire values + // (r15s/r1m/r15m/ut/pg/io/ls/it/tmp/swp/mem/slots, among others), the + // same set the C scheduler's own "info worker" REPL command shows -- + // populated by the apiserver from the worker's own resval pushes. + ResourceValues []ResourceDistributionInfo `json:"resource_values"` +} + +// ResourceDistributionInfo mirrors apiserver/models.ResourceDistributionInfo's +// JSON shape -- one resource's current value for one host/worker. +type ResourceDistributionInfo struct { + ResourceName string `json:"res_name"` + IsDynamic bool `json:"is_dynamic"` + ValueType int `json:"value_type"` + BooleanValue bool `json:"boolean_value"` + StringValue string `json:"string_value"` + NumericValue float64 `json:"numeric_value"` + Location string `json:"location"` +} + +var HostFieldsMap = map[string]string{ + HEADER_HOST_NAME: "host_name", + HEADER_HOST_STATUS: "host_status", + HEADER_HOST_NCPUS: "num_cores", + HEADER_HOST_MAX_SLOT: "max_slot", + HEADER_HOST_NUM_RUN: "num_run", + HEADER_HOST_NUM_SSUSP: "num_ssusp", + HEADER_HOST_NUM_USUSP: "num_ususp", + HEADER_HOST_HOST_TYPE: "host_type", +} + +type SchedStop struct { + Ls string `json:"ls"` // Load Sharing + Pg string `json:"pg"` // Page + R1m string `json:"r1m"` // Recent 1 minute + Tmp string `json:"tmp"` // Temporary +} + +type HostControlRequest struct { + ControlAction string `json:"action" binding:"required"` //control action + Hostname []string `json:"host_name" binding:"required"` //host name + Comment string `json:"comment"` //comment +} + +type HostControlResponse struct { + Data struct { + Accepted []*HostControlResponseItem `json:"accepted"` + Failed []*HostControlResponseItem `json:"failed"` + } `json:"data"` +} + +type HostControlResponseItem struct { + HostName string `json:"host_name"` + Msg string `json:"msg"` +} + +func (h *XCEHost) UnmarshalJSON(data []byte) error { + type Alias XCEHost + var a Alias + if err := UnmarshalOrderMapRow(data, &a); err != nil { + return err + } + *h = XCEHost(a) + return nil +} + +type ResReqSelectRequest struct { + Select string `json:"select"` + Order string `json:"order,omitempty"` + Limit int `json:"limit,omitempty"` + PageToken string `json:"page_token,omitempty"` +} diff --git a/src/frontend/xcecli/models/jobs.go b/src/frontend/xcecli/models/jobs.go index 5bf0e34e..e95d895c 100755 --- a/src/frontend/xcecli/models/jobs.go +++ b/src/frontend/xcecli/models/jobs.go @@ -60,8 +60,6 @@ var JobLoadSchedulingHeaders = []string{ HEADER_JOB_MEM, } - - // for array jobs const ( HEADER_ARRAY_JOB_JOBID = "JOBID" @@ -93,96 +91,96 @@ type Jsonb struct { // Job define the data structure in the job info type QueryJob struct { - JobID int64 `json:"job_id"` - SubmitUser string `json:"submit_user"` - JobStatus string `json:"job_status"` - JobName string `json:"job_name"` - Command string `json:"command"` - QueueName string `json:"queue_name"` - ProjectName string `json:"project_name"` - UserGroup string `json:"user_group"` - LicenseProject string `json:"license_project"` - JobDescription string `json:"job_description"` - Resreq string `json:"resreq"` - RsvID string `json:"rsv_id"` - SLA string `json:"sla"` - MaxNumProcessors int `json:"maxnum_processors"` - NumProcessors int `json:"num_processors"` - AskedHosts string `json:"asked_hosts"` - BeginTime string `json:"begin_time"` - TermTime string `json:"term_time"` - UserPriority int `json:"user_priority"` - DependCond string `json:"depend_cond"` - SubmitTime int64 `json:"submit_time"` - ReserveTime int64 `json:"reserve_time"` - StartTime int64 `json:"start_time"` - EndTime int64 `json:"end_time"` - JobRusage string `json:"job_rusage"` - MaxMem float64 `json:"max_mem"` - AvgMem float64 `json:"avg_mem"` - UsedMem float64 `json:"used_mem"` - CPUTime int `json:"cpu_time"` - PeakCPUTime int `json:"peak_cpu_time"` - PeakCPUDuration float64 `json:"peak_cpu_duration"` - MemEfficiency float64 `json:"mem_efficiency"` // Memory efficiency in percentage - AvgCPUEfficiency float64 `json:"avg_cpu_efficiency"` // Average CPU efficiency in percentage - PeakCPUEfficiency float64 `json:"peak_cpu_efficiency"` // Peak CPU efficiency in percentage - NumHosts int `json:"num_hosts"` // Number of hosts used by the job - FirstHost string `json:"first_host"` // First host where the job ran - AllocSlot string `json:"alloc_slots"` // Allocated slot information - Duration int `json:"duration"` - Umask int `json:"umask"` - CWD string `json:"cwd"` - SubmitHomeDir string `json:"submit_home_dir"` - FromHost string `json:"from_host"` - ExecHosts ExecHosts `json:"exec_hosts"` + JobID int64 `json:"job_id"` + SubmitUser string `json:"submit_user"` + JobStatus string `json:"job_status"` + JobName string `json:"job_name"` + Command string `json:"command"` + QueueName string `json:"queue_name"` + ProjectName string `json:"project_name"` + UserGroup string `json:"user_group"` + LicenseProject string `json:"license_project"` + JobDescription string `json:"job_description"` + Resreq string `json:"resreq"` + RsvID string `json:"rsv_id"` + SLA string `json:"sla"` + MaxNumProcessors int `json:"maxnum_processors"` + NumProcessors int `json:"num_processors"` + AskedHosts string `json:"asked_hosts"` + BeginTime string `json:"begin_time"` + TermTime string `json:"term_time"` + UserPriority int `json:"user_priority"` + DependCond string `json:"depend_cond"` + SubmitTime int64 `json:"submit_time"` + ReserveTime int64 `json:"reserve_time"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` + JobRusage string `json:"job_rusage"` + MaxMem float64 `json:"max_mem"` + AvgMem float64 `json:"avg_mem"` + UsedMem float64 `json:"used_mem"` + CPUTime int `json:"cpu_time"` + PeakCPUTime int `json:"peak_cpu_time"` + PeakCPUDuration float64 `json:"peak_cpu_duration"` + MemEfficiency float64 `json:"mem_efficiency"` // Memory efficiency in percentage + AvgCPUEfficiency float64 `json:"avg_cpu_efficiency"` // Average CPU efficiency in percentage + PeakCPUEfficiency float64 `json:"peak_cpu_efficiency"` // Peak CPU efficiency in percentage + NumHosts int `json:"num_hosts"` // Number of hosts used by the job + FirstHost string `json:"first_host"` // First host where the job ran + AllocSlot string `json:"alloc_slots"` // Allocated slot information + Duration int `json:"duration"` + Umask int `json:"umask"` + CWD string `json:"cwd"` + SubmitHomeDir string `json:"submit_home_dir"` + FromHost string `json:"from_host"` + ExecHosts ExecHosts `json:"exec_hosts"` ExitStatus ExitStatusValue `json:"exit_status"` - ExecUID int `json:"exec_uid"` - ExecHome string `json:"exec_home"` - ExecCWD string `json:"exec_cwd"` - ExecUserName string `json:"exec_user_name"` - JobPriority int `json:"job_priority"` - ChargedSAAP string `json:"charged_SAAP"` - PreExecCmd string `json:"pre_exec_cmd"` - PostExecCmd string `json:"post_exec_cmd"` - RequeueErrorValues string `json:"requeue_error_values"` - NumLicenses int `json:"num_licenses"` - LicenseNames []string `json:"license_names"` - RunTime int `json:"run_time"` - RunTimeEstimation int `json:"run_time_estimation"` - MigThreshold int `json:"mig_threshold"` - ChkpntPeriod int `json:"chkpnt_period"` - ChkpntDir string `json:"chkpnt_dir"` - OutDir string `json:"out_dir"` - InFile string `json:"in_file"` - OutFile string `json:"out_file"` - ErrFile string `json:"err_file"` - MailUser string `json:"mail_user"` - LoginShell string `json:"login_shell"` - ExceptList string `json:"except_list"` - RLimits64 []int64 `json:"rlimits64"` - SubEnvVars string `json:"sub_env_vars"` - PendingReason string `json:"pending_reason"` - JobStartInfo string `json:"job_start_info"` - JobExecInfo string `json:"job_exec_info"` - CoreLimit float64 `json:"core_limit"` - CPULimit string `json:"cpu_limit"` - RunLimit string `json:"run_limit"` - FileLimit float64 `json:"file_limit"` - DataLimit float64 `json:"data_limit"` - StackLimit float64 `json:"stack_limit"` - MemLimit float64 `json:"mem_limit"` - SwapLimit float64 `json:"swap_limit"` - ProcessLimit float64 `json:"process_limit"` - ThreadLimit float64 `json:"thread_limit"` - NumFiles int `json:"num_files"` - ArrayIdx int `json:"array_idx"` // - UniqueJobID int64 `json:"unique_job_id"` // - ExtendData JobExtendData `json:"extend_data"` - InteractiveJobURL string `json:"interactive_job_url"` - PGIDs IntSlice `json:"pgids"` - PID IntSlice `json:"pids"` - LaunchType string `json:"launch_type"` + ExecUID int `json:"exec_uid"` + ExecHome string `json:"exec_home"` + ExecCWD string `json:"exec_cwd"` + ExecUserName string `json:"exec_user_name"` + JobPriority int `json:"job_priority"` + ChargedSAAP string `json:"charged_SAAP"` + PreExecCmd string `json:"pre_exec_cmd"` + PostExecCmd string `json:"post_exec_cmd"` + RequeueErrorValues string `json:"requeue_error_values"` + NumLicenses int `json:"num_licenses"` + LicenseNames []string `json:"license_names"` + RunTime int `json:"run_time"` + RunTimeEstimation int `json:"run_time_estimation"` + MigThreshold int `json:"mig_threshold"` + ChkpntPeriod int `json:"chkpnt_period"` + ChkpntDir string `json:"chkpnt_dir"` + OutDir string `json:"out_dir"` + InFile string `json:"in_file"` + OutFile string `json:"out_file"` + ErrFile string `json:"err_file"` + MailUser string `json:"mail_user"` + LoginShell string `json:"login_shell"` + ExceptList string `json:"except_list"` + RLimits64 []int64 `json:"rlimits64"` + SubEnvVars string `json:"sub_env_vars"` + PendingReason string `json:"pending_reason"` + JobStartInfo string `json:"job_start_info"` + JobExecInfo string `json:"job_exec_info"` + CoreLimit float64 `json:"core_limit"` + CPULimit string `json:"cpu_limit"` + RunLimit string `json:"run_limit"` + FileLimit float64 `json:"file_limit"` + DataLimit float64 `json:"data_limit"` + StackLimit float64 `json:"stack_limit"` + MemLimit float64 `json:"mem_limit"` + SwapLimit float64 `json:"swap_limit"` + ProcessLimit float64 `json:"process_limit"` + ThreadLimit float64 `json:"thread_limit"` + NumFiles int `json:"num_files"` + ArrayIdx int `json:"array_idx"` // + UniqueJobID int64 `json:"unique_job_id"` // + ExtendData JobExtendData `json:"extend_data"` + InteractiveJobURL string `json:"interactive_job_url"` + PGIDs IntSlice `json:"pgids"` + PID IntSlice `json:"pids"` + LaunchType string `json:"launch_type"` } var JobFieldsMap = map[string]string{ diff --git a/src/frontend/xcecli/models/queues.go b/src/frontend/xcecli/models/queues.go index 4d792b5d..3f585764 100755 --- a/src/frontend/xcecli/models/queues.go +++ b/src/frontend/xcecli/models/queues.go @@ -1,124 +1,124 @@ -package models - -const ( - HEADER_QUEUE_NAME = "QUEUE_NAME" - HEADER_QUEUE_PRIORITY = "PRIORITY" - HEADER_QUEUE_STATUS = "QUEUE_STATUS" - HEADER_QUEUE_NUM_PEND = "NUM_PEND" - HEADER_QUEUE_NUM_RUN = "NUM_RUN" - HEADER_QUEUE_NUM_SSUSP = "NUM_SSUSP" - HEADER_QUEUE_NUM_USUSP = "NUM_USUSP" - HEADER_QUEUE_NUM_DONE = "NUM_DONE" - // queue detail info - HEADER_QUEUE_PRIO = "PRIO" - HEADER_QUEUE_NICE = "NICE" - HEADER_QUEUE_MAX = "MAX" - HEADER_QUEUE_JLU = "JL/U" - HEADER_QUEUE_JLP = "JL/P" - HEADER_QUEUE_JLH = "JL/H" - HEADER_QUEUE_NJOBS = "NJOBS" - HEADER_QUEUE_PEND = "PEND" - HEADER_QUEUE_RUN = "RUN" - HEADER_QUEUE_SSUSP = "SSUSP" - HEADER_QUEUE_USUSP = "USUSP" - HEADER_QUEUE_RSV = "RSV" - // second line - HEADER_QUEUE_R15S = "r15s" - HEADER_QUEUE_R1M = "r1m" - HEADER_QUEUE_R15M = "r15m" - HEADER_QUEUE_UT = "ut" - HEADER_QUEUE_PG = "pg" - HEADER_QUEUE_IO = "io" - HEADER_QUEUE_LS = "ls" - HEADER_QUEUE_IT = "it" - HEADER_QUEUE_TMP = "tmp" - HEADER_QUEUE_SWP = "swp" - HEADER_QUEUE_MEM = "mem" -) - -var QueueDetailHeaders = []string{ - HEADER_QUEUE_PRIO, - HEADER_QUEUE_NICE, - HEADER_QUEUE_STATUS, - HEADER_QUEUE_MAX, - HEADER_QUEUE_JLU, - HEADER_QUEUE_JLP, - HEADER_QUEUE_JLH, - HEADER_QUEUE_NJOBS, - HEADER_QUEUE_PEND, - HEADER_QUEUE_RUN, - HEADER_QUEUE_SSUSP, - HEADER_QUEUE_USUSP, - HEADER_QUEUE_RSV, -} - -var QueueLoadSchedulingHeaders = []string{ - HEADER_QUEUE_R15S, - HEADER_QUEUE_R1M, - HEADER_QUEUE_R15M, - HEADER_QUEUE_UT, - HEADER_QUEUE_PG, - HEADER_QUEUE_IO, - HEADER_QUEUE_LS, - HEADER_QUEUE_IT, - HEADER_QUEUE_TMP, - HEADER_QUEUE_SWP, - HEADER_QUEUE_MEM, -} - -// QueuesResponse define the data structure in the queue query response -type QueuesResponse struct { - Code int `json:"code"` - Msg string `json:"msg"` - Data struct { - Count int `json:"count"` - Rows []DBQueueInfo `json:"rows"` - Page_token string `json:"page_token"` - } `json:"data"` -} - -var QueueFieldsMap = map[string]string{ - HEADER_QUEUE_NAME: "queue_name", - HEADER_QUEUE_PRIORITY: "priority", - HEADER_QUEUE_STATUS: "queue_status", - HEADER_QUEUE_NUM_PEND: "num_pend", - HEADER_QUEUE_NUM_RUN: "num_run", - HEADER_QUEUE_NUM_SSUSP: "num_ssusp", - HEADER_QUEUE_NUM_USUSP: "num_ususp", - HEADER_QUEUE_NUM_DONE: "num_done", -} - -// DBQueueInfo define the data structure in the queue info -type DBQueueInfo struct { - QueueName string `json:"queue_name"` - QueueStatus string `json:"queue_status"` - Priority int `json:"priority"` - Description string `json:"description"` - NumPend int `json:"num_pend"` - NumRun int `json:"num_run"` - NumSSusp int `json:"num_ssusp"` - NumUSusp int `json:"num_ususp"` - NumDone int `json:"num_done"` - FSInfo map[string]interface{} `json:"fs_info"` // JSONB -} - -type QueueControlRequest struct { - ControlAction string `json:"action" binding:"required"` //control action - QueueName []string `json:"queue_name" binding:"required"` //queue name - Comment string `json:"comment"` //comment -} - -type QueueControlResponse struct { - Data struct { - Accepted []*QueueControlResponseItem `json:"accepted"` - Failed []*QueueControlResponseItem `json:"failed"` - } `json:"data"` -} - -type QueueControlResponseItem struct { - QueueName string `json:"queue_name"` - Msg string `json:"msg"` -} +package models + +const ( + HEADER_QUEUE_NAME = "QUEUE_NAME" + HEADER_QUEUE_PRIORITY = "PRIORITY" + HEADER_QUEUE_STATUS = "QUEUE_STATUS" + HEADER_QUEUE_NUM_PEND = "NUM_PEND" + HEADER_QUEUE_NUM_RUN = "NUM_RUN" + HEADER_QUEUE_NUM_SSUSP = "NUM_SSUSP" + HEADER_QUEUE_NUM_USUSP = "NUM_USUSP" + HEADER_QUEUE_NUM_DONE = "NUM_DONE" + // queue detail info + HEADER_QUEUE_PRIO = "PRIO" + HEADER_QUEUE_NICE = "NICE" + HEADER_QUEUE_MAX = "MAX" + HEADER_QUEUE_JLU = "JL/U" + HEADER_QUEUE_JLP = "JL/P" + HEADER_QUEUE_JLH = "JL/H" + HEADER_QUEUE_NJOBS = "NJOBS" + HEADER_QUEUE_PEND = "PEND" + HEADER_QUEUE_RUN = "RUN" + HEADER_QUEUE_SSUSP = "SSUSP" + HEADER_QUEUE_USUSP = "USUSP" + HEADER_QUEUE_RSV = "RSV" + // second line + HEADER_QUEUE_R15S = "r15s" + HEADER_QUEUE_R1M = "r1m" + HEADER_QUEUE_R15M = "r15m" + HEADER_QUEUE_UT = "ut" + HEADER_QUEUE_PG = "pg" + HEADER_QUEUE_IO = "io" + HEADER_QUEUE_LS = "ls" + HEADER_QUEUE_IT = "it" + HEADER_QUEUE_TMP = "tmp" + HEADER_QUEUE_SWP = "swp" + HEADER_QUEUE_MEM = "mem" +) + +var QueueDetailHeaders = []string{ + HEADER_QUEUE_PRIO, + HEADER_QUEUE_NICE, + HEADER_QUEUE_STATUS, + HEADER_QUEUE_MAX, + HEADER_QUEUE_JLU, + HEADER_QUEUE_JLP, + HEADER_QUEUE_JLH, + HEADER_QUEUE_NJOBS, + HEADER_QUEUE_PEND, + HEADER_QUEUE_RUN, + HEADER_QUEUE_SSUSP, + HEADER_QUEUE_USUSP, + HEADER_QUEUE_RSV, +} + +var QueueLoadSchedulingHeaders = []string{ + HEADER_QUEUE_R15S, + HEADER_QUEUE_R1M, + HEADER_QUEUE_R15M, + HEADER_QUEUE_UT, + HEADER_QUEUE_PG, + HEADER_QUEUE_IO, + HEADER_QUEUE_LS, + HEADER_QUEUE_IT, + HEADER_QUEUE_TMP, + HEADER_QUEUE_SWP, + HEADER_QUEUE_MEM, +} + +// QueuesResponse define the data structure in the queue query response +type QueuesResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data struct { + Count int `json:"count"` + Rows []DBQueueInfo `json:"rows"` + Page_token string `json:"page_token"` + } `json:"data"` +} + +var QueueFieldsMap = map[string]string{ + HEADER_QUEUE_NAME: "queue_name", + HEADER_QUEUE_PRIORITY: "priority", + HEADER_QUEUE_STATUS: "queue_status", + HEADER_QUEUE_NUM_PEND: "num_pend", + HEADER_QUEUE_NUM_RUN: "num_run", + HEADER_QUEUE_NUM_SSUSP: "num_ssusp", + HEADER_QUEUE_NUM_USUSP: "num_ususp", + HEADER_QUEUE_NUM_DONE: "num_done", +} + +// DBQueueInfo define the data structure in the queue info +type DBQueueInfo struct { + QueueName string `json:"queue_name"` + QueueStatus string `json:"queue_status"` + Priority int `json:"priority"` + Description string `json:"description"` + NumPend int `json:"num_pend"` + NumRun int `json:"num_run"` + NumSSusp int `json:"num_ssusp"` + NumUSusp int `json:"num_ususp"` + NumDone int `json:"num_done"` + FSInfo map[string]interface{} `json:"fs_info"` // JSONB +} + +type QueueControlRequest struct { + ControlAction string `json:"action" binding:"required"` //control action + QueueName []string `json:"queue_name" binding:"required"` //queue name + Comment string `json:"comment"` //comment +} + +type QueueControlResponse struct { + Data struct { + Accepted []*QueueControlResponseItem `json:"accepted"` + Failed []*QueueControlResponseItem `json:"failed"` + } `json:"data"` +} + +type QueueControlResponseItem struct { + QueueName string `json:"queue_name"` + Msg string `json:"msg"` +} func (q *DBQueueInfo) UnmarshalJSON(data []byte) error { type Alias DBQueueInfo diff --git a/src/frontend/xcecli/router/router.go b/src/frontend/xcecli/router/router.go index a333555e..e97a7fd3 100755 --- a/src/frontend/xcecli/router/router.go +++ b/src/frontend/xcecli/router/router.go @@ -1,117 +1,117 @@ -package cmd - -import ( - "fmt" - apiserver "xcecli/cmd/auth" - "xcecli/cmd/clusters" - "xcecli/cmd/hosts" - "xcecli/cmd/jobs" - "xcecli/cmd/permissions" - "xcecli/cmd/queues" - "xcecli/cmd/resources" - "xcecli/cmd/roles" - "xcecli/cmd/users" - "xcecli/common/request" - "xcecli/config" - - "github.com/spf13/cobra" -) - -var ( - rootCmd = &cobra.Command{ - Use: "xce", - Short: "XCE CLI tool for the API Server operations", - } - configManager *config.ConfigManager -) - -// execute the root command -func Execute(cm *config.ConfigManager, version string) error { - if cm == nil { - return fmt.Errorf("the config manager is not initialized") - } - configManager = cm - rootCmd.Version = version - rootCmd.CompletionOptions.DisableDefaultCmd = true - rootCmd.SilenceErrors = true - rootCmd.SilenceUsage = true - - // initialize the subcommands - initCommands() - - return rootCmd.Execute() -} - -// withAuth wraps a command with authentication check -// This function creates a middleware that checks authentication before executing any command -func withAuth(cmd *cobra.Command) *cobra.Command { - // Store original handlers - originalRunE := cmd.RunE - originalRun := cmd.Run - - // Create new RunE that checks authentication first - cmd.RunE = func(cmd *cobra.Command, args []string) error { - // Check authentication before executing the command - _, err := configManager.IsAuthenticated() - if err != nil { - return err - } - - // Execute the original command logic - if originalRunE != nil { - return originalRunE(cmd, args) - } - if originalRun != nil { - originalRun(cmd, args) - return nil - } - return nil - } - - return cmd -} - -// addCommandWithAuth adds a command and all its subcommands with authentication check -func addCommandWithAuth(parent *cobra.Command, cmd *cobra.Command) { - // Apply auth check to the command itself - withAuth(cmd) - - // Apply auth check to all subcommands recursively - for _, subCmd := range cmd.Commands() { - withAuth(subCmd) - } - - parent.AddCommand(cmd) -} - -// initialize all the subcommands -func initCommands() { - cobra.EnableCommandSorting = false - rootCmd.AddGroup( - &cobra.Group{ - ID: "api", - Title: "API Server Operations", - }, - &cobra.Group{ - ID: "query", - Title: "Query Operations", - }, - ) - - // Add authentication commands (no auth required) - rootCmd.AddCommand(apiserver.NewLogonCmd(configManager)) - rootCmd.AddCommand(apiserver.NewLogoutCmd(configManager)) - - // Add commands that require authentication - // These commands will automatically check authentication before execution - addCommandWithAuth(rootCmd, apiserver.NewListCmd(configManager)) - addCommandWithAuth(rootCmd, permissions.NewPermsCmd(configManager)) - addCommandWithAuth(rootCmd, roles.NewRolesCmd(configManager)) - addCommandWithAuth(rootCmd, users.NewUsersCmd(configManager)) - addCommandWithAuth(rootCmd, jobs.NewJobsCmd(configManager)) - addCommandWithAuth(rootCmd, hosts.NewHostsCmd(configManager)) - addCommandWithAuth(rootCmd, queues.NewQueuesCmd(configManager)) - addCommandWithAuth(rootCmd, clusters.NewClustersCmd(configManager)) - addCommandWithAuth(rootCmd, resources.NewResourcesCmd(configManager)) - rootCmd.SetFlagErrorFunc(request.FlagErrorHandler) -} +package cmd + +import ( + "fmt" + apiserver "xcecli/cmd/auth" + "xcecli/cmd/clusters" + "xcecli/cmd/hosts" + "xcecli/cmd/jobs" + "xcecli/cmd/permissions" + "xcecli/cmd/queues" + "xcecli/cmd/resources" + "xcecli/cmd/roles" + "xcecli/cmd/users" + "xcecli/common/request" + "xcecli/config" + + "github.com/spf13/cobra" +) + +var ( + rootCmd = &cobra.Command{ + Use: "xce", + Short: "XCE CLI tool for the API Server operations", + } + configManager *config.ConfigManager +) + +// execute the root command +func Execute(cm *config.ConfigManager, version string) error { + if cm == nil { + return fmt.Errorf("the config manager is not initialized") + } + configManager = cm + rootCmd.Version = version + rootCmd.CompletionOptions.DisableDefaultCmd = true + rootCmd.SilenceErrors = true + rootCmd.SilenceUsage = true + + // initialize the subcommands + initCommands() + + return rootCmd.Execute() +} + +// withAuth wraps a command with authentication check +// This function creates a middleware that checks authentication before executing any command +func withAuth(cmd *cobra.Command) *cobra.Command { + // Store original handlers + originalRunE := cmd.RunE + originalRun := cmd.Run + + // Create new RunE that checks authentication first + cmd.RunE = func(cmd *cobra.Command, args []string) error { + // Check authentication before executing the command + _, err := configManager.IsAuthenticated() + if err != nil { + return err + } + + // Execute the original command logic + if originalRunE != nil { + return originalRunE(cmd, args) + } + if originalRun != nil { + originalRun(cmd, args) + return nil + } + return nil + } + + return cmd +} + +// addCommandWithAuth adds a command and all its subcommands with authentication check +func addCommandWithAuth(parent *cobra.Command, cmd *cobra.Command) { + // Apply auth check to the command itself + withAuth(cmd) + + // Apply auth check to all subcommands recursively + for _, subCmd := range cmd.Commands() { + withAuth(subCmd) + } + + parent.AddCommand(cmd) +} + +// initialize all the subcommands +func initCommands() { + cobra.EnableCommandSorting = false + rootCmd.AddGroup( + &cobra.Group{ + ID: "api", + Title: "API Server Operations", + }, + &cobra.Group{ + ID: "query", + Title: "Query Operations", + }, + ) + + // Add authentication commands (no auth required) + rootCmd.AddCommand(apiserver.NewLogonCmd(configManager)) + rootCmd.AddCommand(apiserver.NewLogoutCmd(configManager)) + + // Add commands that require authentication + // These commands will automatically check authentication before execution + addCommandWithAuth(rootCmd, apiserver.NewListCmd(configManager)) + addCommandWithAuth(rootCmd, permissions.NewPermsCmd(configManager)) + addCommandWithAuth(rootCmd, roles.NewRolesCmd(configManager)) + addCommandWithAuth(rootCmd, users.NewUsersCmd(configManager)) + addCommandWithAuth(rootCmd, jobs.NewJobsCmd(configManager)) + addCommandWithAuth(rootCmd, hosts.NewHostsCmd(configManager)) + addCommandWithAuth(rootCmd, queues.NewQueuesCmd(configManager)) + addCommandWithAuth(rootCmd, clusters.NewClustersCmd(configManager)) + addCommandWithAuth(rootCmd, resources.NewResourcesCmd(configManager)) + rootCmd.SetFlagErrorFunc(request.FlagErrorHandler) +}