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) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-02 09:33:16 +00:00
parent b57ce6d6a9
commit 3615c7c0fb
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
29 changed files with 1280 additions and 1285 deletions

View file

@ -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},

View file

@ -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)
}
}
}

View file

@ -1,9 +1,9 @@
package main
import (
_ "apiserver/docs"
"apiserver/backend"
"apiserver/backend/auth"
_ "apiserver/docs"
"apiserver/middleware"
"apiserver/router"
"apiserver/store"

View file

@ -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

View file

@ -675,4 +675,4 @@ func TestIntegration_SpecialCharactersInHostname(t *testing.T) {
if hostnameVal != xssHostname {
t.Errorf("hostname round-trip: got %q, want %q", hostnameVal, xssHostname)
}
}
}

View file

@ -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
}

View file

@ -1,11 +1,11 @@
package controllers
import (
"errors"
"apiserver/backend"
"apiserver/models"
"apiserver/store"
"apiserver/utils"
"errors"
"strings"
"testing"

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -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"`
}

View file

@ -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
}

View file

@ -68,4 +68,4 @@ func TestHostStateStr(t *testing.T) {
}
})
}
}
}

View file

@ -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) --

View file

@ -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",
}

View file

@ -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())
}
}
}

View file

@ -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.

View file

@ -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()
}

View file

@ -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 <action>' 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 <action>' 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
}

View file

@ -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
// "-<dirtyFingerprint>" 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
// "-<dirtyFingerprint>" 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)
}
}

View file

@ -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

View file

@ -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)
}

View file

@ -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...)
}

View file

@ -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
}

View file

@ -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"`
}

View file

@ -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"`
}

View file

@ -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{

View file

@ -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

View file

@ -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)
}