feat(delivery): refine base service integrations
This commit is contained in:
@@ -129,9 +129,13 @@ type MySQLServiceLedgerItem struct {
|
||||
}
|
||||
|
||||
type DeploymentCredentialView struct {
|
||||
Username string `json:"username"`
|
||||
Host string `json:"host"`
|
||||
Password string `json:"password"`
|
||||
Service string `json:"service"`
|
||||
InstanceName string `json:"instance_name"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
AccountHost string `json:"account_host"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// targetMetadata describes the native VM候选节点池以及部署形态,由 AWX inventory hosts 动态组装。
|
||||
@@ -397,13 +401,19 @@ func (s *DeliveryService) getTarget(ctx context.Context, templateID uint64) (Del
|
||||
return s.awxDeliveryTarget(ctx, *template)
|
||||
}
|
||||
|
||||
func (s *DeliveryService) ListHostMountPaths(ctx context.Context, targetID uint64, hostName string) ([]DeliveryMountPath, error) {
|
||||
func (s *DeliveryService) ListHostMountPaths(ctx context.Context, targetID uint64, hostName string, prefix string) ([]DeliveryMountPath, error) {
|
||||
if targetID == 0 {
|
||||
return nil, fmt.Errorf("target_id is required")
|
||||
}
|
||||
if hostName == "" || len(hostName) > 253 || !hostNamePattern.MatchString(hostName) {
|
||||
return nil, fmt.Errorf("host must be a valid inventory host name")
|
||||
}
|
||||
prefix = strings.TrimSpace(prefix)
|
||||
if prefix != "" {
|
||||
if err := validateDirectoryLookupPrefix(prefix); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
template, err := s.awx.GetJobTemplate(ctx, targetID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("deployment target is unavailable: %w", err)
|
||||
@@ -423,9 +433,16 @@ func (s *DeliveryService) ListHostMountPaths(ctx context.Context, targetID uint6
|
||||
return nil, fmt.Errorf("host %q is not in the deployment target inventory", hostName)
|
||||
}
|
||||
if s.cfg.AWXFactsTemplateID != 0 {
|
||||
if err := s.refreshHostFacts(ctx, hostName); err != nil {
|
||||
stdout, err := s.refreshHostFacts(ctx, hostName, prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if prefix != "" {
|
||||
return directoryPathsFromAWXStdout(stdout), nil
|
||||
}
|
||||
}
|
||||
if prefix != "" {
|
||||
return []DeliveryMountPath{}, nil
|
||||
}
|
||||
facts, err := s.awx.GetHostFacts(ctx, matched.ID)
|
||||
if err != nil {
|
||||
@@ -438,21 +455,86 @@ func (s *DeliveryService) ListHostMountPaths(ctx context.Context, targetID uint6
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *DeliveryService) refreshHostFacts(ctx context.Context, hostName string) error {
|
||||
func validateDirectoryLookupPrefix(prefix string) error {
|
||||
if prefix == "" {
|
||||
return nil
|
||||
}
|
||||
if len(prefix) > 512 {
|
||||
return fmt.Errorf("path prefix is too long")
|
||||
}
|
||||
if !strings.HasPrefix(prefix, "/") {
|
||||
return fmt.Errorf("path prefix must be an absolute path")
|
||||
}
|
||||
if strings.ContainsRune(prefix, 0) {
|
||||
return fmt.Errorf("path prefix contains invalid characters")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func directoryPathsFromAWXStdout(stdout string) []DeliveryMountPath {
|
||||
const marker = "XINFRA_PATH_COMPLETIONS_JSON="
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
idx := strings.Index(line, marker)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
if items, ok := parseDirectoryCompletionJSON(line[idx+len(marker):]); ok {
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].Path < items[j].Path
|
||||
})
|
||||
return items
|
||||
}
|
||||
}
|
||||
return []DeliveryMountPath{}
|
||||
}
|
||||
|
||||
func parseDirectoryCompletionJSON(raw string) ([]DeliveryMountPath, bool) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
candidates := []string{raw}
|
||||
if strings.Contains(raw, `\"`) {
|
||||
candidates = append(candidates, strings.ReplaceAll(raw, `\"`, `"`))
|
||||
}
|
||||
for _, candidate := range candidates {
|
||||
start := strings.Index(candidate, "[")
|
||||
end := strings.LastIndex(candidate, "]")
|
||||
if start < 0 || end < start {
|
||||
continue
|
||||
}
|
||||
var items []DeliveryMountPath
|
||||
if err := json.Unmarshal([]byte(candidate[start:end+1]), &items); err == nil {
|
||||
return items, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (s *DeliveryService) refreshHostFacts(ctx context.Context, hostName string, lookupPath string) (string, error) {
|
||||
extraVars := map[string]any{
|
||||
"target_hosts": hostName,
|
||||
}
|
||||
if lookupPath != "" {
|
||||
extraVars["lookup_path"] = lookupPath
|
||||
}
|
||||
job, err := s.awx.Launch(ctx, s.cfg.AWXFactsTemplateID, AWXLaunchRequest{
|
||||
Limit: hostName,
|
||||
Limit: hostName,
|
||||
ExtraVars: extraVars,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("launch AWX facts job: %w", err)
|
||||
return "", fmt.Errorf("launch AWX facts job: %w", err)
|
||||
}
|
||||
done, err := s.awx.WaitJob(ctx, strconv.FormatUint(job.ID, 10), time.Duration(s.cfg.AWXFactsTimeoutSeconds)*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if done.Status != "successful" || done.Failed {
|
||||
return fmt.Errorf("AWX facts job %d finished with status %s", done.ID, done.Status)
|
||||
return "", fmt.Errorf("AWX facts job %d finished with status %s", done.ID, done.Status)
|
||||
}
|
||||
return nil
|
||||
stdout, err := s.awx.JobStdout(ctx, strconv.FormatUint(job.ID, 10))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return stdout, nil
|
||||
}
|
||||
|
||||
func (s *DeliveryService) awxDeliveryTarget(ctx context.Context, template AWXJobTemplate) (DeliveryTarget, error) {
|
||||
@@ -503,6 +585,9 @@ func (s *DeliveryService) CreateTask(ctx context.Context, userID uint64, isAdmin
|
||||
if len(credentialInput["root@localhost"]) < 16 || len(credentialInput["xinfra_admin@%"]) < 16 {
|
||||
return nil, false, fmt.Errorf("mysql passwords must be at least 16 characters")
|
||||
}
|
||||
if !mysqlPasswordPattern.MatchString(credentialInput["root@localhost"]) || !mysqlPasswordPattern.MatchString(credentialInput["xinfra_admin@%"]) {
|
||||
return nil, false, fmt.Errorf("mysql passwords may only contain letters and digits")
|
||||
}
|
||||
}
|
||||
input.MySQLRootPassword = ""
|
||||
input.MySQLAdminPassword = ""
|
||||
@@ -657,6 +742,8 @@ var (
|
||||
// timezone 仅接受偏移量(±HH:MM)、SYSTEM 或命名时区(如 Asia/Shanghai)。
|
||||
var timezonePattern = regexp.MustCompile(`^([+-](0\d|1[0-4]):[0-5]\d|SYSTEM|[A-Za-z]+(?:/[A-Za-z0-9_+-]+)+)$`)
|
||||
|
||||
var mysqlPasswordPattern = regexp.MustCompile(`^[A-Za-z0-9]+$`)
|
||||
|
||||
func validateDeliveryInput(input MySQLDeliveryInput, _ []string) error {
|
||||
if len(input.Namespace) > 63 || !dnsLabelPattern.MatchString(input.Namespace) {
|
||||
return fmt.Errorf("namespace must be a valid Kubernetes DNS label")
|
||||
@@ -773,7 +860,13 @@ func (s *DeliveryService) ListTasks(ctx context.Context, userID uint64, isAdmin
|
||||
query = query.Where("business_line_id IN (?)", s.db.Model(&model.BusinessLineUser{}).Select("business_line_id").Where("user_id = ?", userID))
|
||||
}
|
||||
var tasks []model.DeliveryTask
|
||||
return tasks, query.Find(&tasks).Error
|
||||
if err := query.Find(&tasks).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.fillCredentialAvailability(ctx, tasks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
func (s *DeliveryService) ListMySQLServiceLedger(ctx context.Context, userID uint64, isAdmin bool, businessLineID uint64) ([]MySQLServiceLedgerItem, error) {
|
||||
@@ -916,6 +1009,11 @@ func (s *DeliveryService) GetTask(ctx context.Context, taskID string, userID uin
|
||||
if err := query.First(&task).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
taskItems := []model.DeliveryTask{task}
|
||||
if err := s.fillCredentialAvailability(ctx, taskItems); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
task = taskItems[0]
|
||||
var events []model.TaskEvent
|
||||
if err := s.db.WithContext(ctx).Where("task_id = ?", taskID).Order("id ASC").Find(&events).Error; err != nil {
|
||||
return nil, nil, err
|
||||
@@ -923,6 +1021,41 @@ func (s *DeliveryService) GetTask(ctx context.Context, taskID string, userID uin
|
||||
return &task, events, nil
|
||||
}
|
||||
|
||||
func (s *DeliveryService) fillCredentialAvailability(ctx context.Context, tasks []model.DeliveryTask) error {
|
||||
if len(tasks) == 0 {
|
||||
return nil
|
||||
}
|
||||
taskIDs := make([]string, 0, len(tasks))
|
||||
for i := range tasks {
|
||||
if tasks[i].Status == model.TaskFinished || tasks[i].Status == model.TaskRegisterFailed {
|
||||
taskIDs = append(taskIDs, tasks[i].ID)
|
||||
}
|
||||
}
|
||||
if len(taskIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
var rows []struct {
|
||||
TaskID string
|
||||
Count int64
|
||||
}
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.DeploymentCredential{}).
|
||||
Select("task_id, count(*) as count").
|
||||
Where("task_id IN ? AND status = ?", taskIDs, "available").
|
||||
Group("task_id").
|
||||
Scan(&rows).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
available := make(map[string]bool, len(rows))
|
||||
for _, row := range rows {
|
||||
available[row.TaskID] = row.Count > 0
|
||||
}
|
||||
for i := range tasks {
|
||||
tasks[i].CredentialAvailable = available[tasks[i].ID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DeliveryService) RevealDeploymentCredentials(ctx context.Context, taskID string, userID uint64, isAdmin bool) ([]DeploymentCredentialView, error) {
|
||||
task, _, err := s.GetTask(ctx, taskID, userID, isAdmin)
|
||||
if err != nil {
|
||||
@@ -933,6 +1066,10 @@ func (s *DeliveryService) RevealDeploymentCredentials(ctx context.Context, taskI
|
||||
}
|
||||
var out []DeploymentCredentialView
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var result model.DeploymentResult
|
||||
if err := tx.Where("task_id = ?", taskID).First(&result).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
var credentials []model.DeploymentCredential
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("task_id = ? AND status = ?", taskID, "available").
|
||||
@@ -949,22 +1086,20 @@ func (s *DeliveryService) RevealDeploymentCredentials(ctx context.Context, taskI
|
||||
return err
|
||||
}
|
||||
out = append(out, DeploymentCredentialView{
|
||||
Username: credential.Username,
|
||||
Host: credential.AccountHost,
|
||||
Password: password,
|
||||
Service: firstNonEmptyDeliveryValue(result.Component, credential.Component, task.Component, "mysql"),
|
||||
InstanceName: firstNonEmptyDeliveryValue(result.InstanceName, credential.InstanceName, task.InstanceName),
|
||||
Host: firstNonEmptyDeliveryValue(result.Host, task.TargetHostIP),
|
||||
Port: firstNonZero(result.Port, task.MySQLPort),
|
||||
Username: credential.Username,
|
||||
AccountHost: credential.AccountHost,
|
||||
Password: password,
|
||||
})
|
||||
}
|
||||
ids := make([]uint64, 0, len(credentials))
|
||||
for _, credential := range credentials {
|
||||
ids = append(ids, credential.ID)
|
||||
}
|
||||
now := time.Now()
|
||||
return tx.Model(&model.DeploymentCredential{}).Where("id IN ?", ids).Updates(map[string]any{
|
||||
"status": "viewed",
|
||||
"viewed_by": userID,
|
||||
"viewed_at": now,
|
||||
"updated_at": now,
|
||||
}).Error
|
||||
return tx.Unscoped().Where("id IN ?", ids).Delete(&model.DeploymentCredential{}).Error
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -972,6 +1107,24 @@ func (s *DeliveryService) RevealDeploymentCredentials(ctx context.Context, taskI
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func firstNonEmptyDeliveryValue(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func firstNonZero(values ...int) int {
|
||||
for _, value := range values {
|
||||
if value != 0 {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *DeliveryService) SubscribeTask(taskID string) (<-chan DeliveryTaskSnapshot, func()) {
|
||||
ch := make(chan DeliveryTaskSnapshot, 8)
|
||||
s.streamMu.Lock()
|
||||
@@ -1170,7 +1323,7 @@ func (s *DeliveryService) claimAndReserve(ctx context.Context) (*model.DeliveryT
|
||||
return s.failInTransaction(tx, &task, model.TaskValidationFailed, "deployment target has no candidate hosts")
|
||||
}
|
||||
var occupied []string
|
||||
occupiedExclude := []string{model.TaskExecutionFailed, model.TaskValidationFailed, model.TaskCanceled}
|
||||
occupiedExclude := allocationReleasedTaskStatuses()
|
||||
if err := tx.Model(&model.DeliveryTask{}).Where("target_id = ? AND target_host <> ? AND status NOT IN ?", task.TargetID, "", occupiedExclude).Pluck("target_host", &occupied).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1641,6 +1794,18 @@ func terminalTaskStatuses() []string {
|
||||
}
|
||||
}
|
||||
|
||||
func allocationReleasedTaskStatuses() []string {
|
||||
return []string{
|
||||
model.TaskFinished,
|
||||
model.TaskExecutionFailed,
|
||||
model.TaskValidationFailed,
|
||||
model.TaskRegisterFailed,
|
||||
model.TaskCanceled,
|
||||
model.TaskRolledBack,
|
||||
model.TaskRollbackAck,
|
||||
}
|
||||
}
|
||||
|
||||
func awxNotificationMessage(input AWXJobNotificationInput) string {
|
||||
status := strings.TrimSpace(input.Status)
|
||||
name := strings.TrimSpace(input.Name)
|
||||
|
||||
@@ -143,6 +143,35 @@ func TestRollbackExtraVarsTargetsOnlyTheAllocatedInstance(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectoryPathsFromAWXStdout(t *testing.T) {
|
||||
stdout := `
|
||||
TASK [Show directory completions] **********************************************
|
||||
ok: [db-01] => {
|
||||
"stdout": "XINFRA_PATH_COMPLETIONS_JSON=[{\"path\":\"/a/bc\",\"available_gi\":12},{\"path\":\"/a/bb\",\"available_gi\":8}]"
|
||||
}
|
||||
`
|
||||
items := directoryPathsFromAWXStdout(stdout)
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("expected two completion items, got %#v", items)
|
||||
}
|
||||
if items[0].Path != "/a/bb" || items[1].Path != "/a/bc" {
|
||||
t.Fatalf("items should be parsed and sorted by path: %#v", items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDirectoryLookupPrefix(t *testing.T) {
|
||||
for _, path := range []string{"/", "/a", "/a/b", "/lib/data"} {
|
||||
if err := validateDirectoryLookupPrefix(path); err != nil {
|
||||
t.Fatalf("valid path prefix %q rejected: %v", path, err)
|
||||
}
|
||||
}
|
||||
for _, path := range []string{"a", "relative/path"} {
|
||||
if err := validateDirectoryLookupPrefix(path); err == nil {
|
||||
t.Fatalf("invalid path prefix %q was accepted", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterFailedIsProtectedFromRollback(t *testing.T) {
|
||||
if !rollbackProtectedStatus(model.TaskRegisterFailed) {
|
||||
t.Fatal("register_failed must preserve the healthy instance and resource usage")
|
||||
|
||||
@@ -37,17 +37,17 @@ type MachineService struct {
|
||||
}
|
||||
|
||||
type MachineListQuery struct {
|
||||
Page int
|
||||
Size int
|
||||
Hostname string
|
||||
AssetNumber string
|
||||
Type string
|
||||
Location string
|
||||
IP string
|
||||
Spec string
|
||||
BusinessLine string
|
||||
Source string
|
||||
Status string
|
||||
BusinessLineID uint64
|
||||
Page int
|
||||
Size int
|
||||
Hostname string
|
||||
AssetNumber string
|
||||
Type string
|
||||
Location string
|
||||
IP string
|
||||
Spec string
|
||||
Source string
|
||||
Status string
|
||||
}
|
||||
|
||||
type MachineResourceItem struct {
|
||||
@@ -189,25 +189,29 @@ func (s *MachineService) SyncNow(ctx context.Context) (*model.MachineSyncState,
|
||||
return s.syncState(ctx)
|
||||
}
|
||||
|
||||
func (s *MachineService) Overview(ctx context.Context) (*MachineOverview, error) {
|
||||
var total, physical int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.MachineResource{}).Count(&total).Error; err != nil {
|
||||
func (s *MachineService) Overview(ctx context.Context, userID uint64, isAdmin bool, businessLineID uint64) (*MachineOverview, error) {
|
||||
baseDB, err := s.scopedMachineDB(ctx, userID, isAdmin, businessLineID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Model(&model.MachineResource{}).Where("resource_type = ?", "physical").Count(&physical).Error; err != nil {
|
||||
var total, physical int64
|
||||
if err := baseDB.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := baseDB.Session(&gorm.Session{}).Where("resource_type = ?", "physical").Count(&physical).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sourceCounts, err := s.sourceCounts(ctx)
|
||||
sourceCounts, err := s.sourceCounts(baseDB.Session(&gorm.Session{}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cloudSources := []string{"aliyun", "ali", "alicloud", "aws", "qiniu"}
|
||||
cmdbTotal, err := s.countExcludingSources(ctx, cloudSources)
|
||||
cmdbTotal, err := s.countExcludingSources(baseDB.Session(&gorm.Session{}), cloudSources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmdbPhysical, err := s.countExcludingSourcesAndType(ctx, cloudSources, "physical")
|
||||
cmdbPhysical, err := s.countExcludingSourcesAndType(baseDB.Session(&gorm.Session{}), cloudSources, "physical")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -239,7 +243,7 @@ func (s *MachineService) Overview(ctx context.Context) (*MachineOverview, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MachineService) List(ctx context.Context, query MachineListQuery) (*MachineResourceList, error) {
|
||||
func (s *MachineService) List(ctx context.Context, userID uint64, isAdmin bool, query MachineListQuery) (*MachineResourceList, error) {
|
||||
if query.Page <= 0 {
|
||||
query.Page = 1
|
||||
}
|
||||
@@ -250,7 +254,10 @@ func (s *MachineService) List(ctx context.Context, query MachineListQuery) (*Mac
|
||||
query.Size = 200
|
||||
}
|
||||
|
||||
db := s.db.WithContext(ctx).Model(&model.MachineResource{})
|
||||
db, err := s.scopedMachineDB(ctx, userID, isAdmin, query.BusinessLineID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db = applyMachineFilters(db, query)
|
||||
|
||||
var total int64
|
||||
@@ -270,6 +277,37 @@ func (s *MachineService) List(ctx context.Context, query MachineListQuery) (*Mac
|
||||
return &MachineResourceList{Total: total, Items: items}, nil
|
||||
}
|
||||
|
||||
func (s *MachineService) scopedMachineDB(ctx context.Context, userID uint64, isAdmin bool, businessLineID uint64) (*gorm.DB, error) {
|
||||
if businessLineID == 0 {
|
||||
return nil, errors.New("business_line_id is required")
|
||||
}
|
||||
if !isAdmin {
|
||||
var binding model.BusinessLineUser
|
||||
err := s.db.WithContext(ctx).Where("business_line_id = ? AND user_id = ?", businessLineID, userID).First(&binding).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("current user does not have business line permission")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var mappings []model.BusinessLineSinaOrganization
|
||||
if err := s.db.WithContext(ctx).Where("business_line_id = ?", businessLineID).Order("sina_organization_name ASC").Find(&mappings).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names := make([]string, 0, len(mappings))
|
||||
for _, mapping := range mappings {
|
||||
if name := strings.TrimSpace(mapping.SinaOrganizationName); name != "" {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
db := s.db.WithContext(ctx).Model(&model.MachineResource{})
|
||||
if len(names) == 0 {
|
||||
return db.Where("1 = 0"), nil
|
||||
}
|
||||
return db.Where("business_line IN ?", names), nil
|
||||
}
|
||||
|
||||
func (s *MachineService) acquireSync() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@@ -527,18 +565,15 @@ func applyMachineFilters(db *gorm.DB, query MachineListQuery) *gorm.DB {
|
||||
if query.Spec != "" {
|
||||
db = db.Where("spec LIKE ?", "%"+query.Spec+"%")
|
||||
}
|
||||
if query.BusinessLine != "" {
|
||||
db = db.Where("business_line LIKE ?", "%"+query.BusinessLine+"%")
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func (s *MachineService) sourceCounts(ctx context.Context) (map[string]int64, error) {
|
||||
func (s *MachineService) sourceCounts(db *gorm.DB) (map[string]int64, error) {
|
||||
var rows []struct {
|
||||
Source string
|
||||
Count int64
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Model(&model.MachineResource{}).Select("source, count(*) as count").Group("source").Scan(&rows).Error; err != nil {
|
||||
if err := db.Select("source, count(*) as count").Group("source").Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := map[string]int64{}
|
||||
@@ -549,15 +584,15 @@ func (s *MachineService) sourceCounts(ctx context.Context) (map[string]int64, er
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *MachineService) countExcludingSources(ctx context.Context, sources []string) (int64, error) {
|
||||
func (s *MachineService) countExcludingSources(db *gorm.DB, sources []string) (int64, error) {
|
||||
var count int64
|
||||
err := s.db.WithContext(ctx).Model(&model.MachineResource{}).Where("source NOT IN ?", sources).Count(&count).Error
|
||||
err := db.Where("source NOT IN ?", sources).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (s *MachineService) countExcludingSourcesAndType(ctx context.Context, sources []string, resourceType string) (int64, error) {
|
||||
func (s *MachineService) countExcludingSourcesAndType(db *gorm.DB, sources []string, resourceType string) (int64, error) {
|
||||
var count int64
|
||||
err := s.db.WithContext(ctx).Model(&model.MachineResource{}).Where("source NOT IN ? AND resource_type = ?", sources, resourceType).Count(&count).Error
|
||||
err := db.Where("source NOT IN ? AND resource_type = ?", sources, resourceType).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user