diff --git a/bench/config_items_save_results_bench_test.go b/bench/config_items_save_results_bench_test.go index f822adb08..652c60b64 100644 --- a/bench/config_items_save_results_bench_test.go +++ b/bench/config_items_save_results_bench_test.go @@ -11,6 +11,7 @@ import ( v1 "github.com/flanksource/config-db/api/v1" "github.com/flanksource/config-db/db" dutyModels "github.com/flanksource/duty/models" + "github.com/flanksource/duty/types" "github.com/google/uuid" ) @@ -202,12 +203,29 @@ func buildScrapeResults(size int, dataset string, revision int) []v1.ScrapeResul "port": 8080, }, }, + Properties: buildScrapeResultProperties(revision), }) } return results } +func buildScrapeResultProperties(revision int) types.Properties { + payload := strings.Repeat("x", 16*1024) + properties := make(types.Properties, 0, 6) + for i := range 6 { + properties = append(properties, &types.Property{ + Name: fmt.Sprintf("bench-property-%d", i), + Label: fmt.Sprintf("Bench Property %d", i), + Type: "text", + Text: fmt.Sprintf("%s-%d-%d", payload, revision, i), + Tooltip: fmt.Sprintf("bench property tooltip %d", i), + Order: i, + }) + } + return properties +} + func cleanupBenchRows(tb testing.TB, scraperID uuid.UUID) { tb.Helper() diff --git a/db/config.go b/db/config.go index b7ee5724c..66e87a34b 100644 --- a/db/config.go +++ b/db/config.go @@ -147,7 +147,6 @@ func NewConfigItemFromResult(ctx api.ScrapeContext, result v1.ScrapeResult) (*mo Name: &result.Name, Source: &result.Source, Labels: (*types.JSONStringMap)(&result.Labels), - Properties: &result.Properties, Config: &dataStr, Ready: result.Ready, Parents: result.Parents, @@ -160,6 +159,15 @@ func NewConfigItemFromResult(ctx api.ScrapeContext, result v1.ScrapeResult) (*mo ci.ScraperID = nil } + // Scraper-owned config results always express the scraper's current property slice. + // A nil/empty result.Properties therefore means "the scraper now owns zero properties" + // and must remove stale scraper-created properties while preserving user/other-scraper properties. + if ci.ScraperID != nil && *ci.ScraperID != uuid.Nil { + ci.Properties = lo.ToPtr(dutyModels.NewOwnedProperties(result.Properties)) + } else if result.Properties != nil { + ci.Properties = lo.ToPtr(dutyModels.NewOwnedProperties(result.Properties)) + } + ci.Tags = types.JSONStringMap(result.Tags) // If the config result hasn't specified an id for the config, // we try to use the external id as the primary key of the config item. diff --git a/db/models/config_item.go b/db/models/config_item.go index ff189818a..bac39da1a 100644 --- a/db/models/config_item.go +++ b/db/models/config_item.go @@ -17,31 +17,31 @@ import ( // ConfigItem represents the config item database table // Deprecated: Use models.ConfigItem from duty. type ConfigItem struct { - ID string `gorm:"primaryKey;unique_index;not null;column:id;default:generate_ulid()" json:"id" ` - ScraperID *uuid.UUID `gorm:"column:scraper_id;default:null" json:"scraper_id,omitempty"` - ConfigClass string `gorm:"column:config_class;default:''" json:"config_class" ` - ExternalID pq.StringArray `gorm:"column:external_id;type:[]text" json:"external_id,omitempty" ` - Type string `gorm:"column:type" json:"type,omitempty" ` - Status *string `gorm:"column:status;default:null" json:"status,omitempty" ` - Ready bool `json:"ready,omitempty" ` - Health *models.Health `json:"health,omitempty"` - Name *string `gorm:"column:name;default:null" json:"name,omitempty" ` - Description *string `gorm:"column:description;default:null" json:"description,omitempty" ` - Config *string `gorm:"column:config;default:null" json:"config,omitempty" ` - Source *string `gorm:"column:source;default:null" json:"source,omitempty" ` - ParentID *string `gorm:"column:parent_id;default:null" json:"parent_id,omitempty"` - Path string `gorm:"column:path;default:null" json:"path,omitempty"` - CostPerMinute float64 `gorm:"column:cost_per_minute;default:null" json:"cost_per_minute,omitempty"` - CostTotal1d float64 `gorm:"column:cost_total_1d;default:null" json:"cost_total_1d,omitempty"` - CostTotal7d float64 `gorm:"column:cost_total_7d;default:null" json:"cost_total_7d,omitempty"` - CostTotal30d float64 `gorm:"column:cost_total_30d;default:null" json:"cost_total_30d,omitempty"` - Labels *types.JSONStringMap `gorm:"column:labels;default:null" json:"labels,omitempty"` - Tags types.JSONStringMap `gorm:"column:tags;default:null" json:"tags,omitempty"` - Properties *types.Properties `gorm:"column:properties;default:null" json:"properties,omitempty"` - CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` - UpdatedAt *time.Time `gorm:"column:updated_at;autoUpdateTime:false;<-:update" json:"updated_at"` - DeletedAt *time.Time `gorm:"column:deleted_at" json:"deleted_at"` - DeleteReason v1.ConfigDeleteReason `gorm:"column:delete_reason" json:"delete_reason"` + ID string `gorm:"primaryKey;unique_index;not null;column:id;default:generate_ulid()" json:"id" ` + ScraperID *uuid.UUID `gorm:"column:scraper_id;default:null" json:"scraper_id,omitempty"` + ConfigClass string `gorm:"column:config_class;default:''" json:"config_class" ` + ExternalID pq.StringArray `gorm:"column:external_id;type:[]text" json:"external_id,omitempty" ` + Type string `gorm:"column:type" json:"type,omitempty" ` + Status *string `gorm:"column:status;default:null" json:"status,omitempty" ` + Ready bool `json:"ready,omitempty" ` + Health *models.Health `json:"health,omitempty"` + Name *string `gorm:"column:name;default:null" json:"name,omitempty" ` + Description *string `gorm:"column:description;default:null" json:"description,omitempty" ` + Config *string `gorm:"column:config;default:null" json:"config,omitempty" ` + Source *string `gorm:"column:source;default:null" json:"source,omitempty" ` + ParentID *string `gorm:"column:parent_id;default:null" json:"parent_id,omitempty"` + Path string `gorm:"column:path;default:null" json:"path,omitempty"` + CostPerMinute float64 `gorm:"column:cost_per_minute;default:null" json:"cost_per_minute,omitempty"` + CostTotal1d float64 `gorm:"column:cost_total_1d;default:null" json:"cost_total_1d,omitempty"` + CostTotal7d float64 `gorm:"column:cost_total_7d;default:null" json:"cost_total_7d,omitempty"` + CostTotal30d float64 `gorm:"column:cost_total_30d;default:null" json:"cost_total_30d,omitempty"` + Labels *types.JSONStringMap `gorm:"column:labels;default:null" json:"labels,omitempty"` + Tags types.JSONStringMap `gorm:"column:tags;default:null" json:"tags,omitempty"` + Properties *models.OwnedProperties `gorm:"column:properties;default:null" json:"properties,omitempty"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` + UpdatedAt *time.Time `gorm:"column:updated_at;autoUpdateTime:false;<-:update" json:"updated_at"` + DeletedAt *time.Time `gorm:"column:deleted_at" json:"deleted_at"` + DeleteReason v1.ConfigDeleteReason `gorm:"column:delete_reason" json:"delete_reason"` Parents []v1.ConfigExternalKey `gorm:"-" json:"parents,omitempty"` Children []v1.ConfigExternalKey `gorm:"-" json:"children,omitempty"` diff --git a/db/update.go b/db/update.go index 8155c259b..d964bd058 100644 --- a/db/update.go +++ b/db/update.go @@ -109,18 +109,6 @@ func mapStringEqual(a, b map[string]string) bool { return true } -func mapEqual(a, b map[string]any) bool { - if len(a) != len(b) { - return false - } - for k, v := range a { - if b[k] != v { - return false - } - } - return true -} - func updateCI(ctx api.ScrapeContext, summary *v1.ScrapeSummary, result *v1.ScrapeResult, ci, existing *models.ConfigItem) (bool, []*models.ConfigChange, error) { ci.ID = existing.ID updates := make(map[string]any) @@ -221,16 +209,51 @@ func updateCI(ctx api.ScrapeContext, summary *v1.ScrapeSummary, result *v1.Scrap summary.AddWarning(ci.Type, fmt.Sprintf("updated scraper_id of config[%s] from %s to %s", ci, existing.ScraperID, ci.ScraperID)) } - if ci.Properties != nil && len(*ci.Properties) > 0 && (existing.Properties == nil || !mapEqual(ci.Properties.AsMap(), existing.Properties.AsMap())) { - updates["properties"] = *ci.Properties - } + // nil properties mean "not observed / no opinion". + // empty properties mean "observed and now owns zero properties", so remove this creator's slice. + propertyUpdateNeeded := ci.Properties != nil - if len(updates) == 0 { + if len(updates) == 0 && !propertyUpdateNeeded { return false, changes, nil } - if err := ctx.DutyContext().DB().Model(ci).Updates(updates).Error; err != nil { - return false, nil, errors.Wrapf(dutydb.ErrorDetails(err), "unable to update config item: %s", ci) + fieldsChanged := len(updates) > 0 + propertyChanged := false + if err := ctx.DutyContext().DB().Transaction(func(tx *gorm.DB) error { + if len(updates) > 0 { + if err := tx.Model(ci).Updates(updates).Error; err != nil { + return dutydb.ErrorDetails(err) + } + } + + if propertyUpdateNeeded { + createdBy := ctx.ScraperID() + if ci.ScraperID != nil && *ci.ScraperID != uuid.Nil { + createdBy = ci.ScraperID.String() + } + if createdBy == "" { + return nil + } + + configID, err := uuid.Parse(ci.ID) + if err != nil { + return fmt.Errorf("invalid config id %q: %w", ci.ID, err) + } + createdByID, err := uuid.Parse(createdBy) + if err != nil { + return fmt.Errorf("invalid property creator id %q: %w", createdBy, err) + } + + result, err := dutyModels.UpdateConfigItemProperties(tx, configID, dutyModels.PropertyCreatorTypeScraper, createdByID, ci.Properties.AsProperties()) + if err != nil { + return dutydb.ErrorDetails(err) + } + ci.Properties = &result.Properties + propertyChanged = result.Changed + } + return nil + }); err != nil { + return false, nil, errors.Wrapf(err, "unable to update config item: %s", ci) } if isDeleted { @@ -241,7 +264,7 @@ func updateCI(ctx api.ScrapeContext, summary *v1.ScrapeSummary, result *v1.Scrap ).Add(1) } - return true, changes, nil + return fieldsChanged || propertyChanged, changes, nil } func shouldExcludeChange(ctx api.ScrapeContext, result *v1.ScrapeResult, changeResult v1.ChangeResult) (bool, error) { @@ -576,13 +599,42 @@ func saveResults(ctx api.ScrapeContext, results []v1.ScrapeResult) (v1.ScrapeSum summary.AddChangeSummary(configType, cs) } - // NOTE: On duplicate primary key do nothing - // because an incremental scraper might have already inserted the config item. - if err := ctx.DB(). - Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "id"}}, DoNothing: true}). - CreateInBatches(extractResult.newConfigs, configItemsBulkInsertSize).Error; err != nil { - return summary, ctx.Oops().Wrapf(dutydb.ErrorDetails(err), "failed to create config items") + newConfigProperties := map[string]dutyModels.OwnedProperties{} + for _, config := range extractResult.newConfigs { + if config.Properties != nil && scraperID != nil && *scraperID != uuid.Nil { + newConfigProperties[config.ID] = *config.Properties + config.Properties = nil + } } + + if err := ctx.DB().Transaction(func(tx *gorm.DB) error { + // NOTE: On duplicate primary key do nothing + // because an incremental scraper might have already inserted the config item. + if err := tx. + Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "id"}}, DoNothing: true}). + CreateInBatches(extractResult.newConfigs, configItemsBulkInsertSize).Error; err != nil { + return dutydb.ErrorDetails(err) + } + + for _, config := range extractResult.newConfigs { + if props, ok := newConfigProperties[config.ID]; ok { + configID, err := uuid.Parse(config.ID) + if err != nil { + return fmt.Errorf("invalid config id %q: %w", config.ID, err) + } + + result, err := dutyModels.UpdateConfigItemProperties(tx, configID, dutyModels.PropertyCreatorTypeScraper, *scraperID, props.AsProperties()) + if err != nil { + return dutydb.ErrorDetails(err) + } + config.Properties = &result.Properties + } + } + return nil + }); err != nil { + return summary, ctx.Oops().Wrapf(err, "failed to create config items") + } + for _, config := range extractResult.newConfigs { summary.AddInserted(config.Type) ctx.TempCache().Insert(*config) diff --git a/go.mod b/go.mod index 958ff2ad6..67a9c0f28 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/flanksource/clicky v1.21.8 github.com/flanksource/commons v1.51.4 github.com/flanksource/deps v1.0.28 - github.com/flanksource/duty v1.0.1301 + github.com/flanksource/duty v1.0.1302-0.20260505080647-4f6025d97824 github.com/flanksource/gomplate/v3 v3.24.79 github.com/flanksource/is-healthy v1.0.87 github.com/flanksource/ketall v1.1.9 @@ -163,12 +163,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/kms v1.49.5 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect - github.com/buger/jsonparser v1.1.2 // indirect github.com/casbin/casbin/v2 v2.135.0 // indirect github.com/casbin/casbin/v3 v3.8.1 // indirect github.com/casbin/gorm-adapter/v3 v3.41.0 // indirect @@ -270,7 +268,6 @@ require ( github.com/hashicorp/hcl/v2 v2.24.0 // indirect github.com/henvic/httpretty v0.1.4 // indirect github.com/hirochachacha/go-smb2 v1.1.0 // indirect - github.com/invopop/jsonschema v0.13.0 // indirect github.com/itchyny/gojq v0.12.19 // indirect github.com/itchyny/timefmt-go v0.1.8 // indirect github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 // indirect @@ -295,7 +292,6 @@ require ( github.com/lrita/cmap v0.0.0-20231108122212-cb084a67f554 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect - github.com/mailru/easyjson v0.9.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/microsoft/kiota-abstractions-go v1.9.4 // indirect github.com/microsoft/kiota-authentication-azure-go v1.3.1 // indirect @@ -360,12 +356,8 @@ require ( github.com/vadimi/go-http-ntlm v1.0.3 // indirect github.com/vadimi/go-http-ntlm/v2 v2.5.0 // indirect github.com/vadimi/go-ntlm v1.2.1 // indirect - github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xuri/efp v0.0.1 // indirect @@ -523,11 +515,11 @@ require ( // replace github.com/flanksource/clicky => ../clicky // replace github.com/flanksource/commons => ../commons // replace github.com/flanksource/deps => ../deps -// replace github.com/flanksource/duty => ../duty - // replace github.com/flanksource/gomplate/v3 => ../gomplate // replace github.com/flanksource/ketall => ../ketall // replace github.com/flanksource/postq => ../postq // replace github.com/flanksource/is-healthy => ../is-healthy +// replace github.com/flanksource/duty => ../duty + replace github.com/glebarez/sqlite => github.com/clarkmcc/gorm-sqlite v0.0.0-20240426202654-00ed082c0311 diff --git a/go.sum b/go.sum index e11f76a14..9d33514aa 100644 --- a/go.sum +++ b/go.sum @@ -339,8 +339,6 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= -github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= -github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -357,8 +355,6 @@ github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6 github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= -github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI= github.com/casbin/casbin/v2 v2.135.0 h1:6BLkMQiGotYyS5yYeWgW19vxqugUlvHFkFiLnLR/bxk= github.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18= @@ -506,8 +502,8 @@ github.com/flanksource/commons v1.51.4 h1:ys3O4g0exWoz/viKf9vwTUItTkP/RgP1jORooh github.com/flanksource/commons v1.51.4/go.mod h1:XXLA39QGuFUyqIK19W5oDCIZDinLzFyFcGj83ZkyOfo= github.com/flanksource/deps v1.0.28 h1:mm7l7WjzLbkj2aFrgnlMaRizp+j+0x22TvtwXzRlFtE= github.com/flanksource/deps v1.0.28/go.mod h1:2YRfP32WZrMxGVMYV51RlVHZfgerxf8DT3TqSgjzmTQ= -github.com/flanksource/duty v1.0.1301 h1:RfuBlJ5G+NMLR0erw4Y/axAzjM4Rr7YM151tljmWs3s= -github.com/flanksource/duty v1.0.1301/go.mod h1:aH4xdGF3brwBiOKUEFsspgu8U7tBiJOZDXrEqB3OMtc= +github.com/flanksource/duty v1.0.1302-0.20260505080647-4f6025d97824 h1:mtV+NQPUvg1rNx5GftWDfFuMGXqBp6o1j09pPGTakwQ= +github.com/flanksource/duty v1.0.1302-0.20260505080647-4f6025d97824/go.mod h1:aH4xdGF3brwBiOKUEFsspgu8U7tBiJOZDXrEqB3OMtc= github.com/flanksource/gomplate/v3 v3.24.79 h1:T5Ls0tjsnDhcV/dQWjrm2UpHiwOhytDLmYDSF0O6p3Q= github.com/flanksource/gomplate/v3 v3.24.79/go.mod h1:RzIg+YwNQI0eUV61LtqmhNN2Qw8ebm1cGa6IhNQmkWE= github.com/flanksource/is-healthy v1.0.87 h1:wSK9wI9tu//gdKO9JxyZe8ZQ5H7MCpwG17KdbWaiMeM= @@ -818,8 +814,6 @@ github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= -github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/itchyny/gojq v0.12.19 h1:ttXA0XCLEMoaLOz5lSeFOZ6u6Q3QxmG46vfgI4O0DEs= github.com/itchyny/gojq v0.12.19/go.mod h1:5galtVPDywX8SPSOrqjGxkBeDhSxEW1gSxoy7tn1iZY= github.com/itchyny/timefmt-go v0.1.8 h1:1YEo1JvfXeAHKdjelbYr/uCuhkybaHCeTkH8Bo791OI= @@ -932,8 +926,6 @@ github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIi github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= -github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -1240,8 +1232,6 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= -github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -1249,12 +1239,6 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=