From f26b4facb0396f7715e029b7710046182d3731d3 Mon Sep 17 00:00:00 2001 From: zema1 Date: Sat, 25 Mar 2023 21:30:25 +0800 Subject: [PATCH] feat: first version --- .github/assets/dingding.png | Bin 0 -> 1042480 bytes .gitignore | 30 + Dockerfile | 21 + LICENSE | 21 + README.md | 84 ++ ent/client.go | 316 +++++++ ent/ent.go | 616 ++++++++++++++ ent/enttest/enttest.go | 84 ++ ent/generate.go | 4 + ent/hook/hook.go | 199 +++++ ent/migrate/migrate.go | 64 ++ ent/migrate/schema.go | 38 + ent/mutation.go | 914 +++++++++++++++++++++ ent/predicate/predicate.go | 10 + ent/runtime.go | 44 + ent/runtime/runtime.go | 10 + ent/schema/vulninformation.go | 32 + ent/tx.go | 210 +++++ ent/vulninformation.go | 199 +++++ ent/vulninformation/vulninformation.go | 74 ++ ent/vulninformation/where.go | 665 +++++++++++++++ ent/vulninformation_create.go | 1048 ++++++++++++++++++++++++ ent/vulninformation_delete.go | 88 ++ ent/vulninformation_query.go | 526 ++++++++++++ ent/vulninformation_update.go | 548 +++++++++++++ go.mod | 63 ++ go.sum | 190 +++++ grab/avd.go | 258 ++++++ grab/grab.go | 87 ++ grab/oscs.go | 258 ++++++ grab/ti.go | 185 +++++ main.go | 329 ++++++++ push/dingding.go | 51 ++ 33 files changed, 7266 insertions(+) create mode 100644 .github/assets/dingding.png create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 ent/client.go create mode 100644 ent/ent.go create mode 100644 ent/enttest/enttest.go create mode 100644 ent/generate.go create mode 100644 ent/hook/hook.go create mode 100644 ent/migrate/migrate.go create mode 100644 ent/migrate/schema.go create mode 100644 ent/mutation.go create mode 100644 ent/predicate/predicate.go create mode 100644 ent/runtime.go create mode 100644 ent/runtime/runtime.go create mode 100644 ent/schema/vulninformation.go create mode 100644 ent/tx.go create mode 100644 ent/vulninformation.go create mode 100644 ent/vulninformation/vulninformation.go create mode 100644 ent/vulninformation/where.go create mode 100644 ent/vulninformation_create.go create mode 100644 ent/vulninformation_delete.go create mode 100644 ent/vulninformation_query.go create mode 100644 ent/vulninformation_update.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 grab/avd.go create mode 100644 grab/grab.go create mode 100644 grab/oscs.go create mode 100644 grab/ti.go create mode 100644 main.go create mode 100644 push/dingding.go diff --git a/.github/assets/dingding.png b/.github/assets/dingding.png new file mode 100644 index 0000000000000000000000000000000000000000..c8c3f5f355d09c602de17ce2e7ef1afc7767ec99 GIT binary patch literal 1042480 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 literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b540c52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +.idea/ +.DS_Store + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +release/ + +# Test binary, built with `go test -c` +*.test +_test +_testmain.go + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +vendor/ + +*.env +config.yml + +*.sqlite3 +*.sqlite3-journal + +data/ +*backup/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ac2f5d2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM golang:1.19-alpine as builder + +LABEL Author=Koalr(https://github.com/zema1) + +WORKDIR /app + +ENV GO111MODULE=on \ + GOPROXY=https://goproxy.cn,direct + +COPY . . +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -extldflags=-static" -o main . + + +FROM alpine:3 + +WORKDIR /app + +COPY --from=builder /app/main /app/main + +ENV DINGDING_ACCESS_TOKEN="" DINGDING_SECRET="" INTERVAL=30m +ENTRYPOINT ["/app/main"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..12d7940 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 koalr + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c976a51 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# WatchVuln 高价值漏洞采集与推送 + +众所周知,CVE 漏洞库中 99% 以上的漏洞只是无现实意义的编号。我想集中精力看下当下需要关注的高价值漏洞有哪些,而不是被各类 RSS +和公众号的 ~~威胁情报~~ 淹没。 于是这个小项目来抓取部分高质量的漏洞信息源然后做推送。 `WatchVuln`意为**监测**漏洞更新,同时也表示这些漏洞需要**注意** +一下。 + +当前抓取了这几个站点的数据: + +| 名称 | 地址 | 推送策略 | +|--------------|---------------------------------------|--------------------------------------------------| +| 阿里云漏洞库 | https://avd.aliyun.com/high-risk/list | 等级为高危或严重 | +| OSCS开源安全情报预警 | https://www.oscs1024.com/cm | 等级为高危严重**或者**包含 `预警` 标签 | +| 奇安信威胁情报中心 | https://ti.qianxin.com/vulnerability | 等级为高危严重**并且**包含 `奇安信CERT验证` `POC公开` `技术细节公布`标签之一 | + +> 所有站点采用的都是公开接口,且抓取策略很柔和,无恶意。如果有侵权,请提交 issue, 我会删除相关源。 + +当有漏洞更新时,会受到一条推送消息: + +![dingding](./.github/assets/dingding.png) + +## 快速使用 + +当前仅支持钉钉机器人推送,使用之前需要有一个钉钉机器人,安全设置使用加签模式,获得这两个密钥一会要用 + +- `DINGDING_ACCESS_TOKEN`: 取钉钉 webhook 的 `access_token` 部分, 形如 `260cdz724fay862bce8d3ea71762cxxxxxxxxx` +- `DINGDING_SECRET`: 加签的那个值, 形如 `SECxxxxxxxxxxxxxxxxxx` + +### 使用 Docker + +```bash +docker run --restart always -d \ + -e DINGDING_ACCESS_TOKEN=xxxx \ + -e DINGDING_SECRET=xxxx \ + -e INTERVAL=30m \ + zemal/watchvuln:latest +``` + +初次运行会在本地建立全量数据库,大概需要 1~5 分钟,可以使用 `docker logs -f [containerId]` 来查看进度。 +完成后会在群内收到一个消息, 后续将每 30分钟 (30m) 检查一次。这个频率可以通过 `INTERVAL` 这个环境变量来设置,支持的形式如 +`120s` `30m` `1h`,最低 1 分钟 (`60s` 或 `1m`) + +> 特别注意: 为了减少内卷,该工具在凌晨 12:00 到 7:00 间会去 sleep 不会运行! + +### 使用二进制 + +前往 Release 下载对应平台的二进制,然后在命令行执行。 + +```bash +NAME: + watchvuln - A high valuable vulnerability watcher and pusher + +USAGE: + watchvuln [global options] command [command options] [arguments...] + +VERSION: + v0.1.0 + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --debug, -d set log level to debug, print more details (default: false) + --interval value, -i value checking every [interval], supported format like 30s, 30m, 1h (default: "30m") + --dingding-access-token value, --dt value access token of dingding bot + --dingding-sign-secret value, --ds value sign secret of dingding bot + --help, -h show help + --version, -v print the version +``` + +在参数中指定相关 Token 即可 + +``` +$ ./watchvuln --dt DINGDING_ACCESS_TOKEN --ds DINGDING_SECRET +``` + +## 常见问题 + +1. 服务重启后支持增量更新吗 + + 支持,数据会保存在运行目录的 `vuln_vx.sqlite3` 中,这是一个 sqlite3 的数据库,服务重启后将按照一定的策略去增量抓取。 + +2. 如何强制重新创建本地数据库 + + 删除运行目录的 `vuln_vx.sqlite3` 文件再重新运行即可 \ No newline at end of file diff --git a/ent/client.go b/ent/client.go new file mode 100644 index 0000000..efe8394 --- /dev/null +++ b/ent/client.go @@ -0,0 +1,316 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "log" + + "github.com/zema1/watchvuln/ent/migrate" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "github.com/zema1/watchvuln/ent/vulninformation" +) + +// Client is the client that holds all ent builders. +type Client struct { + config + // Schema is the client for creating, migrating and dropping schema. + Schema *migrate.Schema + // VulnInformation is the client for interacting with the VulnInformation builders. + VulnInformation *VulnInformationClient +} + +// NewClient creates a new client configured with the given options. +func NewClient(opts ...Option) *Client { + cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}} + cfg.options(opts...) + client := &Client{config: cfg} + client.init() + return client +} + +func (c *Client) init() { + c.Schema = migrate.NewSchema(c.driver) + c.VulnInformation = NewVulnInformationClient(c.config) +} + +type ( + // config is the configuration for the client and its builder. + config struct { + // driver used for executing database requests. + driver dialect.Driver + // debug enable a debug logging. + debug bool + // log used for logging on debug mode. + log func(...any) + // hooks to execute on mutations. + hooks *hooks + // interceptors to execute on queries. + inters *inters + } + // Option function to configure the client. + Option func(*config) +) + +// options applies the options on the config object. +func (c *config) options(opts ...Option) { + for _, opt := range opts { + opt(c) + } + if c.debug { + c.driver = dialect.Debug(c.driver, c.log) + } +} + +// Debug enables debug logging on the ent.Driver. +func Debug() Option { + return func(c *config) { + c.debug = true + } +} + +// Log sets the logging function for debug mode. +func Log(fn func(...any)) Option { + return func(c *config) { + c.log = fn + } +} + +// Driver configures the client driver. +func Driver(driver dialect.Driver) Option { + return func(c *config) { + c.driver = driver + } +} + +// Open opens a database/sql.DB specified by the driver name and +// the data source name, and returns a new client attached to it. +// Optional parameters can be added for configuring the client. +func Open(driverName, dataSourceName string, options ...Option) (*Client, error) { + switch driverName { + case dialect.MySQL, dialect.Postgres, dialect.SQLite: + drv, err := sql.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + return NewClient(append(options, Driver(drv))...), nil + default: + return nil, fmt.Errorf("unsupported driver: %q", driverName) + } +} + +// Tx returns a new transactional client. The provided context +// is used until the transaction is committed or rolled back. +func (c *Client) Tx(ctx context.Context) (*Tx, error) { + if _, ok := c.driver.(*txDriver); ok { + return nil, errors.New("ent: cannot start a transaction within a transaction") + } + tx, err := newTx(ctx, c.driver) + if err != nil { + return nil, fmt.Errorf("ent: starting a transaction: %w", err) + } + cfg := c.config + cfg.driver = tx + return &Tx{ + ctx: ctx, + config: cfg, + VulnInformation: NewVulnInformationClient(cfg), + }, nil +} + +// BeginTx returns a transactional client with specified options. +func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + if _, ok := c.driver.(*txDriver); ok { + return nil, errors.New("ent: cannot start a transaction within a transaction") + } + tx, err := c.driver.(interface { + BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error) + }).BeginTx(ctx, opts) + if err != nil { + return nil, fmt.Errorf("ent: starting a transaction: %w", err) + } + cfg := c.config + cfg.driver = &txDriver{tx: tx, drv: c.driver} + return &Tx{ + ctx: ctx, + config: cfg, + VulnInformation: NewVulnInformationClient(cfg), + }, nil +} + +// Debug returns a new debug-client. It's used to get verbose logging on specific operations. +// +// client.Debug(). +// VulnInformation. +// Query(). +// Count(ctx) +func (c *Client) Debug() *Client { + if c.debug { + return c + } + cfg := c.config + cfg.driver = dialect.Debug(c.driver, c.log) + client := &Client{config: cfg} + client.init() + return client +} + +// Close closes the database connection and prevents new queries from starting. +func (c *Client) Close() error { + return c.driver.Close() +} + +// Use adds the mutation hooks to all the entity clients. +// In order to add hooks to a specific client, call: `client.Node.Use(...)`. +func (c *Client) Use(hooks ...Hook) { + c.VulnInformation.Use(hooks...) +} + +// Intercept adds the query interceptors to all the entity clients. +// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. +func (c *Client) Intercept(interceptors ...Interceptor) { + c.VulnInformation.Intercept(interceptors...) +} + +// Mutate implements the ent.Mutator interface. +func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { + switch m := m.(type) { + case *VulnInformationMutation: + return c.VulnInformation.mutate(ctx, m) + default: + return nil, fmt.Errorf("ent: unknown mutation type %T", m) + } +} + +// VulnInformationClient is a client for the VulnInformation schema. +type VulnInformationClient struct { + config +} + +// NewVulnInformationClient returns a client for the VulnInformation from the given config. +func NewVulnInformationClient(c config) *VulnInformationClient { + return &VulnInformationClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `vulninformation.Hooks(f(g(h())))`. +func (c *VulnInformationClient) Use(hooks ...Hook) { + c.hooks.VulnInformation = append(c.hooks.VulnInformation, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `vulninformation.Intercept(f(g(h())))`. +func (c *VulnInformationClient) Intercept(interceptors ...Interceptor) { + c.inters.VulnInformation = append(c.inters.VulnInformation, interceptors...) +} + +// Create returns a builder for creating a VulnInformation entity. +func (c *VulnInformationClient) Create() *VulnInformationCreate { + mutation := newVulnInformationMutation(c.config, OpCreate) + return &VulnInformationCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of VulnInformation entities. +func (c *VulnInformationClient) CreateBulk(builders ...*VulnInformationCreate) *VulnInformationCreateBulk { + return &VulnInformationCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for VulnInformation. +func (c *VulnInformationClient) Update() *VulnInformationUpdate { + mutation := newVulnInformationMutation(c.config, OpUpdate) + return &VulnInformationUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *VulnInformationClient) UpdateOne(vi *VulnInformation) *VulnInformationUpdateOne { + mutation := newVulnInformationMutation(c.config, OpUpdateOne, withVulnInformation(vi)) + return &VulnInformationUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *VulnInformationClient) UpdateOneID(id int) *VulnInformationUpdateOne { + mutation := newVulnInformationMutation(c.config, OpUpdateOne, withVulnInformationID(id)) + return &VulnInformationUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for VulnInformation. +func (c *VulnInformationClient) Delete() *VulnInformationDelete { + mutation := newVulnInformationMutation(c.config, OpDelete) + return &VulnInformationDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *VulnInformationClient) DeleteOne(vi *VulnInformation) *VulnInformationDeleteOne { + return c.DeleteOneID(vi.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *VulnInformationClient) DeleteOneID(id int) *VulnInformationDeleteOne { + builder := c.Delete().Where(vulninformation.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &VulnInformationDeleteOne{builder} +} + +// Query returns a query builder for VulnInformation. +func (c *VulnInformationClient) Query() *VulnInformationQuery { + return &VulnInformationQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeVulnInformation}, + inters: c.Interceptors(), + } +} + +// Get returns a VulnInformation entity by its id. +func (c *VulnInformationClient) Get(ctx context.Context, id int) (*VulnInformation, error) { + return c.Query().Where(vulninformation.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *VulnInformationClient) GetX(ctx context.Context, id int) *VulnInformation { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *VulnInformationClient) Hooks() []Hook { + return c.hooks.VulnInformation +} + +// Interceptors returns the client interceptors. +func (c *VulnInformationClient) Interceptors() []Interceptor { + return c.inters.VulnInformation +} + +func (c *VulnInformationClient) mutate(ctx context.Context, m *VulnInformationMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&VulnInformationCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&VulnInformationUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&VulnInformationUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&VulnInformationDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown VulnInformation mutation op: %q", m.Op()) + } +} + +// hooks and interceptors per client, for fast access. +type ( + hooks struct { + VulnInformation []ent.Hook + } + inters struct { + VulnInformation []ent.Interceptor + } +) diff --git a/ent/ent.go b/ent/ent.go new file mode 100644 index 0000000..9fe98b7 --- /dev/null +++ b/ent/ent.go @@ -0,0 +1,616 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "reflect" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/zema1/watchvuln/ent/vulninformation" +) + +// ent aliases to avoid import conflicts in user's code. +type ( + Op = ent.Op + Hook = ent.Hook + Value = ent.Value + Query = ent.Query + QueryContext = ent.QueryContext + Querier = ent.Querier + QuerierFunc = ent.QuerierFunc + Interceptor = ent.Interceptor + InterceptFunc = ent.InterceptFunc + Traverser = ent.Traverser + TraverseFunc = ent.TraverseFunc + Policy = ent.Policy + Mutator = ent.Mutator + Mutation = ent.Mutation + MutateFunc = ent.MutateFunc +) + +type clientCtxKey struct{} + +// FromContext returns a Client stored inside a context, or nil if there isn't one. +func FromContext(ctx context.Context) *Client { + c, _ := ctx.Value(clientCtxKey{}).(*Client) + return c +} + +// NewContext returns a new context with the given Client attached. +func NewContext(parent context.Context, c *Client) context.Context { + return context.WithValue(parent, clientCtxKey{}, c) +} + +type txCtxKey struct{} + +// TxFromContext returns a Tx stored inside a context, or nil if there isn't one. +func TxFromContext(ctx context.Context) *Tx { + tx, _ := ctx.Value(txCtxKey{}).(*Tx) + return tx +} + +// NewTxContext returns a new context with the given Tx attached. +func NewTxContext(parent context.Context, tx *Tx) context.Context { + return context.WithValue(parent, txCtxKey{}, tx) +} + +// OrderFunc applies an ordering on the sql selector. +type OrderFunc func(*sql.Selector) + +// columnChecker returns a function indicates if the column exists in the given column. +func columnChecker(table string) func(string) error { + checks := map[string]func(string) bool{ + vulninformation.Table: vulninformation.ValidColumn, + } + check, ok := checks[table] + if !ok { + return func(string) error { + return fmt.Errorf("unknown table %q", table) + } + } + return func(column string) error { + if !check(column) { + return fmt.Errorf("unknown column %q for table %q", column, table) + } + return nil + } +} + +// Asc applies the given fields in ASC order. +func Asc(fields ...string) OrderFunc { + return func(s *sql.Selector) { + check := columnChecker(s.TableName()) + for _, f := range fields { + if err := check(f); err != nil { + s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) + } + s.OrderBy(sql.Asc(s.C(f))) + } + } +} + +// Desc applies the given fields in DESC order. +func Desc(fields ...string) OrderFunc { + return func(s *sql.Selector) { + check := columnChecker(s.TableName()) + for _, f := range fields { + if err := check(f); err != nil { + s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) + } + s.OrderBy(sql.Desc(s.C(f))) + } + } +} + +// AggregateFunc applies an aggregation step on the group-by traversal/selector. +type AggregateFunc func(*sql.Selector) string + +// As is a pseudo aggregation function for renaming another other functions with custom names. For example: +// +// GroupBy(field1, field2). +// Aggregate(ent.As(ent.Sum(field1), "sum_field1"), (ent.As(ent.Sum(field2), "sum_field2")). +// Scan(ctx, &v) +func As(fn AggregateFunc, end string) AggregateFunc { + return func(s *sql.Selector) string { + return sql.As(fn(s), end) + } +} + +// Count applies the "count" aggregation function on each group. +func Count() AggregateFunc { + return func(s *sql.Selector) string { + return sql.Count("*") + } +} + +// Max applies the "max" aggregation function on the given field of each group. +func Max(field string) AggregateFunc { + return func(s *sql.Selector) string { + check := columnChecker(s.TableName()) + if err := check(field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Max(s.C(field)) + } +} + +// Mean applies the "mean" aggregation function on the given field of each group. +func Mean(field string) AggregateFunc { + return func(s *sql.Selector) string { + check := columnChecker(s.TableName()) + if err := check(field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Avg(s.C(field)) + } +} + +// Min applies the "min" aggregation function on the given field of each group. +func Min(field string) AggregateFunc { + return func(s *sql.Selector) string { + check := columnChecker(s.TableName()) + if err := check(field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Min(s.C(field)) + } +} + +// Sum applies the "sum" aggregation function on the given field of each group. +func Sum(field string) AggregateFunc { + return func(s *sql.Selector) string { + check := columnChecker(s.TableName()) + if err := check(field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Sum(s.C(field)) + } +} + +// ValidationError returns when validating a field or edge fails. +type ValidationError struct { + Name string // Field or edge name. + err error +} + +// Error implements the error interface. +func (e *ValidationError) Error() string { + return e.err.Error() +} + +// Unwrap implements the errors.Wrapper interface. +func (e *ValidationError) Unwrap() error { + return e.err +} + +// IsValidationError returns a boolean indicating whether the error is a validation error. +func IsValidationError(err error) bool { + if err == nil { + return false + } + var e *ValidationError + return errors.As(err, &e) +} + +// NotFoundError returns when trying to fetch a specific entity and it was not found in the database. +type NotFoundError struct { + label string +} + +// Error implements the error interface. +func (e *NotFoundError) Error() string { + return "ent: " + e.label + " not found" +} + +// IsNotFound returns a boolean indicating whether the error is a not found error. +func IsNotFound(err error) bool { + if err == nil { + return false + } + var e *NotFoundError + return errors.As(err, &e) +} + +// MaskNotFound masks not found error. +func MaskNotFound(err error) error { + if IsNotFound(err) { + return nil + } + return err +} + +// NotSingularError returns when trying to fetch a singular entity and more then one was found in the database. +type NotSingularError struct { + label string +} + +// Error implements the error interface. +func (e *NotSingularError) Error() string { + return "ent: " + e.label + " not singular" +} + +// IsNotSingular returns a boolean indicating whether the error is a not singular error. +func IsNotSingular(err error) bool { + if err == nil { + return false + } + var e *NotSingularError + return errors.As(err, &e) +} + +// NotLoadedError returns when trying to get a node that was not loaded by the query. +type NotLoadedError struct { + edge string +} + +// Error implements the error interface. +func (e *NotLoadedError) Error() string { + return "ent: " + e.edge + " edge was not loaded" +} + +// IsNotLoaded returns a boolean indicating whether the error is a not loaded error. +func IsNotLoaded(err error) bool { + if err == nil { + return false + } + var e *NotLoadedError + return errors.As(err, &e) +} + +// ConstraintError returns when trying to create/update one or more entities and +// one or more of their constraints failed. For example, violation of edge or +// field uniqueness. +type ConstraintError struct { + msg string + wrap error +} + +// Error implements the error interface. +func (e ConstraintError) Error() string { + return "ent: constraint failed: " + e.msg +} + +// Unwrap implements the errors.Wrapper interface. +func (e *ConstraintError) Unwrap() error { + return e.wrap +} + +// IsConstraintError returns a boolean indicating whether the error is a constraint failure. +func IsConstraintError(err error) bool { + if err == nil { + return false + } + var e *ConstraintError + return errors.As(err, &e) +} + +// selector embedded by the different Select/GroupBy builders. +type selector struct { + label string + flds *[]string + fns []AggregateFunc + scan func(context.Context, any) error +} + +// ScanX is like Scan, but panics if an error occurs. +func (s *selector) ScanX(ctx context.Context, v any) { + if err := s.scan(ctx, v); err != nil { + panic(err) + } +} + +// Strings returns list of strings from a selector. It is only allowed when selecting one field. +func (s *selector) Strings(ctx context.Context) ([]string, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Strings is not achievable when selecting more than 1 field") + } + var v []string + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// StringsX is like Strings, but panics if an error occurs. +func (s *selector) StringsX(ctx context.Context) []string { + v, err := s.Strings(ctx) + if err != nil { + panic(err) + } + return v +} + +// String returns a single string from a selector. It is only allowed when selecting one field. +func (s *selector) String(ctx context.Context) (_ string, err error) { + var v []string + if v, err = s.Strings(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Strings returned %d results when one was expected", len(v)) + } + return +} + +// StringX is like String, but panics if an error occurs. +func (s *selector) StringX(ctx context.Context) string { + v, err := s.String(ctx) + if err != nil { + panic(err) + } + return v +} + +// Ints returns list of ints from a selector. It is only allowed when selecting one field. +func (s *selector) Ints(ctx context.Context) ([]int, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Ints is not achievable when selecting more than 1 field") + } + var v []int + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// IntsX is like Ints, but panics if an error occurs. +func (s *selector) IntsX(ctx context.Context) []int { + v, err := s.Ints(ctx) + if err != nil { + panic(err) + } + return v +} + +// Int returns a single int from a selector. It is only allowed when selecting one field. +func (s *selector) Int(ctx context.Context) (_ int, err error) { + var v []int + if v, err = s.Ints(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Ints returned %d results when one was expected", len(v)) + } + return +} + +// IntX is like Int, but panics if an error occurs. +func (s *selector) IntX(ctx context.Context) int { + v, err := s.Int(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64s returns list of float64s from a selector. It is only allowed when selecting one field. +func (s *selector) Float64s(ctx context.Context) ([]float64, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Float64s is not achievable when selecting more than 1 field") + } + var v []float64 + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// Float64sX is like Float64s, but panics if an error occurs. +func (s *selector) Float64sX(ctx context.Context) []float64 { + v, err := s.Float64s(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64 returns a single float64 from a selector. It is only allowed when selecting one field. +func (s *selector) Float64(ctx context.Context) (_ float64, err error) { + var v []float64 + if v, err = s.Float64s(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Float64s returned %d results when one was expected", len(v)) + } + return +} + +// Float64X is like Float64, but panics if an error occurs. +func (s *selector) Float64X(ctx context.Context) float64 { + v, err := s.Float64(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bools returns list of bools from a selector. It is only allowed when selecting one field. +func (s *selector) Bools(ctx context.Context) ([]bool, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Bools is not achievable when selecting more than 1 field") + } + var v []bool + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// BoolsX is like Bools, but panics if an error occurs. +func (s *selector) BoolsX(ctx context.Context) []bool { + v, err := s.Bools(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bool returns a single bool from a selector. It is only allowed when selecting one field. +func (s *selector) Bool(ctx context.Context) (_ bool, err error) { + var v []bool + if v, err = s.Bools(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Bools returned %d results when one was expected", len(v)) + } + return +} + +// BoolX is like Bool, but panics if an error occurs. +func (s *selector) BoolX(ctx context.Context) bool { + v, err := s.Bool(ctx) + if err != nil { + panic(err) + } + return v +} + +// withHooks invokes the builder operation with the given hooks, if any. +func withHooks[V Value, M any, PM interface { + *M + Mutation +}](ctx context.Context, exec func(context.Context) (V, error), mutation PM, hooks []Hook) (value V, err error) { + if len(hooks) == 0 { + return exec(ctx) + } + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutationT, ok := any(m).(PM) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + // Set the mutation to the builder. + *mutation = *mutationT + return exec(ctx) + }) + for i := len(hooks) - 1; i >= 0; i-- { + if hooks[i] == nil { + return value, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = hooks[i](mut) + } + v, err := mut.Mutate(ctx, mutation) + if err != nil { + return value, err + } + nv, ok := v.(V) + if !ok { + return value, fmt.Errorf("unexpected node type %T returned from %T", v, mutation) + } + return nv, nil +} + +// setContextOp returns a new context with the given QueryContext attached (including its op) in case it does not exist. +func setContextOp(ctx context.Context, qc *QueryContext, op string) context.Context { + if ent.QueryFromContext(ctx) == nil { + qc.Op = op + ctx = ent.NewQueryContext(ctx, qc) + } + return ctx +} + +func querierAll[V Value, Q interface { + sqlAll(context.Context, ...queryHook) (V, error) +}]() Querier { + return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + return query.sqlAll(ctx) + }) +} + +func querierCount[Q interface { + sqlCount(context.Context) (int, error) +}]() Querier { + return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + return query.sqlCount(ctx) + }) +} + +func withInterceptors[V Value](ctx context.Context, q Query, qr Querier, inters []Interceptor) (v V, err error) { + for i := len(inters) - 1; i >= 0; i-- { + qr = inters[i].Intercept(qr) + } + rv, err := qr.Query(ctx, q) + if err != nil { + return v, err + } + vt, ok := rv.(V) + if !ok { + return v, fmt.Errorf("unexpected type %T returned from %T. expected type: %T", vt, q, v) + } + return vt, nil +} + +func scanWithInterceptors[Q1 ent.Query, Q2 interface { + sqlScan(context.Context, Q1, any) error +}](ctx context.Context, rootQuery Q1, selectOrGroup Q2, inters []Interceptor, v any) error { + rv := reflect.ValueOf(v) + var qr Querier = QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q1) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + if err := selectOrGroup.sqlScan(ctx, query, v); err != nil { + return nil, err + } + if k := rv.Kind(); k == reflect.Pointer && rv.Elem().CanInterface() { + return rv.Elem().Interface(), nil + } + return v, nil + }) + for i := len(inters) - 1; i >= 0; i-- { + qr = inters[i].Intercept(qr) + } + vv, err := qr.Query(ctx, rootQuery) + if err != nil { + return err + } + switch rv2 := reflect.ValueOf(vv); { + case rv.IsNil(), rv2.IsNil(), rv.Kind() != reflect.Pointer: + case rv.Type() == rv2.Type(): + rv.Elem().Set(rv2.Elem()) + case rv.Elem().Type() == rv2.Type(): + rv.Elem().Set(rv2) + } + return nil +} + +// queryHook describes an internal hook for the different sqlAll methods. +type queryHook func(context.Context, *sqlgraph.QuerySpec) diff --git a/ent/enttest/enttest.go b/ent/enttest/enttest.go new file mode 100644 index 0000000..67b2783 --- /dev/null +++ b/ent/enttest/enttest.go @@ -0,0 +1,84 @@ +// Code generated by ent, DO NOT EDIT. + +package enttest + +import ( + "context" + + "github.com/zema1/watchvuln/ent" + // required by schema hooks. + _ "github.com/zema1/watchvuln/ent/runtime" + + "entgo.io/ent/dialect/sql/schema" + "github.com/zema1/watchvuln/ent/migrate" +) + +type ( + // TestingT is the interface that is shared between + // testing.T and testing.B and used by enttest. + TestingT interface { + FailNow() + Error(...any) + } + + // Option configures client creation. + Option func(*options) + + options struct { + opts []ent.Option + migrateOpts []schema.MigrateOption + } +) + +// WithOptions forwards options to client creation. +func WithOptions(opts ...ent.Option) Option { + return func(o *options) { + o.opts = append(o.opts, opts...) + } +} + +// WithMigrateOptions forwards options to auto migration. +func WithMigrateOptions(opts ...schema.MigrateOption) Option { + return func(o *options) { + o.migrateOpts = append(o.migrateOpts, opts...) + } +} + +func newOptions(opts []Option) *options { + o := &options{} + for _, opt := range opts { + opt(o) + } + return o +} + +// Open calls ent.Open and auto-run migration. +func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Client { + o := newOptions(opts) + c, err := ent.Open(driverName, dataSourceName, o.opts...) + if err != nil { + t.Error(err) + t.FailNow() + } + migrateSchema(t, c, o) + return c +} + +// NewClient calls ent.NewClient and auto-run migration. +func NewClient(t TestingT, opts ...Option) *ent.Client { + o := newOptions(opts) + c := ent.NewClient(o.opts...) + migrateSchema(t, c, o) + return c +} +func migrateSchema(t TestingT, c *ent.Client, o *options) { + tables, err := schema.CopyTables(migrate.Tables) + if err != nil { + t.Error(err) + t.FailNow() + } + if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil { + t.Error(err) + t.FailNow() + } +} diff --git a/ent/generate.go b/ent/generate.go new file mode 100644 index 0000000..172acaf --- /dev/null +++ b/ent/generate.go @@ -0,0 +1,4 @@ +package ent + +//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/upsert ./schema +//go:generate echo "generate ent succeed" diff --git a/ent/hook/hook.go b/ent/hook/hook.go new file mode 100644 index 0000000..5b6e466 --- /dev/null +++ b/ent/hook/hook.go @@ -0,0 +1,199 @@ +// Code generated by ent, DO NOT EDIT. + +package hook + +import ( + "context" + "fmt" + + "github.com/zema1/watchvuln/ent" +) + +// The VulnInformationFunc type is an adapter to allow the use of ordinary +// function as VulnInformation mutator. +type VulnInformationFunc func(context.Context, *ent.VulnInformationMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f VulnInformationFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.VulnInformationMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.VulnInformationMutation", m) +} + +// Condition is a hook condition function. +type Condition func(context.Context, ent.Mutation) bool + +// And groups conditions with the AND operator. +func And(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + if !first(ctx, m) || !second(ctx, m) { + return false + } + for _, cond := range rest { + if !cond(ctx, m) { + return false + } + } + return true + } +} + +// Or groups conditions with the OR operator. +func Or(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + if first(ctx, m) || second(ctx, m) { + return true + } + for _, cond := range rest { + if cond(ctx, m) { + return true + } + } + return false + } +} + +// Not negates a given condition. +func Not(cond Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + return !cond(ctx, m) + } +} + +// HasOp is a condition testing mutation operation. +func HasOp(op ent.Op) Condition { + return func(_ context.Context, m ent.Mutation) bool { + return m.Op().Is(op) + } +} + +// HasAddedFields is a condition validating `.AddedField` on fields. +func HasAddedFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if _, exists := m.AddedField(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.AddedField(field); !exists { + return false + } + } + return true + } +} + +// HasClearedFields is a condition validating `.FieldCleared` on fields. +func HasClearedFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if exists := m.FieldCleared(field); !exists { + return false + } + for _, field := range fields { + if exists := m.FieldCleared(field); !exists { + return false + } + } + return true + } +} + +// HasFields is a condition validating `.Field` on fields. +func HasFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if _, exists := m.Field(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.Field(field); !exists { + return false + } + } + return true + } +} + +// If executes the given hook under condition. +// +// hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) +func If(hk ent.Hook, cond Condition) ent.Hook { + return func(next ent.Mutator) ent.Mutator { + return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if cond(ctx, m) { + return hk(next).Mutate(ctx, m) + } + return next.Mutate(ctx, m) + }) + } +} + +// On executes the given hook only for the given operation. +// +// hook.On(Log, ent.Delete|ent.Create) +func On(hk ent.Hook, op ent.Op) ent.Hook { + return If(hk, HasOp(op)) +} + +// Unless skips the given hook only for the given operation. +// +// hook.Unless(Log, ent.Update|ent.UpdateOne) +func Unless(hk ent.Hook, op ent.Op) ent.Hook { + return If(hk, Not(HasOp(op))) +} + +// FixedError is a hook returning a fixed error. +func FixedError(err error) ent.Hook { + return func(ent.Mutator) ent.Mutator { + return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) { + return nil, err + }) + } +} + +// Reject returns a hook that rejects all operations that match op. +// +// func (T) Hooks() []ent.Hook { +// return []ent.Hook{ +// Reject(ent.Delete|ent.Update), +// } +// } +func Reject(op ent.Op) ent.Hook { + hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) + return On(hk, op) +} + +// Chain acts as a list of hooks and is effectively immutable. +// Once created, it will always hold the same set of hooks in the same order. +type Chain struct { + hooks []ent.Hook +} + +// NewChain creates a new chain of hooks. +func NewChain(hooks ...ent.Hook) Chain { + return Chain{append([]ent.Hook(nil), hooks...)} +} + +// Hook chains the list of hooks and returns the final hook. +func (c Chain) Hook() ent.Hook { + return func(mutator ent.Mutator) ent.Mutator { + for i := len(c.hooks) - 1; i >= 0; i-- { + mutator = c.hooks[i](mutator) + } + return mutator + } +} + +// Append extends a chain, adding the specified hook +// as the last ones in the mutation flow. +func (c Chain) Append(hooks ...ent.Hook) Chain { + newHooks := make([]ent.Hook, 0, len(c.hooks)+len(hooks)) + newHooks = append(newHooks, c.hooks...) + newHooks = append(newHooks, hooks...) + return Chain{newHooks} +} + +// Extend extends a chain, adding the specified chain +// as the last ones in the mutation flow. +func (c Chain) Extend(chain Chain) Chain { + return c.Append(chain.hooks...) +} diff --git a/ent/migrate/migrate.go b/ent/migrate/migrate.go new file mode 100644 index 0000000..1956a6b --- /dev/null +++ b/ent/migrate/migrate.go @@ -0,0 +1,64 @@ +// Code generated by ent, DO NOT EDIT. + +package migrate + +import ( + "context" + "fmt" + "io" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql/schema" +) + +var ( + // WithGlobalUniqueID sets the universal ids options to the migration. + // If this option is enabled, ent migration will allocate a 1<<32 range + // for the ids of each entity (table). + // Note that this option cannot be applied on tables that already exist. + WithGlobalUniqueID = schema.WithGlobalUniqueID + // WithDropColumn sets the drop column option to the migration. + // If this option is enabled, ent migration will drop old columns + // that were used for both fields and edges. This defaults to false. + WithDropColumn = schema.WithDropColumn + // WithDropIndex sets the drop index option to the migration. + // If this option is enabled, ent migration will drop old indexes + // that were defined in the schema. This defaults to false. + // Note that unique constraints are defined using `UNIQUE INDEX`, + // and therefore, it's recommended to enable this option to get more + // flexibility in the schema changes. + WithDropIndex = schema.WithDropIndex + // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. + WithForeignKeys = schema.WithForeignKeys +) + +// Schema is the API for creating, migrating and dropping a schema. +type Schema struct { + drv dialect.Driver +} + +// NewSchema creates a new schema client. +func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } + +// Create creates all schema resources. +func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { + return Create(ctx, s, Tables, opts...) +} + +// Create creates all table resources using the given schema driver. +func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error { + migrate, err := schema.NewMigrate(s.drv, opts...) + if err != nil { + return fmt.Errorf("ent/migrate: %w", err) + } + return migrate.Create(ctx, tables...) +} + +// WriteTo writes the schema changes to w instead of running them against the database. +// +// if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { +// log.Fatal(err) +// } +func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { + return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...) +} diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go new file mode 100644 index 0000000..4e57ab4 --- /dev/null +++ b/ent/migrate/schema.go @@ -0,0 +1,38 @@ +// Code generated by ent, DO NOT EDIT. + +package migrate + +import ( + "entgo.io/ent/dialect/sql/schema" + "entgo.io/ent/schema/field" +) + +var ( + // VulnInformationsColumns holds the columns for the "vuln_informations" table. + VulnInformationsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "key", Type: field.TypeString, Unique: true}, + {Name: "title", Type: field.TypeString, Default: ""}, + {Name: "description", Type: field.TypeString, Default: ""}, + {Name: "severity", Type: field.TypeString, Default: ""}, + {Name: "cve", Type: field.TypeString, Default: ""}, + {Name: "disclosure", Type: field.TypeString, Default: ""}, + {Name: "solutions", Type: field.TypeString, Default: ""}, + {Name: "references", Type: field.TypeJSON, Nullable: true}, + {Name: "tags", Type: field.TypeJSON, Nullable: true}, + {Name: "from", Type: field.TypeString, Default: ""}, + } + // VulnInformationsTable holds the schema information for the "vuln_informations" table. + VulnInformationsTable = &schema.Table{ + Name: "vuln_informations", + Columns: VulnInformationsColumns, + PrimaryKey: []*schema.Column{VulnInformationsColumns[0]}, + } + // Tables holds all the tables in the schema. + Tables = []*schema.Table{ + VulnInformationsTable, + } +) + +func init() { +} diff --git a/ent/mutation.go b/ent/mutation.go new file mode 100644 index 0000000..e138bd6 --- /dev/null +++ b/ent/mutation.go @@ -0,0 +1,914 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "sync" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/zema1/watchvuln/ent/predicate" + "github.com/zema1/watchvuln/ent/vulninformation" +) + +const ( + // Operation types. + OpCreate = ent.OpCreate + OpDelete = ent.OpDelete + OpDeleteOne = ent.OpDeleteOne + OpUpdate = ent.OpUpdate + OpUpdateOne = ent.OpUpdateOne + + // Node types. + TypeVulnInformation = "VulnInformation" +) + +// VulnInformationMutation represents an operation that mutates the VulnInformation nodes in the graph. +type VulnInformationMutation struct { + config + op Op + typ string + id *int + key *string + title *string + description *string + severity *string + cve *string + disclosure *string + solutions *string + references *[]string + appendreferences []string + tags *[]string + appendtags []string + from *string + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*VulnInformation, error) + predicates []predicate.VulnInformation +} + +var _ ent.Mutation = (*VulnInformationMutation)(nil) + +// vulninformationOption allows management of the mutation configuration using functional options. +type vulninformationOption func(*VulnInformationMutation) + +// newVulnInformationMutation creates new mutation for the VulnInformation entity. +func newVulnInformationMutation(c config, op Op, opts ...vulninformationOption) *VulnInformationMutation { + m := &VulnInformationMutation{ + config: c, + op: op, + typ: TypeVulnInformation, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withVulnInformationID sets the ID field of the mutation. +func withVulnInformationID(id int) vulninformationOption { + return func(m *VulnInformationMutation) { + var ( + err error + once sync.Once + value *VulnInformation + ) + m.oldValue = func(ctx context.Context) (*VulnInformation, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().VulnInformation.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withVulnInformation sets the old VulnInformation of the mutation. +func withVulnInformation(node *VulnInformation) vulninformationOption { + return func(m *VulnInformationMutation) { + m.oldValue = func(context.Context) (*VulnInformation, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m VulnInformationMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m VulnInformationMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *VulnInformationMutation) ID() (id int, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *VulnInformationMutation) IDs(ctx context.Context) ([]int, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().VulnInformation.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetKey sets the "key" field. +func (m *VulnInformationMutation) SetKey(s string) { + m.key = &s +} + +// Key returns the value of the "key" field in the mutation. +func (m *VulnInformationMutation) Key() (r string, exists bool) { + v := m.key + if v == nil { + return + } + return *v, true +} + +// OldKey returns the old "key" field's value of the VulnInformation entity. +// If the VulnInformation object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VulnInformationMutation) OldKey(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldKey is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldKey requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldKey: %w", err) + } + return oldValue.Key, nil +} + +// ResetKey resets all changes to the "key" field. +func (m *VulnInformationMutation) ResetKey() { + m.key = nil +} + +// SetTitle sets the "title" field. +func (m *VulnInformationMutation) SetTitle(s string) { + m.title = &s +} + +// Title returns the value of the "title" field in the mutation. +func (m *VulnInformationMutation) Title() (r string, exists bool) { + v := m.title + if v == nil { + return + } + return *v, true +} + +// OldTitle returns the old "title" field's value of the VulnInformation entity. +// If the VulnInformation object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VulnInformationMutation) OldTitle(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTitle is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTitle requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTitle: %w", err) + } + return oldValue.Title, nil +} + +// ResetTitle resets all changes to the "title" field. +func (m *VulnInformationMutation) ResetTitle() { + m.title = nil +} + +// SetDescription sets the "description" field. +func (m *VulnInformationMutation) SetDescription(s string) { + m.description = &s +} + +// Description returns the value of the "description" field in the mutation. +func (m *VulnInformationMutation) Description() (r string, exists bool) { + v := m.description + if v == nil { + return + } + return *v, true +} + +// OldDescription returns the old "description" field's value of the VulnInformation entity. +// If the VulnInformation object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VulnInformationMutation) OldDescription(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldDescription is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldDescription requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldDescription: %w", err) + } + return oldValue.Description, nil +} + +// ResetDescription resets all changes to the "description" field. +func (m *VulnInformationMutation) ResetDescription() { + m.description = nil +} + +// SetSeverity sets the "severity" field. +func (m *VulnInformationMutation) SetSeverity(s string) { + m.severity = &s +} + +// Severity returns the value of the "severity" field in the mutation. +func (m *VulnInformationMutation) Severity() (r string, exists bool) { + v := m.severity + if v == nil { + return + } + return *v, true +} + +// OldSeverity returns the old "severity" field's value of the VulnInformation entity. +// If the VulnInformation object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VulnInformationMutation) OldSeverity(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSeverity is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSeverity requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSeverity: %w", err) + } + return oldValue.Severity, nil +} + +// ResetSeverity resets all changes to the "severity" field. +func (m *VulnInformationMutation) ResetSeverity() { + m.severity = nil +} + +// SetCve sets the "cve" field. +func (m *VulnInformationMutation) SetCve(s string) { + m.cve = &s +} + +// Cve returns the value of the "cve" field in the mutation. +func (m *VulnInformationMutation) Cve() (r string, exists bool) { + v := m.cve + if v == nil { + return + } + return *v, true +} + +// OldCve returns the old "cve" field's value of the VulnInformation entity. +// If the VulnInformation object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VulnInformationMutation) OldCve(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCve is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCve requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCve: %w", err) + } + return oldValue.Cve, nil +} + +// ResetCve resets all changes to the "cve" field. +func (m *VulnInformationMutation) ResetCve() { + m.cve = nil +} + +// SetDisclosure sets the "disclosure" field. +func (m *VulnInformationMutation) SetDisclosure(s string) { + m.disclosure = &s +} + +// Disclosure returns the value of the "disclosure" field in the mutation. +func (m *VulnInformationMutation) Disclosure() (r string, exists bool) { + v := m.disclosure + if v == nil { + return + } + return *v, true +} + +// OldDisclosure returns the old "disclosure" field's value of the VulnInformation entity. +// If the VulnInformation object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VulnInformationMutation) OldDisclosure(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldDisclosure is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldDisclosure requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldDisclosure: %w", err) + } + return oldValue.Disclosure, nil +} + +// ResetDisclosure resets all changes to the "disclosure" field. +func (m *VulnInformationMutation) ResetDisclosure() { + m.disclosure = nil +} + +// SetSolutions sets the "solutions" field. +func (m *VulnInformationMutation) SetSolutions(s string) { + m.solutions = &s +} + +// Solutions returns the value of the "solutions" field in the mutation. +func (m *VulnInformationMutation) Solutions() (r string, exists bool) { + v := m.solutions + if v == nil { + return + } + return *v, true +} + +// OldSolutions returns the old "solutions" field's value of the VulnInformation entity. +// If the VulnInformation object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VulnInformationMutation) OldSolutions(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSolutions is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSolutions requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSolutions: %w", err) + } + return oldValue.Solutions, nil +} + +// ResetSolutions resets all changes to the "solutions" field. +func (m *VulnInformationMutation) ResetSolutions() { + m.solutions = nil +} + +// SetReferences sets the "references" field. +func (m *VulnInformationMutation) SetReferences(s []string) { + m.references = &s + m.appendreferences = nil +} + +// References returns the value of the "references" field in the mutation. +func (m *VulnInformationMutation) References() (r []string, exists bool) { + v := m.references + if v == nil { + return + } + return *v, true +} + +// OldReferences returns the old "references" field's value of the VulnInformation entity. +// If the VulnInformation object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VulnInformationMutation) OldReferences(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReferences is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReferences requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReferences: %w", err) + } + return oldValue.References, nil +} + +// AppendReferences adds s to the "references" field. +func (m *VulnInformationMutation) AppendReferences(s []string) { + m.appendreferences = append(m.appendreferences, s...) +} + +// AppendedReferences returns the list of values that were appended to the "references" field in this mutation. +func (m *VulnInformationMutation) AppendedReferences() ([]string, bool) { + if len(m.appendreferences) == 0 { + return nil, false + } + return m.appendreferences, true +} + +// ClearReferences clears the value of the "references" field. +func (m *VulnInformationMutation) ClearReferences() { + m.references = nil + m.appendreferences = nil + m.clearedFields[vulninformation.FieldReferences] = struct{}{} +} + +// ReferencesCleared returns if the "references" field was cleared in this mutation. +func (m *VulnInformationMutation) ReferencesCleared() bool { + _, ok := m.clearedFields[vulninformation.FieldReferences] + return ok +} + +// ResetReferences resets all changes to the "references" field. +func (m *VulnInformationMutation) ResetReferences() { + m.references = nil + m.appendreferences = nil + delete(m.clearedFields, vulninformation.FieldReferences) +} + +// SetTags sets the "tags" field. +func (m *VulnInformationMutation) SetTags(s []string) { + m.tags = &s + m.appendtags = nil +} + +// Tags returns the value of the "tags" field in the mutation. +func (m *VulnInformationMutation) Tags() (r []string, exists bool) { + v := m.tags + if v == nil { + return + } + return *v, true +} + +// OldTags returns the old "tags" field's value of the VulnInformation entity. +// If the VulnInformation object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VulnInformationMutation) OldTags(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTags is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTags requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTags: %w", err) + } + return oldValue.Tags, nil +} + +// AppendTags adds s to the "tags" field. +func (m *VulnInformationMutation) AppendTags(s []string) { + m.appendtags = append(m.appendtags, s...) +} + +// AppendedTags returns the list of values that were appended to the "tags" field in this mutation. +func (m *VulnInformationMutation) AppendedTags() ([]string, bool) { + if len(m.appendtags) == 0 { + return nil, false + } + return m.appendtags, true +} + +// ClearTags clears the value of the "tags" field. +func (m *VulnInformationMutation) ClearTags() { + m.tags = nil + m.appendtags = nil + m.clearedFields[vulninformation.FieldTags] = struct{}{} +} + +// TagsCleared returns if the "tags" field was cleared in this mutation. +func (m *VulnInformationMutation) TagsCleared() bool { + _, ok := m.clearedFields[vulninformation.FieldTags] + return ok +} + +// ResetTags resets all changes to the "tags" field. +func (m *VulnInformationMutation) ResetTags() { + m.tags = nil + m.appendtags = nil + delete(m.clearedFields, vulninformation.FieldTags) +} + +// SetFrom sets the "from" field. +func (m *VulnInformationMutation) SetFrom(s string) { + m.from = &s +} + +// From returns the value of the "from" field in the mutation. +func (m *VulnInformationMutation) From() (r string, exists bool) { + v := m.from + if v == nil { + return + } + return *v, true +} + +// OldFrom returns the old "from" field's value of the VulnInformation entity. +// If the VulnInformation object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *VulnInformationMutation) OldFrom(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFrom is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFrom requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFrom: %w", err) + } + return oldValue.From, nil +} + +// ResetFrom resets all changes to the "from" field. +func (m *VulnInformationMutation) ResetFrom() { + m.from = nil +} + +// Where appends a list predicates to the VulnInformationMutation builder. +func (m *VulnInformationMutation) Where(ps ...predicate.VulnInformation) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the VulnInformationMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *VulnInformationMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.VulnInformation, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *VulnInformationMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *VulnInformationMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (VulnInformation). +func (m *VulnInformationMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *VulnInformationMutation) Fields() []string { + fields := make([]string, 0, 10) + if m.key != nil { + fields = append(fields, vulninformation.FieldKey) + } + if m.title != nil { + fields = append(fields, vulninformation.FieldTitle) + } + if m.description != nil { + fields = append(fields, vulninformation.FieldDescription) + } + if m.severity != nil { + fields = append(fields, vulninformation.FieldSeverity) + } + if m.cve != nil { + fields = append(fields, vulninformation.FieldCve) + } + if m.disclosure != nil { + fields = append(fields, vulninformation.FieldDisclosure) + } + if m.solutions != nil { + fields = append(fields, vulninformation.FieldSolutions) + } + if m.references != nil { + fields = append(fields, vulninformation.FieldReferences) + } + if m.tags != nil { + fields = append(fields, vulninformation.FieldTags) + } + if m.from != nil { + fields = append(fields, vulninformation.FieldFrom) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *VulnInformationMutation) Field(name string) (ent.Value, bool) { + switch name { + case vulninformation.FieldKey: + return m.Key() + case vulninformation.FieldTitle: + return m.Title() + case vulninformation.FieldDescription: + return m.Description() + case vulninformation.FieldSeverity: + return m.Severity() + case vulninformation.FieldCve: + return m.Cve() + case vulninformation.FieldDisclosure: + return m.Disclosure() + case vulninformation.FieldSolutions: + return m.Solutions() + case vulninformation.FieldReferences: + return m.References() + case vulninformation.FieldTags: + return m.Tags() + case vulninformation.FieldFrom: + return m.From() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *VulnInformationMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case vulninformation.FieldKey: + return m.OldKey(ctx) + case vulninformation.FieldTitle: + return m.OldTitle(ctx) + case vulninformation.FieldDescription: + return m.OldDescription(ctx) + case vulninformation.FieldSeverity: + return m.OldSeverity(ctx) + case vulninformation.FieldCve: + return m.OldCve(ctx) + case vulninformation.FieldDisclosure: + return m.OldDisclosure(ctx) + case vulninformation.FieldSolutions: + return m.OldSolutions(ctx) + case vulninformation.FieldReferences: + return m.OldReferences(ctx) + case vulninformation.FieldTags: + return m.OldTags(ctx) + case vulninformation.FieldFrom: + return m.OldFrom(ctx) + } + return nil, fmt.Errorf("unknown VulnInformation field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *VulnInformationMutation) SetField(name string, value ent.Value) error { + switch name { + case vulninformation.FieldKey: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetKey(v) + return nil + case vulninformation.FieldTitle: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTitle(v) + return nil + case vulninformation.FieldDescription: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetDescription(v) + return nil + case vulninformation.FieldSeverity: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSeverity(v) + return nil + case vulninformation.FieldCve: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCve(v) + return nil + case vulninformation.FieldDisclosure: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetDisclosure(v) + return nil + case vulninformation.FieldSolutions: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSolutions(v) + return nil + case vulninformation.FieldReferences: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReferences(v) + return nil + case vulninformation.FieldTags: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTags(v) + return nil + case vulninformation.FieldFrom: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFrom(v) + return nil + } + return fmt.Errorf("unknown VulnInformation field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *VulnInformationMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *VulnInformationMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *VulnInformationMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown VulnInformation numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *VulnInformationMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(vulninformation.FieldReferences) { + fields = append(fields, vulninformation.FieldReferences) + } + if m.FieldCleared(vulninformation.FieldTags) { + fields = append(fields, vulninformation.FieldTags) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *VulnInformationMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *VulnInformationMutation) ClearField(name string) error { + switch name { + case vulninformation.FieldReferences: + m.ClearReferences() + return nil + case vulninformation.FieldTags: + m.ClearTags() + return nil + } + return fmt.Errorf("unknown VulnInformation nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *VulnInformationMutation) ResetField(name string) error { + switch name { + case vulninformation.FieldKey: + m.ResetKey() + return nil + case vulninformation.FieldTitle: + m.ResetTitle() + return nil + case vulninformation.FieldDescription: + m.ResetDescription() + return nil + case vulninformation.FieldSeverity: + m.ResetSeverity() + return nil + case vulninformation.FieldCve: + m.ResetCve() + return nil + case vulninformation.FieldDisclosure: + m.ResetDisclosure() + return nil + case vulninformation.FieldSolutions: + m.ResetSolutions() + return nil + case vulninformation.FieldReferences: + m.ResetReferences() + return nil + case vulninformation.FieldTags: + m.ResetTags() + return nil + case vulninformation.FieldFrom: + m.ResetFrom() + return nil + } + return fmt.Errorf("unknown VulnInformation field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *VulnInformationMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *VulnInformationMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *VulnInformationMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *VulnInformationMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *VulnInformationMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *VulnInformationMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *VulnInformationMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown VulnInformation unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *VulnInformationMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown VulnInformation edge %s", name) +} diff --git a/ent/predicate/predicate.go b/ent/predicate/predicate.go new file mode 100644 index 0000000..63d45f2 --- /dev/null +++ b/ent/predicate/predicate.go @@ -0,0 +1,10 @@ +// Code generated by ent, DO NOT EDIT. + +package predicate + +import ( + "entgo.io/ent/dialect/sql" +) + +// VulnInformation is the predicate function for vulninformation builders. +type VulnInformation func(*sql.Selector) diff --git a/ent/runtime.go b/ent/runtime.go new file mode 100644 index 0000000..13d304e --- /dev/null +++ b/ent/runtime.go @@ -0,0 +1,44 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "github.com/zema1/watchvuln/ent/schema" + "github.com/zema1/watchvuln/ent/vulninformation" +) + +// The init function reads all schema descriptors with runtime code +// (default values, validators, hooks and policies) and stitches it +// to their package variables. +func init() { + vulninformationFields := schema.VulnInformation{}.Fields() + _ = vulninformationFields + // vulninformationDescTitle is the schema descriptor for title field. + vulninformationDescTitle := vulninformationFields[1].Descriptor() + // vulninformation.DefaultTitle holds the default value on creation for the title field. + vulninformation.DefaultTitle = vulninformationDescTitle.Default.(string) + // vulninformationDescDescription is the schema descriptor for description field. + vulninformationDescDescription := vulninformationFields[2].Descriptor() + // vulninformation.DefaultDescription holds the default value on creation for the description field. + vulninformation.DefaultDescription = vulninformationDescDescription.Default.(string) + // vulninformationDescSeverity is the schema descriptor for severity field. + vulninformationDescSeverity := vulninformationFields[3].Descriptor() + // vulninformation.DefaultSeverity holds the default value on creation for the severity field. + vulninformation.DefaultSeverity = vulninformationDescSeverity.Default.(string) + // vulninformationDescCve is the schema descriptor for cve field. + vulninformationDescCve := vulninformationFields[4].Descriptor() + // vulninformation.DefaultCve holds the default value on creation for the cve field. + vulninformation.DefaultCve = vulninformationDescCve.Default.(string) + // vulninformationDescDisclosure is the schema descriptor for disclosure field. + vulninformationDescDisclosure := vulninformationFields[5].Descriptor() + // vulninformation.DefaultDisclosure holds the default value on creation for the disclosure field. + vulninformation.DefaultDisclosure = vulninformationDescDisclosure.Default.(string) + // vulninformationDescSolutions is the schema descriptor for solutions field. + vulninformationDescSolutions := vulninformationFields[6].Descriptor() + // vulninformation.DefaultSolutions holds the default value on creation for the solutions field. + vulninformation.DefaultSolutions = vulninformationDescSolutions.Default.(string) + // vulninformationDescFrom is the schema descriptor for from field. + vulninformationDescFrom := vulninformationFields[9].Descriptor() + // vulninformation.DefaultFrom holds the default value on creation for the from field. + vulninformation.DefaultFrom = vulninformationDescFrom.Default.(string) +} diff --git a/ent/runtime/runtime.go b/ent/runtime/runtime.go new file mode 100644 index 0000000..741eca1 --- /dev/null +++ b/ent/runtime/runtime.go @@ -0,0 +1,10 @@ +// Code generated by ent, DO NOT EDIT. + +package runtime + +// The schema-stitching logic is generated in github.com/zema1/vulntracker/ent/runtime.go + +const ( + Version = "v0.11.10" // Version of ent codegen. + Sum = "h1:iqn32ybY5HRW3xSAyMNdNKpZhKgMf1Zunsej9yPKUI8=" // Sum of ent codegen. +) diff --git a/ent/schema/vulninformation.go b/ent/schema/vulninformation.go new file mode 100644 index 0000000..a2a2243 --- /dev/null +++ b/ent/schema/vulninformation.go @@ -0,0 +1,32 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/field" +) + +// VulnInformation holds the schema definition for the VulnInformation entity. +type VulnInformation struct { + ent.Schema +} + +// Fields of the VulnInformation. +func (VulnInformation) Fields() []ent.Field { + return []ent.Field{ + field.String("key").Unique(), + field.String("title").Default(""), + field.String("description").Default(""), + field.String("severity").Default(""), + field.String("cve").Default(""), + field.String("disclosure").Default(""), + field.String("solutions").Default(""), + field.Strings("references").Optional(), + field.Strings("tags").Optional(), + field.String("from").Default(""), + } +} + +// Edges of the VulnInformation. +func (VulnInformation) Edges() []ent.Edge { + return nil +} diff --git a/ent/tx.go b/ent/tx.go new file mode 100644 index 0000000..5f84132 --- /dev/null +++ b/ent/tx.go @@ -0,0 +1,210 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "sync" + + "entgo.io/ent/dialect" +) + +// Tx is a transactional client that is created by calling Client.Tx(). +type Tx struct { + config + // VulnInformation is the client for interacting with the VulnInformation builders. + VulnInformation *VulnInformationClient + + // lazily loaded. + client *Client + clientOnce sync.Once + // ctx lives for the life of the transaction. It is + // the same context used by the underlying connection. + ctx context.Context +} + +type ( + // Committer is the interface that wraps the Commit method. + Committer interface { + Commit(context.Context, *Tx) error + } + + // The CommitFunc type is an adapter to allow the use of ordinary + // function as a Committer. If f is a function with the appropriate + // signature, CommitFunc(f) is a Committer that calls f. + CommitFunc func(context.Context, *Tx) error + + // CommitHook defines the "commit middleware". A function that gets a Committer + // and returns a Committer. For example: + // + // hook := func(next ent.Committer) ent.Committer { + // return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error { + // // Do some stuff before. + // if err := next.Commit(ctx, tx); err != nil { + // return err + // } + // // Do some stuff after. + // return nil + // }) + // } + // + CommitHook func(Committer) Committer +) + +// Commit calls f(ctx, m). +func (f CommitFunc) Commit(ctx context.Context, tx *Tx) error { + return f(ctx, tx) +} + +// Commit commits the transaction. +func (tx *Tx) Commit() error { + txDriver := tx.config.driver.(*txDriver) + var fn Committer = CommitFunc(func(context.Context, *Tx) error { + return txDriver.tx.Commit() + }) + txDriver.mu.Lock() + hooks := append([]CommitHook(nil), txDriver.onCommit...) + txDriver.mu.Unlock() + for i := len(hooks) - 1; i >= 0; i-- { + fn = hooks[i](fn) + } + return fn.Commit(tx.ctx, tx) +} + +// OnCommit adds a hook to call on commit. +func (tx *Tx) OnCommit(f CommitHook) { + txDriver := tx.config.driver.(*txDriver) + txDriver.mu.Lock() + txDriver.onCommit = append(txDriver.onCommit, f) + txDriver.mu.Unlock() +} + +type ( + // Rollbacker is the interface that wraps the Rollback method. + Rollbacker interface { + Rollback(context.Context, *Tx) error + } + + // The RollbackFunc type is an adapter to allow the use of ordinary + // function as a Rollbacker. If f is a function with the appropriate + // signature, RollbackFunc(f) is a Rollbacker that calls f. + RollbackFunc func(context.Context, *Tx) error + + // RollbackHook defines the "rollback middleware". A function that gets a Rollbacker + // and returns a Rollbacker. For example: + // + // hook := func(next ent.Rollbacker) ent.Rollbacker { + // return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error { + // // Do some stuff before. + // if err := next.Rollback(ctx, tx); err != nil { + // return err + // } + // // Do some stuff after. + // return nil + // }) + // } + // + RollbackHook func(Rollbacker) Rollbacker +) + +// Rollback calls f(ctx, m). +func (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error { + return f(ctx, tx) +} + +// Rollback rollbacks the transaction. +func (tx *Tx) Rollback() error { + txDriver := tx.config.driver.(*txDriver) + var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error { + return txDriver.tx.Rollback() + }) + txDriver.mu.Lock() + hooks := append([]RollbackHook(nil), txDriver.onRollback...) + txDriver.mu.Unlock() + for i := len(hooks) - 1; i >= 0; i-- { + fn = hooks[i](fn) + } + return fn.Rollback(tx.ctx, tx) +} + +// OnRollback adds a hook to call on rollback. +func (tx *Tx) OnRollback(f RollbackHook) { + txDriver := tx.config.driver.(*txDriver) + txDriver.mu.Lock() + txDriver.onRollback = append(txDriver.onRollback, f) + txDriver.mu.Unlock() +} + +// Client returns a Client that binds to current transaction. +func (tx *Tx) Client() *Client { + tx.clientOnce.Do(func() { + tx.client = &Client{config: tx.config} + tx.client.init() + }) + return tx.client +} + +func (tx *Tx) init() { + tx.VulnInformation = NewVulnInformationClient(tx.config) +} + +// txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. +// The idea is to support transactions without adding any extra code to the builders. +// When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. +// Commit and Rollback are nop for the internal builders and the user must call one +// of them in order to commit or rollback the transaction. +// +// If a closed transaction is embedded in one of the generated entities, and the entity +// applies a query, for example: VulnInformation.QueryXXX(), the query will be executed +// through the driver which created this transaction. +// +// Note that txDriver is not goroutine safe. +type txDriver struct { + // the driver we started the transaction from. + drv dialect.Driver + // tx is the underlying transaction. + tx dialect.Tx + // completion hooks. + mu sync.Mutex + onCommit []CommitHook + onRollback []RollbackHook +} + +// newTx creates a new transactional driver. +func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { + tx, err := drv.Tx(ctx) + if err != nil { + return nil, err + } + return &txDriver{tx: tx, drv: drv}, nil +} + +// Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls +// from the internal builders. Should be called only by the internal builders. +func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } + +// Dialect returns the dialect of the driver we started the transaction from. +func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } + +// Close is a nop close. +func (*txDriver) Close() error { return nil } + +// Commit is a nop commit for the internal builders. +// User must call `Tx.Commit` in order to commit the transaction. +func (*txDriver) Commit() error { return nil } + +// Rollback is a nop rollback for the internal builders. +// User must call `Tx.Rollback` in order to rollback the transaction. +func (*txDriver) Rollback() error { return nil } + +// Exec calls tx.Exec. +func (tx *txDriver) Exec(ctx context.Context, query string, args, v any) error { + return tx.tx.Exec(ctx, query, args, v) +} + +// Query calls tx.Query. +func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error { + return tx.tx.Query(ctx, query, args, v) +} + +var _ dialect.Driver = (*txDriver)(nil) diff --git a/ent/vulninformation.go b/ent/vulninformation.go new file mode 100644 index 0000000..f3f6679 --- /dev/null +++ b/ent/vulninformation.go @@ -0,0 +1,199 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "encoding/json" + "fmt" + "strings" + + "entgo.io/ent/dialect/sql" + "github.com/zema1/watchvuln/ent/vulninformation" +) + +// VulnInformation is the model entity for the VulnInformation schema. +type VulnInformation struct { + config `json:"-"` + // ID of the ent. + ID int `json:"id,omitempty"` + // Key holds the value of the "key" field. + Key string `json:"key,omitempty"` + // Title holds the value of the "title" field. + Title string `json:"title,omitempty"` + // Description holds the value of the "description" field. + Description string `json:"description,omitempty"` + // Severity holds the value of the "severity" field. + Severity string `json:"severity,omitempty"` + // Cve holds the value of the "cve" field. + Cve string `json:"cve,omitempty"` + // Disclosure holds the value of the "disclosure" field. + Disclosure string `json:"disclosure,omitempty"` + // Solutions holds the value of the "solutions" field. + Solutions string `json:"solutions,omitempty"` + // References holds the value of the "references" field. + References []string `json:"references,omitempty"` + // Tags holds the value of the "tags" field. + Tags []string `json:"tags,omitempty"` + // From holds the value of the "from" field. + From string `json:"from,omitempty"` +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*VulnInformation) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case vulninformation.FieldReferences, vulninformation.FieldTags: + values[i] = new([]byte) + case vulninformation.FieldID: + values[i] = new(sql.NullInt64) + case vulninformation.FieldKey, vulninformation.FieldTitle, vulninformation.FieldDescription, vulninformation.FieldSeverity, vulninformation.FieldCve, vulninformation.FieldDisclosure, vulninformation.FieldSolutions, vulninformation.FieldFrom: + values[i] = new(sql.NullString) + default: + return nil, fmt.Errorf("unexpected column %q for type VulnInformation", columns[i]) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the VulnInformation fields. +func (vi *VulnInformation) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case vulninformation.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + vi.ID = int(value.Int64) + case vulninformation.FieldKey: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field key", values[i]) + } else if value.Valid { + vi.Key = value.String + } + case vulninformation.FieldTitle: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field title", values[i]) + } else if value.Valid { + vi.Title = value.String + } + case vulninformation.FieldDescription: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field description", values[i]) + } else if value.Valid { + vi.Description = value.String + } + case vulninformation.FieldSeverity: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field severity", values[i]) + } else if value.Valid { + vi.Severity = value.String + } + case vulninformation.FieldCve: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field cve", values[i]) + } else if value.Valid { + vi.Cve = value.String + } + case vulninformation.FieldDisclosure: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field disclosure", values[i]) + } else if value.Valid { + vi.Disclosure = value.String + } + case vulninformation.FieldSolutions: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field solutions", values[i]) + } else if value.Valid { + vi.Solutions = value.String + } + case vulninformation.FieldReferences: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field references", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &vi.References); err != nil { + return fmt.Errorf("unmarshal field references: %w", err) + } + } + case vulninformation.FieldTags: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field tags", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &vi.Tags); err != nil { + return fmt.Errorf("unmarshal field tags: %w", err) + } + } + case vulninformation.FieldFrom: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field from", values[i]) + } else if value.Valid { + vi.From = value.String + } + } + } + return nil +} + +// Update returns a builder for updating this VulnInformation. +// Note that you need to call VulnInformation.Unwrap() before calling this method if this VulnInformation +// was returned from a transaction, and the transaction was committed or rolled back. +func (vi *VulnInformation) Update() *VulnInformationUpdateOne { + return NewVulnInformationClient(vi.config).UpdateOne(vi) +} + +// Unwrap unwraps the VulnInformation entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (vi *VulnInformation) Unwrap() *VulnInformation { + _tx, ok := vi.config.driver.(*txDriver) + if !ok { + panic("ent: VulnInformation is not a transactional entity") + } + vi.config.driver = _tx.drv + return vi +} + +// String implements the fmt.Stringer. +func (vi *VulnInformation) String() string { + var builder strings.Builder + builder.WriteString("VulnInformation(") + builder.WriteString(fmt.Sprintf("id=%v, ", vi.ID)) + builder.WriteString("key=") + builder.WriteString(vi.Key) + builder.WriteString(", ") + builder.WriteString("title=") + builder.WriteString(vi.Title) + builder.WriteString(", ") + builder.WriteString("description=") + builder.WriteString(vi.Description) + builder.WriteString(", ") + builder.WriteString("severity=") + builder.WriteString(vi.Severity) + builder.WriteString(", ") + builder.WriteString("cve=") + builder.WriteString(vi.Cve) + builder.WriteString(", ") + builder.WriteString("disclosure=") + builder.WriteString(vi.Disclosure) + builder.WriteString(", ") + builder.WriteString("solutions=") + builder.WriteString(vi.Solutions) + builder.WriteString(", ") + builder.WriteString("references=") + builder.WriteString(fmt.Sprintf("%v", vi.References)) + builder.WriteString(", ") + builder.WriteString("tags=") + builder.WriteString(fmt.Sprintf("%v", vi.Tags)) + builder.WriteString(", ") + builder.WriteString("from=") + builder.WriteString(vi.From) + builder.WriteByte(')') + return builder.String() +} + +// VulnInformations is a parsable slice of VulnInformation. +type VulnInformations []*VulnInformation diff --git a/ent/vulninformation/vulninformation.go b/ent/vulninformation/vulninformation.go new file mode 100644 index 0000000..8f616e5 --- /dev/null +++ b/ent/vulninformation/vulninformation.go @@ -0,0 +1,74 @@ +// Code generated by ent, DO NOT EDIT. + +package vulninformation + +const ( + // Label holds the string label denoting the vulninformation type in the database. + Label = "vuln_information" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldKey holds the string denoting the key field in the database. + FieldKey = "key" + // FieldTitle holds the string denoting the title field in the database. + FieldTitle = "title" + // FieldDescription holds the string denoting the description field in the database. + FieldDescription = "description" + // FieldSeverity holds the string denoting the severity field in the database. + FieldSeverity = "severity" + // FieldCve holds the string denoting the cve field in the database. + FieldCve = "cve" + // FieldDisclosure holds the string denoting the disclosure field in the database. + FieldDisclosure = "disclosure" + // FieldSolutions holds the string denoting the solutions field in the database. + FieldSolutions = "solutions" + // FieldReferences holds the string denoting the references field in the database. + FieldReferences = "references" + // FieldTags holds the string denoting the tags field in the database. + FieldTags = "tags" + // FieldFrom holds the string denoting the from field in the database. + FieldFrom = "from" + // Table holds the table name of the vulninformation in the database. + Table = "vuln_informations" +) + +// Columns holds all SQL columns for vulninformation fields. +var Columns = []string{ + FieldID, + FieldKey, + FieldTitle, + FieldDescription, + FieldSeverity, + FieldCve, + FieldDisclosure, + FieldSolutions, + FieldReferences, + FieldTags, + FieldFrom, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultTitle holds the default value on creation for the "title" field. + DefaultTitle string + // DefaultDescription holds the default value on creation for the "description" field. + DefaultDescription string + // DefaultSeverity holds the default value on creation for the "severity" field. + DefaultSeverity string + // DefaultCve holds the default value on creation for the "cve" field. + DefaultCve string + // DefaultDisclosure holds the default value on creation for the "disclosure" field. + DefaultDisclosure string + // DefaultSolutions holds the default value on creation for the "solutions" field. + DefaultSolutions string + // DefaultFrom holds the default value on creation for the "from" field. + DefaultFrom string +) diff --git a/ent/vulninformation/where.go b/ent/vulninformation/where.go new file mode 100644 index 0000000..dba8a5c --- /dev/null +++ b/ent/vulninformation/where.go @@ -0,0 +1,665 @@ +// Code generated by ent, DO NOT EDIT. + +package vulninformation + +import ( + "entgo.io/ent/dialect/sql" + "github.com/zema1/watchvuln/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLTE(FieldID, id)) +} + +// Key applies equality check predicate on the "key" field. It's identical to KeyEQ. +func Key(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldKey, v)) +} + +// Title applies equality check predicate on the "title" field. It's identical to TitleEQ. +func Title(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldTitle, v)) +} + +// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ. +func Description(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldDescription, v)) +} + +// Severity applies equality check predicate on the "severity" field. It's identical to SeverityEQ. +func Severity(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldSeverity, v)) +} + +// Cve applies equality check predicate on the "cve" field. It's identical to CveEQ. +func Cve(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldCve, v)) +} + +// Disclosure applies equality check predicate on the "disclosure" field. It's identical to DisclosureEQ. +func Disclosure(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldDisclosure, v)) +} + +// Solutions applies equality check predicate on the "solutions" field. It's identical to SolutionsEQ. +func Solutions(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldSolutions, v)) +} + +// From applies equality check predicate on the "from" field. It's identical to FromEQ. +func From(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldFrom, v)) +} + +// KeyEQ applies the EQ predicate on the "key" field. +func KeyEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldKey, v)) +} + +// KeyNEQ applies the NEQ predicate on the "key" field. +func KeyNEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNEQ(FieldKey, v)) +} + +// KeyIn applies the In predicate on the "key" field. +func KeyIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldIn(FieldKey, vs...)) +} + +// KeyNotIn applies the NotIn predicate on the "key" field. +func KeyNotIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNotIn(FieldKey, vs...)) +} + +// KeyGT applies the GT predicate on the "key" field. +func KeyGT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGT(FieldKey, v)) +} + +// KeyGTE applies the GTE predicate on the "key" field. +func KeyGTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGTE(FieldKey, v)) +} + +// KeyLT applies the LT predicate on the "key" field. +func KeyLT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLT(FieldKey, v)) +} + +// KeyLTE applies the LTE predicate on the "key" field. +func KeyLTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLTE(FieldKey, v)) +} + +// KeyContains applies the Contains predicate on the "key" field. +func KeyContains(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContains(FieldKey, v)) +} + +// KeyHasPrefix applies the HasPrefix predicate on the "key" field. +func KeyHasPrefix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasPrefix(FieldKey, v)) +} + +// KeyHasSuffix applies the HasSuffix predicate on the "key" field. +func KeyHasSuffix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasSuffix(FieldKey, v)) +} + +// KeyEqualFold applies the EqualFold predicate on the "key" field. +func KeyEqualFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEqualFold(FieldKey, v)) +} + +// KeyContainsFold applies the ContainsFold predicate on the "key" field. +func KeyContainsFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContainsFold(FieldKey, v)) +} + +// TitleEQ applies the EQ predicate on the "title" field. +func TitleEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldTitle, v)) +} + +// TitleNEQ applies the NEQ predicate on the "title" field. +func TitleNEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNEQ(FieldTitle, v)) +} + +// TitleIn applies the In predicate on the "title" field. +func TitleIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldIn(FieldTitle, vs...)) +} + +// TitleNotIn applies the NotIn predicate on the "title" field. +func TitleNotIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNotIn(FieldTitle, vs...)) +} + +// TitleGT applies the GT predicate on the "title" field. +func TitleGT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGT(FieldTitle, v)) +} + +// TitleGTE applies the GTE predicate on the "title" field. +func TitleGTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGTE(FieldTitle, v)) +} + +// TitleLT applies the LT predicate on the "title" field. +func TitleLT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLT(FieldTitle, v)) +} + +// TitleLTE applies the LTE predicate on the "title" field. +func TitleLTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLTE(FieldTitle, v)) +} + +// TitleContains applies the Contains predicate on the "title" field. +func TitleContains(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContains(FieldTitle, v)) +} + +// TitleHasPrefix applies the HasPrefix predicate on the "title" field. +func TitleHasPrefix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasPrefix(FieldTitle, v)) +} + +// TitleHasSuffix applies the HasSuffix predicate on the "title" field. +func TitleHasSuffix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasSuffix(FieldTitle, v)) +} + +// TitleEqualFold applies the EqualFold predicate on the "title" field. +func TitleEqualFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEqualFold(FieldTitle, v)) +} + +// TitleContainsFold applies the ContainsFold predicate on the "title" field. +func TitleContainsFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContainsFold(FieldTitle, v)) +} + +// DescriptionEQ applies the EQ predicate on the "description" field. +func DescriptionEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldDescription, v)) +} + +// DescriptionNEQ applies the NEQ predicate on the "description" field. +func DescriptionNEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNEQ(FieldDescription, v)) +} + +// DescriptionIn applies the In predicate on the "description" field. +func DescriptionIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldIn(FieldDescription, vs...)) +} + +// DescriptionNotIn applies the NotIn predicate on the "description" field. +func DescriptionNotIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNotIn(FieldDescription, vs...)) +} + +// DescriptionGT applies the GT predicate on the "description" field. +func DescriptionGT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGT(FieldDescription, v)) +} + +// DescriptionGTE applies the GTE predicate on the "description" field. +func DescriptionGTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGTE(FieldDescription, v)) +} + +// DescriptionLT applies the LT predicate on the "description" field. +func DescriptionLT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLT(FieldDescription, v)) +} + +// DescriptionLTE applies the LTE predicate on the "description" field. +func DescriptionLTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLTE(FieldDescription, v)) +} + +// DescriptionContains applies the Contains predicate on the "description" field. +func DescriptionContains(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContains(FieldDescription, v)) +} + +// DescriptionHasPrefix applies the HasPrefix predicate on the "description" field. +func DescriptionHasPrefix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasPrefix(FieldDescription, v)) +} + +// DescriptionHasSuffix applies the HasSuffix predicate on the "description" field. +func DescriptionHasSuffix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasSuffix(FieldDescription, v)) +} + +// DescriptionEqualFold applies the EqualFold predicate on the "description" field. +func DescriptionEqualFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEqualFold(FieldDescription, v)) +} + +// DescriptionContainsFold applies the ContainsFold predicate on the "description" field. +func DescriptionContainsFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContainsFold(FieldDescription, v)) +} + +// SeverityEQ applies the EQ predicate on the "severity" field. +func SeverityEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldSeverity, v)) +} + +// SeverityNEQ applies the NEQ predicate on the "severity" field. +func SeverityNEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNEQ(FieldSeverity, v)) +} + +// SeverityIn applies the In predicate on the "severity" field. +func SeverityIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldIn(FieldSeverity, vs...)) +} + +// SeverityNotIn applies the NotIn predicate on the "severity" field. +func SeverityNotIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNotIn(FieldSeverity, vs...)) +} + +// SeverityGT applies the GT predicate on the "severity" field. +func SeverityGT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGT(FieldSeverity, v)) +} + +// SeverityGTE applies the GTE predicate on the "severity" field. +func SeverityGTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGTE(FieldSeverity, v)) +} + +// SeverityLT applies the LT predicate on the "severity" field. +func SeverityLT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLT(FieldSeverity, v)) +} + +// SeverityLTE applies the LTE predicate on the "severity" field. +func SeverityLTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLTE(FieldSeverity, v)) +} + +// SeverityContains applies the Contains predicate on the "severity" field. +func SeverityContains(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContains(FieldSeverity, v)) +} + +// SeverityHasPrefix applies the HasPrefix predicate on the "severity" field. +func SeverityHasPrefix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasPrefix(FieldSeverity, v)) +} + +// SeverityHasSuffix applies the HasSuffix predicate on the "severity" field. +func SeverityHasSuffix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasSuffix(FieldSeverity, v)) +} + +// SeverityEqualFold applies the EqualFold predicate on the "severity" field. +func SeverityEqualFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEqualFold(FieldSeverity, v)) +} + +// SeverityContainsFold applies the ContainsFold predicate on the "severity" field. +func SeverityContainsFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContainsFold(FieldSeverity, v)) +} + +// CveEQ applies the EQ predicate on the "cve" field. +func CveEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldCve, v)) +} + +// CveNEQ applies the NEQ predicate on the "cve" field. +func CveNEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNEQ(FieldCve, v)) +} + +// CveIn applies the In predicate on the "cve" field. +func CveIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldIn(FieldCve, vs...)) +} + +// CveNotIn applies the NotIn predicate on the "cve" field. +func CveNotIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNotIn(FieldCve, vs...)) +} + +// CveGT applies the GT predicate on the "cve" field. +func CveGT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGT(FieldCve, v)) +} + +// CveGTE applies the GTE predicate on the "cve" field. +func CveGTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGTE(FieldCve, v)) +} + +// CveLT applies the LT predicate on the "cve" field. +func CveLT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLT(FieldCve, v)) +} + +// CveLTE applies the LTE predicate on the "cve" field. +func CveLTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLTE(FieldCve, v)) +} + +// CveContains applies the Contains predicate on the "cve" field. +func CveContains(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContains(FieldCve, v)) +} + +// CveHasPrefix applies the HasPrefix predicate on the "cve" field. +func CveHasPrefix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasPrefix(FieldCve, v)) +} + +// CveHasSuffix applies the HasSuffix predicate on the "cve" field. +func CveHasSuffix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasSuffix(FieldCve, v)) +} + +// CveEqualFold applies the EqualFold predicate on the "cve" field. +func CveEqualFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEqualFold(FieldCve, v)) +} + +// CveContainsFold applies the ContainsFold predicate on the "cve" field. +func CveContainsFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContainsFold(FieldCve, v)) +} + +// DisclosureEQ applies the EQ predicate on the "disclosure" field. +func DisclosureEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldDisclosure, v)) +} + +// DisclosureNEQ applies the NEQ predicate on the "disclosure" field. +func DisclosureNEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNEQ(FieldDisclosure, v)) +} + +// DisclosureIn applies the In predicate on the "disclosure" field. +func DisclosureIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldIn(FieldDisclosure, vs...)) +} + +// DisclosureNotIn applies the NotIn predicate on the "disclosure" field. +func DisclosureNotIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNotIn(FieldDisclosure, vs...)) +} + +// DisclosureGT applies the GT predicate on the "disclosure" field. +func DisclosureGT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGT(FieldDisclosure, v)) +} + +// DisclosureGTE applies the GTE predicate on the "disclosure" field. +func DisclosureGTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGTE(FieldDisclosure, v)) +} + +// DisclosureLT applies the LT predicate on the "disclosure" field. +func DisclosureLT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLT(FieldDisclosure, v)) +} + +// DisclosureLTE applies the LTE predicate on the "disclosure" field. +func DisclosureLTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLTE(FieldDisclosure, v)) +} + +// DisclosureContains applies the Contains predicate on the "disclosure" field. +func DisclosureContains(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContains(FieldDisclosure, v)) +} + +// DisclosureHasPrefix applies the HasPrefix predicate on the "disclosure" field. +func DisclosureHasPrefix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasPrefix(FieldDisclosure, v)) +} + +// DisclosureHasSuffix applies the HasSuffix predicate on the "disclosure" field. +func DisclosureHasSuffix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasSuffix(FieldDisclosure, v)) +} + +// DisclosureEqualFold applies the EqualFold predicate on the "disclosure" field. +func DisclosureEqualFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEqualFold(FieldDisclosure, v)) +} + +// DisclosureContainsFold applies the ContainsFold predicate on the "disclosure" field. +func DisclosureContainsFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContainsFold(FieldDisclosure, v)) +} + +// SolutionsEQ applies the EQ predicate on the "solutions" field. +func SolutionsEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldSolutions, v)) +} + +// SolutionsNEQ applies the NEQ predicate on the "solutions" field. +func SolutionsNEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNEQ(FieldSolutions, v)) +} + +// SolutionsIn applies the In predicate on the "solutions" field. +func SolutionsIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldIn(FieldSolutions, vs...)) +} + +// SolutionsNotIn applies the NotIn predicate on the "solutions" field. +func SolutionsNotIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNotIn(FieldSolutions, vs...)) +} + +// SolutionsGT applies the GT predicate on the "solutions" field. +func SolutionsGT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGT(FieldSolutions, v)) +} + +// SolutionsGTE applies the GTE predicate on the "solutions" field. +func SolutionsGTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGTE(FieldSolutions, v)) +} + +// SolutionsLT applies the LT predicate on the "solutions" field. +func SolutionsLT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLT(FieldSolutions, v)) +} + +// SolutionsLTE applies the LTE predicate on the "solutions" field. +func SolutionsLTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLTE(FieldSolutions, v)) +} + +// SolutionsContains applies the Contains predicate on the "solutions" field. +func SolutionsContains(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContains(FieldSolutions, v)) +} + +// SolutionsHasPrefix applies the HasPrefix predicate on the "solutions" field. +func SolutionsHasPrefix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasPrefix(FieldSolutions, v)) +} + +// SolutionsHasSuffix applies the HasSuffix predicate on the "solutions" field. +func SolutionsHasSuffix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasSuffix(FieldSolutions, v)) +} + +// SolutionsEqualFold applies the EqualFold predicate on the "solutions" field. +func SolutionsEqualFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEqualFold(FieldSolutions, v)) +} + +// SolutionsContainsFold applies the ContainsFold predicate on the "solutions" field. +func SolutionsContainsFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContainsFold(FieldSolutions, v)) +} + +// ReferencesIsNil applies the IsNil predicate on the "references" field. +func ReferencesIsNil() predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldIsNull(FieldReferences)) +} + +// ReferencesNotNil applies the NotNil predicate on the "references" field. +func ReferencesNotNil() predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNotNull(FieldReferences)) +} + +// TagsIsNil applies the IsNil predicate on the "tags" field. +func TagsIsNil() predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldIsNull(FieldTags)) +} + +// TagsNotNil applies the NotNil predicate on the "tags" field. +func TagsNotNil() predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNotNull(FieldTags)) +} + +// FromEQ applies the EQ predicate on the "from" field. +func FromEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEQ(FieldFrom, v)) +} + +// FromNEQ applies the NEQ predicate on the "from" field. +func FromNEQ(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNEQ(FieldFrom, v)) +} + +// FromIn applies the In predicate on the "from" field. +func FromIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldIn(FieldFrom, vs...)) +} + +// FromNotIn applies the NotIn predicate on the "from" field. +func FromNotIn(vs ...string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldNotIn(FieldFrom, vs...)) +} + +// FromGT applies the GT predicate on the "from" field. +func FromGT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGT(FieldFrom, v)) +} + +// FromGTE applies the GTE predicate on the "from" field. +func FromGTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldGTE(FieldFrom, v)) +} + +// FromLT applies the LT predicate on the "from" field. +func FromLT(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLT(FieldFrom, v)) +} + +// FromLTE applies the LTE predicate on the "from" field. +func FromLTE(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldLTE(FieldFrom, v)) +} + +// FromContains applies the Contains predicate on the "from" field. +func FromContains(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContains(FieldFrom, v)) +} + +// FromHasPrefix applies the HasPrefix predicate on the "from" field. +func FromHasPrefix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasPrefix(FieldFrom, v)) +} + +// FromHasSuffix applies the HasSuffix predicate on the "from" field. +func FromHasSuffix(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldHasSuffix(FieldFrom, v)) +} + +// FromEqualFold applies the EqualFold predicate on the "from" field. +func FromEqualFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldEqualFold(FieldFrom, v)) +} + +// FromContainsFold applies the ContainsFold predicate on the "from" field. +func FromContainsFold(v string) predicate.VulnInformation { + return predicate.VulnInformation(sql.FieldContainsFold(FieldFrom, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.VulnInformation) predicate.VulnInformation { + return predicate.VulnInformation(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for _, p := range predicates { + p(s1) + } + s.Where(s1.P()) + }) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.VulnInformation) predicate.VulnInformation { + return predicate.VulnInformation(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for i, p := range predicates { + if i > 0 { + s1.Or() + } + p(s1) + } + s.Where(s1.P()) + }) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.VulnInformation) predicate.VulnInformation { + return predicate.VulnInformation(func(s *sql.Selector) { + p(s.Not()) + }) +} diff --git a/ent/vulninformation_create.go b/ent/vulninformation_create.go new file mode 100644 index 0000000..a0c30a8 --- /dev/null +++ b/ent/vulninformation_create.go @@ -0,0 +1,1048 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/zema1/watchvuln/ent/vulninformation" +) + +// VulnInformationCreate is the builder for creating a VulnInformation entity. +type VulnInformationCreate struct { + config + mutation *VulnInformationMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetKey sets the "key" field. +func (vic *VulnInformationCreate) SetKey(s string) *VulnInformationCreate { + vic.mutation.SetKey(s) + return vic +} + +// SetTitle sets the "title" field. +func (vic *VulnInformationCreate) SetTitle(s string) *VulnInformationCreate { + vic.mutation.SetTitle(s) + return vic +} + +// SetNillableTitle sets the "title" field if the given value is not nil. +func (vic *VulnInformationCreate) SetNillableTitle(s *string) *VulnInformationCreate { + if s != nil { + vic.SetTitle(*s) + } + return vic +} + +// SetDescription sets the "description" field. +func (vic *VulnInformationCreate) SetDescription(s string) *VulnInformationCreate { + vic.mutation.SetDescription(s) + return vic +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (vic *VulnInformationCreate) SetNillableDescription(s *string) *VulnInformationCreate { + if s != nil { + vic.SetDescription(*s) + } + return vic +} + +// SetSeverity sets the "severity" field. +func (vic *VulnInformationCreate) SetSeverity(s string) *VulnInformationCreate { + vic.mutation.SetSeverity(s) + return vic +} + +// SetNillableSeverity sets the "severity" field if the given value is not nil. +func (vic *VulnInformationCreate) SetNillableSeverity(s *string) *VulnInformationCreate { + if s != nil { + vic.SetSeverity(*s) + } + return vic +} + +// SetCve sets the "cve" field. +func (vic *VulnInformationCreate) SetCve(s string) *VulnInformationCreate { + vic.mutation.SetCve(s) + return vic +} + +// SetNillableCve sets the "cve" field if the given value is not nil. +func (vic *VulnInformationCreate) SetNillableCve(s *string) *VulnInformationCreate { + if s != nil { + vic.SetCve(*s) + } + return vic +} + +// SetDisclosure sets the "disclosure" field. +func (vic *VulnInformationCreate) SetDisclosure(s string) *VulnInformationCreate { + vic.mutation.SetDisclosure(s) + return vic +} + +// SetNillableDisclosure sets the "disclosure" field if the given value is not nil. +func (vic *VulnInformationCreate) SetNillableDisclosure(s *string) *VulnInformationCreate { + if s != nil { + vic.SetDisclosure(*s) + } + return vic +} + +// SetSolutions sets the "solutions" field. +func (vic *VulnInformationCreate) SetSolutions(s string) *VulnInformationCreate { + vic.mutation.SetSolutions(s) + return vic +} + +// SetNillableSolutions sets the "solutions" field if the given value is not nil. +func (vic *VulnInformationCreate) SetNillableSolutions(s *string) *VulnInformationCreate { + if s != nil { + vic.SetSolutions(*s) + } + return vic +} + +// SetReferences sets the "references" field. +func (vic *VulnInformationCreate) SetReferences(s []string) *VulnInformationCreate { + vic.mutation.SetReferences(s) + return vic +} + +// SetTags sets the "tags" field. +func (vic *VulnInformationCreate) SetTags(s []string) *VulnInformationCreate { + vic.mutation.SetTags(s) + return vic +} + +// SetFrom sets the "from" field. +func (vic *VulnInformationCreate) SetFrom(s string) *VulnInformationCreate { + vic.mutation.SetFrom(s) + return vic +} + +// SetNillableFrom sets the "from" field if the given value is not nil. +func (vic *VulnInformationCreate) SetNillableFrom(s *string) *VulnInformationCreate { + if s != nil { + vic.SetFrom(*s) + } + return vic +} + +// Mutation returns the VulnInformationMutation object of the builder. +func (vic *VulnInformationCreate) Mutation() *VulnInformationMutation { + return vic.mutation +} + +// Save creates the VulnInformation in the database. +func (vic *VulnInformationCreate) Save(ctx context.Context) (*VulnInformation, error) { + vic.defaults() + return withHooks[*VulnInformation, VulnInformationMutation](ctx, vic.sqlSave, vic.mutation, vic.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (vic *VulnInformationCreate) SaveX(ctx context.Context) *VulnInformation { + v, err := vic.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (vic *VulnInformationCreate) Exec(ctx context.Context) error { + _, err := vic.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vic *VulnInformationCreate) ExecX(ctx context.Context) { + if err := vic.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (vic *VulnInformationCreate) defaults() { + if _, ok := vic.mutation.Title(); !ok { + v := vulninformation.DefaultTitle + vic.mutation.SetTitle(v) + } + if _, ok := vic.mutation.Description(); !ok { + v := vulninformation.DefaultDescription + vic.mutation.SetDescription(v) + } + if _, ok := vic.mutation.Severity(); !ok { + v := vulninformation.DefaultSeverity + vic.mutation.SetSeverity(v) + } + if _, ok := vic.mutation.Cve(); !ok { + v := vulninformation.DefaultCve + vic.mutation.SetCve(v) + } + if _, ok := vic.mutation.Disclosure(); !ok { + v := vulninformation.DefaultDisclosure + vic.mutation.SetDisclosure(v) + } + if _, ok := vic.mutation.Solutions(); !ok { + v := vulninformation.DefaultSolutions + vic.mutation.SetSolutions(v) + } + if _, ok := vic.mutation.From(); !ok { + v := vulninformation.DefaultFrom + vic.mutation.SetFrom(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (vic *VulnInformationCreate) check() error { + if _, ok := vic.mutation.Key(); !ok { + return &ValidationError{Name: "key", err: errors.New(`ent: missing required field "VulnInformation.key"`)} + } + if _, ok := vic.mutation.Title(); !ok { + return &ValidationError{Name: "title", err: errors.New(`ent: missing required field "VulnInformation.title"`)} + } + if _, ok := vic.mutation.Description(); !ok { + return &ValidationError{Name: "description", err: errors.New(`ent: missing required field "VulnInformation.description"`)} + } + if _, ok := vic.mutation.Severity(); !ok { + return &ValidationError{Name: "severity", err: errors.New(`ent: missing required field "VulnInformation.severity"`)} + } + if _, ok := vic.mutation.Cve(); !ok { + return &ValidationError{Name: "cve", err: errors.New(`ent: missing required field "VulnInformation.cve"`)} + } + if _, ok := vic.mutation.Disclosure(); !ok { + return &ValidationError{Name: "disclosure", err: errors.New(`ent: missing required field "VulnInformation.disclosure"`)} + } + if _, ok := vic.mutation.Solutions(); !ok { + return &ValidationError{Name: "solutions", err: errors.New(`ent: missing required field "VulnInformation.solutions"`)} + } + if _, ok := vic.mutation.From(); !ok { + return &ValidationError{Name: "from", err: errors.New(`ent: missing required field "VulnInformation.from"`)} + } + return nil +} + +func (vic *VulnInformationCreate) sqlSave(ctx context.Context) (*VulnInformation, error) { + if err := vic.check(); err != nil { + return nil, err + } + _node, _spec := vic.createSpec() + if err := sqlgraph.CreateNode(ctx, vic.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + id := _spec.ID.Value.(int64) + _node.ID = int(id) + vic.mutation.id = &_node.ID + vic.mutation.done = true + return _node, nil +} + +func (vic *VulnInformationCreate) createSpec() (*VulnInformation, *sqlgraph.CreateSpec) { + var ( + _node = &VulnInformation{config: vic.config} + _spec = sqlgraph.NewCreateSpec(vulninformation.Table, sqlgraph.NewFieldSpec(vulninformation.FieldID, field.TypeInt)) + ) + _spec.OnConflict = vic.conflict + if value, ok := vic.mutation.Key(); ok { + _spec.SetField(vulninformation.FieldKey, field.TypeString, value) + _node.Key = value + } + if value, ok := vic.mutation.Title(); ok { + _spec.SetField(vulninformation.FieldTitle, field.TypeString, value) + _node.Title = value + } + if value, ok := vic.mutation.Description(); ok { + _spec.SetField(vulninformation.FieldDescription, field.TypeString, value) + _node.Description = value + } + if value, ok := vic.mutation.Severity(); ok { + _spec.SetField(vulninformation.FieldSeverity, field.TypeString, value) + _node.Severity = value + } + if value, ok := vic.mutation.Cve(); ok { + _spec.SetField(vulninformation.FieldCve, field.TypeString, value) + _node.Cve = value + } + if value, ok := vic.mutation.Disclosure(); ok { + _spec.SetField(vulninformation.FieldDisclosure, field.TypeString, value) + _node.Disclosure = value + } + if value, ok := vic.mutation.Solutions(); ok { + _spec.SetField(vulninformation.FieldSolutions, field.TypeString, value) + _node.Solutions = value + } + if value, ok := vic.mutation.References(); ok { + _spec.SetField(vulninformation.FieldReferences, field.TypeJSON, value) + _node.References = value + } + if value, ok := vic.mutation.Tags(); ok { + _spec.SetField(vulninformation.FieldTags, field.TypeJSON, value) + _node.Tags = value + } + if value, ok := vic.mutation.From(); ok { + _spec.SetField(vulninformation.FieldFrom, field.TypeString, value) + _node.From = value + } + return _node, _spec +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.VulnInformation.Create(). +// SetKey(v). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.VulnInformationUpsert) { +// SetKey(v+v). +// }). +// Exec(ctx) +func (vic *VulnInformationCreate) OnConflict(opts ...sql.ConflictOption) *VulnInformationUpsertOne { + vic.conflict = opts + return &VulnInformationUpsertOne{ + create: vic, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.VulnInformation.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (vic *VulnInformationCreate) OnConflictColumns(columns ...string) *VulnInformationUpsertOne { + vic.conflict = append(vic.conflict, sql.ConflictColumns(columns...)) + return &VulnInformationUpsertOne{ + create: vic, + } +} + +type ( + // VulnInformationUpsertOne is the builder for "upsert"-ing + // one VulnInformation node. + VulnInformationUpsertOne struct { + create *VulnInformationCreate + } + + // VulnInformationUpsert is the "OnConflict" setter. + VulnInformationUpsert struct { + *sql.UpdateSet + } +) + +// SetKey sets the "key" field. +func (u *VulnInformationUpsert) SetKey(v string) *VulnInformationUpsert { + u.Set(vulninformation.FieldKey, v) + return u +} + +// UpdateKey sets the "key" field to the value that was provided on create. +func (u *VulnInformationUpsert) UpdateKey() *VulnInformationUpsert { + u.SetExcluded(vulninformation.FieldKey) + return u +} + +// SetTitle sets the "title" field. +func (u *VulnInformationUpsert) SetTitle(v string) *VulnInformationUpsert { + u.Set(vulninformation.FieldTitle, v) + return u +} + +// UpdateTitle sets the "title" field to the value that was provided on create. +func (u *VulnInformationUpsert) UpdateTitle() *VulnInformationUpsert { + u.SetExcluded(vulninformation.FieldTitle) + return u +} + +// SetDescription sets the "description" field. +func (u *VulnInformationUpsert) SetDescription(v string) *VulnInformationUpsert { + u.Set(vulninformation.FieldDescription, v) + return u +} + +// UpdateDescription sets the "description" field to the value that was provided on create. +func (u *VulnInformationUpsert) UpdateDescription() *VulnInformationUpsert { + u.SetExcluded(vulninformation.FieldDescription) + return u +} + +// SetSeverity sets the "severity" field. +func (u *VulnInformationUpsert) SetSeverity(v string) *VulnInformationUpsert { + u.Set(vulninformation.FieldSeverity, v) + return u +} + +// UpdateSeverity sets the "severity" field to the value that was provided on create. +func (u *VulnInformationUpsert) UpdateSeverity() *VulnInformationUpsert { + u.SetExcluded(vulninformation.FieldSeverity) + return u +} + +// SetCve sets the "cve" field. +func (u *VulnInformationUpsert) SetCve(v string) *VulnInformationUpsert { + u.Set(vulninformation.FieldCve, v) + return u +} + +// UpdateCve sets the "cve" field to the value that was provided on create. +func (u *VulnInformationUpsert) UpdateCve() *VulnInformationUpsert { + u.SetExcluded(vulninformation.FieldCve) + return u +} + +// SetDisclosure sets the "disclosure" field. +func (u *VulnInformationUpsert) SetDisclosure(v string) *VulnInformationUpsert { + u.Set(vulninformation.FieldDisclosure, v) + return u +} + +// UpdateDisclosure sets the "disclosure" field to the value that was provided on create. +func (u *VulnInformationUpsert) UpdateDisclosure() *VulnInformationUpsert { + u.SetExcluded(vulninformation.FieldDisclosure) + return u +} + +// SetSolutions sets the "solutions" field. +func (u *VulnInformationUpsert) SetSolutions(v string) *VulnInformationUpsert { + u.Set(vulninformation.FieldSolutions, v) + return u +} + +// UpdateSolutions sets the "solutions" field to the value that was provided on create. +func (u *VulnInformationUpsert) UpdateSolutions() *VulnInformationUpsert { + u.SetExcluded(vulninformation.FieldSolutions) + return u +} + +// SetReferences sets the "references" field. +func (u *VulnInformationUpsert) SetReferences(v []string) *VulnInformationUpsert { + u.Set(vulninformation.FieldReferences, v) + return u +} + +// UpdateReferences sets the "references" field to the value that was provided on create. +func (u *VulnInformationUpsert) UpdateReferences() *VulnInformationUpsert { + u.SetExcluded(vulninformation.FieldReferences) + return u +} + +// ClearReferences clears the value of the "references" field. +func (u *VulnInformationUpsert) ClearReferences() *VulnInformationUpsert { + u.SetNull(vulninformation.FieldReferences) + return u +} + +// SetTags sets the "tags" field. +func (u *VulnInformationUpsert) SetTags(v []string) *VulnInformationUpsert { + u.Set(vulninformation.FieldTags, v) + return u +} + +// UpdateTags sets the "tags" field to the value that was provided on create. +func (u *VulnInformationUpsert) UpdateTags() *VulnInformationUpsert { + u.SetExcluded(vulninformation.FieldTags) + return u +} + +// ClearTags clears the value of the "tags" field. +func (u *VulnInformationUpsert) ClearTags() *VulnInformationUpsert { + u.SetNull(vulninformation.FieldTags) + return u +} + +// SetFrom sets the "from" field. +func (u *VulnInformationUpsert) SetFrom(v string) *VulnInformationUpsert { + u.Set(vulninformation.FieldFrom, v) + return u +} + +// UpdateFrom sets the "from" field to the value that was provided on create. +func (u *VulnInformationUpsert) UpdateFrom() *VulnInformationUpsert { + u.SetExcluded(vulninformation.FieldFrom) + return u +} + +// UpdateNewValues updates the mutable fields using the new values that were set on create. +// Using this option is equivalent to using: +// +// client.VulnInformation.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *VulnInformationUpsertOne) UpdateNewValues() *VulnInformationUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.VulnInformation.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *VulnInformationUpsertOne) Ignore() *VulnInformationUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *VulnInformationUpsertOne) DoNothing() *VulnInformationUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the VulnInformationCreate.OnConflict +// documentation for more info. +func (u *VulnInformationUpsertOne) Update(set func(*VulnInformationUpsert)) *VulnInformationUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&VulnInformationUpsert{UpdateSet: update}) + })) + return u +} + +// SetKey sets the "key" field. +func (u *VulnInformationUpsertOne) SetKey(v string) *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.SetKey(v) + }) +} + +// UpdateKey sets the "key" field to the value that was provided on create. +func (u *VulnInformationUpsertOne) UpdateKey() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateKey() + }) +} + +// SetTitle sets the "title" field. +func (u *VulnInformationUpsertOne) SetTitle(v string) *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.SetTitle(v) + }) +} + +// UpdateTitle sets the "title" field to the value that was provided on create. +func (u *VulnInformationUpsertOne) UpdateTitle() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateTitle() + }) +} + +// SetDescription sets the "description" field. +func (u *VulnInformationUpsertOne) SetDescription(v string) *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.SetDescription(v) + }) +} + +// UpdateDescription sets the "description" field to the value that was provided on create. +func (u *VulnInformationUpsertOne) UpdateDescription() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateDescription() + }) +} + +// SetSeverity sets the "severity" field. +func (u *VulnInformationUpsertOne) SetSeverity(v string) *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.SetSeverity(v) + }) +} + +// UpdateSeverity sets the "severity" field to the value that was provided on create. +func (u *VulnInformationUpsertOne) UpdateSeverity() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateSeverity() + }) +} + +// SetCve sets the "cve" field. +func (u *VulnInformationUpsertOne) SetCve(v string) *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.SetCve(v) + }) +} + +// UpdateCve sets the "cve" field to the value that was provided on create. +func (u *VulnInformationUpsertOne) UpdateCve() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateCve() + }) +} + +// SetDisclosure sets the "disclosure" field. +func (u *VulnInformationUpsertOne) SetDisclosure(v string) *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.SetDisclosure(v) + }) +} + +// UpdateDisclosure sets the "disclosure" field to the value that was provided on create. +func (u *VulnInformationUpsertOne) UpdateDisclosure() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateDisclosure() + }) +} + +// SetSolutions sets the "solutions" field. +func (u *VulnInformationUpsertOne) SetSolutions(v string) *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.SetSolutions(v) + }) +} + +// UpdateSolutions sets the "solutions" field to the value that was provided on create. +func (u *VulnInformationUpsertOne) UpdateSolutions() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateSolutions() + }) +} + +// SetReferences sets the "references" field. +func (u *VulnInformationUpsertOne) SetReferences(v []string) *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.SetReferences(v) + }) +} + +// UpdateReferences sets the "references" field to the value that was provided on create. +func (u *VulnInformationUpsertOne) UpdateReferences() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateReferences() + }) +} + +// ClearReferences clears the value of the "references" field. +func (u *VulnInformationUpsertOne) ClearReferences() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.ClearReferences() + }) +} + +// SetTags sets the "tags" field. +func (u *VulnInformationUpsertOne) SetTags(v []string) *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.SetTags(v) + }) +} + +// UpdateTags sets the "tags" field to the value that was provided on create. +func (u *VulnInformationUpsertOne) UpdateTags() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateTags() + }) +} + +// ClearTags clears the value of the "tags" field. +func (u *VulnInformationUpsertOne) ClearTags() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.ClearTags() + }) +} + +// SetFrom sets the "from" field. +func (u *VulnInformationUpsertOne) SetFrom(v string) *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.SetFrom(v) + }) +} + +// UpdateFrom sets the "from" field to the value that was provided on create. +func (u *VulnInformationUpsertOne) UpdateFrom() *VulnInformationUpsertOne { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateFrom() + }) +} + +// Exec executes the query. +func (u *VulnInformationUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for VulnInformationCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *VulnInformationUpsertOne) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} + +// Exec executes the UPSERT query and returns the inserted/updated ID. +func (u *VulnInformationUpsertOne) ID(ctx context.Context) (id int, err error) { + node, err := u.create.Save(ctx) + if err != nil { + return id, err + } + return node.ID, nil +} + +// IDX is like ID, but panics if an error occurs. +func (u *VulnInformationUpsertOne) IDX(ctx context.Context) int { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +// VulnInformationCreateBulk is the builder for creating many VulnInformation entities in bulk. +type VulnInformationCreateBulk struct { + config + builders []*VulnInformationCreate + conflict []sql.ConflictOption +} + +// Save creates the VulnInformation entities in the database. +func (vicb *VulnInformationCreateBulk) Save(ctx context.Context) ([]*VulnInformation, error) { + specs := make([]*sqlgraph.CreateSpec, len(vicb.builders)) + nodes := make([]*VulnInformation, len(vicb.builders)) + mutators := make([]Mutator, len(vicb.builders)) + for i := range vicb.builders { + func(i int, root context.Context) { + builder := vicb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*VulnInformationMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + nodes[i], specs[i] = builder.createSpec() + var err error + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, vicb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + spec.OnConflict = vicb.conflict + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, vicb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + if specs[i].ID.Value != nil { + id := specs[i].ID.Value.(int64) + nodes[i].ID = int(id) + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, vicb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (vicb *VulnInformationCreateBulk) SaveX(ctx context.Context) []*VulnInformation { + v, err := vicb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (vicb *VulnInformationCreateBulk) Exec(ctx context.Context) error { + _, err := vicb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (vicb *VulnInformationCreateBulk) ExecX(ctx context.Context) { + if err := vicb.Exec(ctx); err != nil { + panic(err) + } +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.VulnInformation.CreateBulk(builders...). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.VulnInformationUpsert) { +// SetKey(v+v). +// }). +// Exec(ctx) +func (vicb *VulnInformationCreateBulk) OnConflict(opts ...sql.ConflictOption) *VulnInformationUpsertBulk { + vicb.conflict = opts + return &VulnInformationUpsertBulk{ + create: vicb, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.VulnInformation.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (vicb *VulnInformationCreateBulk) OnConflictColumns(columns ...string) *VulnInformationUpsertBulk { + vicb.conflict = append(vicb.conflict, sql.ConflictColumns(columns...)) + return &VulnInformationUpsertBulk{ + create: vicb, + } +} + +// VulnInformationUpsertBulk is the builder for "upsert"-ing +// a bulk of VulnInformation nodes. +type VulnInformationUpsertBulk struct { + create *VulnInformationCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.VulnInformation.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *VulnInformationUpsertBulk) UpdateNewValues() *VulnInformationUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.VulnInformation.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *VulnInformationUpsertBulk) Ignore() *VulnInformationUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *VulnInformationUpsertBulk) DoNothing() *VulnInformationUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the VulnInformationCreateBulk.OnConflict +// documentation for more info. +func (u *VulnInformationUpsertBulk) Update(set func(*VulnInformationUpsert)) *VulnInformationUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&VulnInformationUpsert{UpdateSet: update}) + })) + return u +} + +// SetKey sets the "key" field. +func (u *VulnInformationUpsertBulk) SetKey(v string) *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.SetKey(v) + }) +} + +// UpdateKey sets the "key" field to the value that was provided on create. +func (u *VulnInformationUpsertBulk) UpdateKey() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateKey() + }) +} + +// SetTitle sets the "title" field. +func (u *VulnInformationUpsertBulk) SetTitle(v string) *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.SetTitle(v) + }) +} + +// UpdateTitle sets the "title" field to the value that was provided on create. +func (u *VulnInformationUpsertBulk) UpdateTitle() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateTitle() + }) +} + +// SetDescription sets the "description" field. +func (u *VulnInformationUpsertBulk) SetDescription(v string) *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.SetDescription(v) + }) +} + +// UpdateDescription sets the "description" field to the value that was provided on create. +func (u *VulnInformationUpsertBulk) UpdateDescription() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateDescription() + }) +} + +// SetSeverity sets the "severity" field. +func (u *VulnInformationUpsertBulk) SetSeverity(v string) *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.SetSeverity(v) + }) +} + +// UpdateSeverity sets the "severity" field to the value that was provided on create. +func (u *VulnInformationUpsertBulk) UpdateSeverity() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateSeverity() + }) +} + +// SetCve sets the "cve" field. +func (u *VulnInformationUpsertBulk) SetCve(v string) *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.SetCve(v) + }) +} + +// UpdateCve sets the "cve" field to the value that was provided on create. +func (u *VulnInformationUpsertBulk) UpdateCve() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateCve() + }) +} + +// SetDisclosure sets the "disclosure" field. +func (u *VulnInformationUpsertBulk) SetDisclosure(v string) *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.SetDisclosure(v) + }) +} + +// UpdateDisclosure sets the "disclosure" field to the value that was provided on create. +func (u *VulnInformationUpsertBulk) UpdateDisclosure() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateDisclosure() + }) +} + +// SetSolutions sets the "solutions" field. +func (u *VulnInformationUpsertBulk) SetSolutions(v string) *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.SetSolutions(v) + }) +} + +// UpdateSolutions sets the "solutions" field to the value that was provided on create. +func (u *VulnInformationUpsertBulk) UpdateSolutions() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateSolutions() + }) +} + +// SetReferences sets the "references" field. +func (u *VulnInformationUpsertBulk) SetReferences(v []string) *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.SetReferences(v) + }) +} + +// UpdateReferences sets the "references" field to the value that was provided on create. +func (u *VulnInformationUpsertBulk) UpdateReferences() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateReferences() + }) +} + +// ClearReferences clears the value of the "references" field. +func (u *VulnInformationUpsertBulk) ClearReferences() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.ClearReferences() + }) +} + +// SetTags sets the "tags" field. +func (u *VulnInformationUpsertBulk) SetTags(v []string) *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.SetTags(v) + }) +} + +// UpdateTags sets the "tags" field to the value that was provided on create. +func (u *VulnInformationUpsertBulk) UpdateTags() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateTags() + }) +} + +// ClearTags clears the value of the "tags" field. +func (u *VulnInformationUpsertBulk) ClearTags() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.ClearTags() + }) +} + +// SetFrom sets the "from" field. +func (u *VulnInformationUpsertBulk) SetFrom(v string) *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.SetFrom(v) + }) +} + +// UpdateFrom sets the "from" field to the value that was provided on create. +func (u *VulnInformationUpsertBulk) UpdateFrom() *VulnInformationUpsertBulk { + return u.Update(func(s *VulnInformationUpsert) { + s.UpdateFrom() + }) +} + +// Exec executes the query. +func (u *VulnInformationUpsertBulk) Exec(ctx context.Context) error { + for i, b := range u.create.builders { + if len(b.conflict) != 0 { + return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the VulnInformationCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for VulnInformationCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *VulnInformationUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/ent/vulninformation_delete.go b/ent/vulninformation_delete.go new file mode 100644 index 0000000..eb22212 --- /dev/null +++ b/ent/vulninformation_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/zema1/watchvuln/ent/predicate" + "github.com/zema1/watchvuln/ent/vulninformation" +) + +// VulnInformationDelete is the builder for deleting a VulnInformation entity. +type VulnInformationDelete struct { + config + hooks []Hook + mutation *VulnInformationMutation +} + +// Where appends a list predicates to the VulnInformationDelete builder. +func (vid *VulnInformationDelete) Where(ps ...predicate.VulnInformation) *VulnInformationDelete { + vid.mutation.Where(ps...) + return vid +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (vid *VulnInformationDelete) Exec(ctx context.Context) (int, error) { + return withHooks[int, VulnInformationMutation](ctx, vid.sqlExec, vid.mutation, vid.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (vid *VulnInformationDelete) ExecX(ctx context.Context) int { + n, err := vid.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (vid *VulnInformationDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(vulninformation.Table, sqlgraph.NewFieldSpec(vulninformation.FieldID, field.TypeInt)) + if ps := vid.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, vid.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + vid.mutation.done = true + return affected, err +} + +// VulnInformationDeleteOne is the builder for deleting a single VulnInformation entity. +type VulnInformationDeleteOne struct { + vid *VulnInformationDelete +} + +// Where appends a list predicates to the VulnInformationDelete builder. +func (vido *VulnInformationDeleteOne) Where(ps ...predicate.VulnInformation) *VulnInformationDeleteOne { + vido.vid.mutation.Where(ps...) + return vido +} + +// Exec executes the deletion query. +func (vido *VulnInformationDeleteOne) Exec(ctx context.Context) error { + n, err := vido.vid.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{vulninformation.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (vido *VulnInformationDeleteOne) ExecX(ctx context.Context) { + if err := vido.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/ent/vulninformation_query.go b/ent/vulninformation_query.go new file mode 100644 index 0000000..30c9739 --- /dev/null +++ b/ent/vulninformation_query.go @@ -0,0 +1,526 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/zema1/watchvuln/ent/predicate" + "github.com/zema1/watchvuln/ent/vulninformation" +) + +// VulnInformationQuery is the builder for querying VulnInformation entities. +type VulnInformationQuery struct { + config + ctx *QueryContext + order []OrderFunc + inters []Interceptor + predicates []predicate.VulnInformation + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the VulnInformationQuery builder. +func (viq *VulnInformationQuery) Where(ps ...predicate.VulnInformation) *VulnInformationQuery { + viq.predicates = append(viq.predicates, ps...) + return viq +} + +// Limit the number of records to be returned by this query. +func (viq *VulnInformationQuery) Limit(limit int) *VulnInformationQuery { + viq.ctx.Limit = &limit + return viq +} + +// Offset to start from. +func (viq *VulnInformationQuery) Offset(offset int) *VulnInformationQuery { + viq.ctx.Offset = &offset + return viq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (viq *VulnInformationQuery) Unique(unique bool) *VulnInformationQuery { + viq.ctx.Unique = &unique + return viq +} + +// Order specifies how the records should be ordered. +func (viq *VulnInformationQuery) Order(o ...OrderFunc) *VulnInformationQuery { + viq.order = append(viq.order, o...) + return viq +} + +// First returns the first VulnInformation entity from the query. +// Returns a *NotFoundError when no VulnInformation was found. +func (viq *VulnInformationQuery) First(ctx context.Context) (*VulnInformation, error) { + nodes, err := viq.Limit(1).All(setContextOp(ctx, viq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{vulninformation.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (viq *VulnInformationQuery) FirstX(ctx context.Context) *VulnInformation { + node, err := viq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first VulnInformation ID from the query. +// Returns a *NotFoundError when no VulnInformation ID was found. +func (viq *VulnInformationQuery) FirstID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = viq.Limit(1).IDs(setContextOp(ctx, viq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{vulninformation.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (viq *VulnInformationQuery) FirstIDX(ctx context.Context) int { + id, err := viq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single VulnInformation entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one VulnInformation entity is found. +// Returns a *NotFoundError when no VulnInformation entities are found. +func (viq *VulnInformationQuery) Only(ctx context.Context) (*VulnInformation, error) { + nodes, err := viq.Limit(2).All(setContextOp(ctx, viq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{vulninformation.Label} + default: + return nil, &NotSingularError{vulninformation.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (viq *VulnInformationQuery) OnlyX(ctx context.Context) *VulnInformation { + node, err := viq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only VulnInformation ID in the query. +// Returns a *NotSingularError when more than one VulnInformation ID is found. +// Returns a *NotFoundError when no entities are found. +func (viq *VulnInformationQuery) OnlyID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = viq.Limit(2).IDs(setContextOp(ctx, viq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{vulninformation.Label} + default: + err = &NotSingularError{vulninformation.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (viq *VulnInformationQuery) OnlyIDX(ctx context.Context) int { + id, err := viq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of VulnInformations. +func (viq *VulnInformationQuery) All(ctx context.Context) ([]*VulnInformation, error) { + ctx = setContextOp(ctx, viq.ctx, "All") + if err := viq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*VulnInformation, *VulnInformationQuery]() + return withInterceptors[[]*VulnInformation](ctx, viq, qr, viq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (viq *VulnInformationQuery) AllX(ctx context.Context) []*VulnInformation { + nodes, err := viq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of VulnInformation IDs. +func (viq *VulnInformationQuery) IDs(ctx context.Context) (ids []int, err error) { + if viq.ctx.Unique == nil && viq.path != nil { + viq.Unique(true) + } + ctx = setContextOp(ctx, viq.ctx, "IDs") + if err = viq.Select(vulninformation.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (viq *VulnInformationQuery) IDsX(ctx context.Context) []int { + ids, err := viq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (viq *VulnInformationQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, viq.ctx, "Count") + if err := viq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, viq, querierCount[*VulnInformationQuery](), viq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (viq *VulnInformationQuery) CountX(ctx context.Context) int { + count, err := viq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (viq *VulnInformationQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, viq.ctx, "Exist") + switch _, err := viq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (viq *VulnInformationQuery) ExistX(ctx context.Context) bool { + exist, err := viq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the VulnInformationQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (viq *VulnInformationQuery) Clone() *VulnInformationQuery { + if viq == nil { + return nil + } + return &VulnInformationQuery{ + config: viq.config, + ctx: viq.ctx.Clone(), + order: append([]OrderFunc{}, viq.order...), + inters: append([]Interceptor{}, viq.inters...), + predicates: append([]predicate.VulnInformation{}, viq.predicates...), + // clone intermediate query. + sql: viq.sql.Clone(), + path: viq.path, + } +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Key string `json:"key,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.VulnInformation.Query(). +// GroupBy(vulninformation.FieldKey). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (viq *VulnInformationQuery) GroupBy(field string, fields ...string) *VulnInformationGroupBy { + viq.ctx.Fields = append([]string{field}, fields...) + grbuild := &VulnInformationGroupBy{build: viq} + grbuild.flds = &viq.ctx.Fields + grbuild.label = vulninformation.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// Key string `json:"key,omitempty"` +// } +// +// client.VulnInformation.Query(). +// Select(vulninformation.FieldKey). +// Scan(ctx, &v) +func (viq *VulnInformationQuery) Select(fields ...string) *VulnInformationSelect { + viq.ctx.Fields = append(viq.ctx.Fields, fields...) + sbuild := &VulnInformationSelect{VulnInformationQuery: viq} + sbuild.label = vulninformation.Label + sbuild.flds, sbuild.scan = &viq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a VulnInformationSelect configured with the given aggregations. +func (viq *VulnInformationQuery) Aggregate(fns ...AggregateFunc) *VulnInformationSelect { + return viq.Select().Aggregate(fns...) +} + +func (viq *VulnInformationQuery) prepareQuery(ctx context.Context) error { + for _, inter := range viq.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, viq); err != nil { + return err + } + } + } + for _, f := range viq.ctx.Fields { + if !vulninformation.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if viq.path != nil { + prev, err := viq.path(ctx) + if err != nil { + return err + } + viq.sql = prev + } + return nil +} + +func (viq *VulnInformationQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*VulnInformation, error) { + var ( + nodes = []*VulnInformation{} + _spec = viq.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*VulnInformation).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &VulnInformation{config: viq.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, viq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (viq *VulnInformationQuery) sqlCount(ctx context.Context) (int, error) { + _spec := viq.querySpec() + _spec.Node.Columns = viq.ctx.Fields + if len(viq.ctx.Fields) > 0 { + _spec.Unique = viq.ctx.Unique != nil && *viq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, viq.driver, _spec) +} + +func (viq *VulnInformationQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(vulninformation.Table, vulninformation.Columns, sqlgraph.NewFieldSpec(vulninformation.FieldID, field.TypeInt)) + _spec.From = viq.sql + if unique := viq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if viq.path != nil { + _spec.Unique = true + } + if fields := viq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, vulninformation.FieldID) + for i := range fields { + if fields[i] != vulninformation.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := viq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := viq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := viq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := viq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (viq *VulnInformationQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(viq.driver.Dialect()) + t1 := builder.Table(vulninformation.Table) + columns := viq.ctx.Fields + if len(columns) == 0 { + columns = vulninformation.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if viq.sql != nil { + selector = viq.sql + selector.Select(selector.Columns(columns...)...) + } + if viq.ctx.Unique != nil && *viq.ctx.Unique { + selector.Distinct() + } + for _, p := range viq.predicates { + p(selector) + } + for _, p := range viq.order { + p(selector) + } + if offset := viq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := viq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// VulnInformationGroupBy is the group-by builder for VulnInformation entities. +type VulnInformationGroupBy struct { + selector + build *VulnInformationQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (vigb *VulnInformationGroupBy) Aggregate(fns ...AggregateFunc) *VulnInformationGroupBy { + vigb.fns = append(vigb.fns, fns...) + return vigb +} + +// Scan applies the selector query and scans the result into the given value. +func (vigb *VulnInformationGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, vigb.build.ctx, "GroupBy") + if err := vigb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*VulnInformationQuery, *VulnInformationGroupBy](ctx, vigb.build, vigb, vigb.build.inters, v) +} + +func (vigb *VulnInformationGroupBy) sqlScan(ctx context.Context, root *VulnInformationQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(vigb.fns)) + for _, fn := range vigb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*vigb.flds)+len(vigb.fns)) + for _, f := range *vigb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*vigb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := vigb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// VulnInformationSelect is the builder for selecting fields of VulnInformation entities. +type VulnInformationSelect struct { + *VulnInformationQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (vis *VulnInformationSelect) Aggregate(fns ...AggregateFunc) *VulnInformationSelect { + vis.fns = append(vis.fns, fns...) + return vis +} + +// Scan applies the selector query and scans the result into the given value. +func (vis *VulnInformationSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, vis.ctx, "Select") + if err := vis.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*VulnInformationQuery, *VulnInformationSelect](ctx, vis.VulnInformationQuery, vis, vis.inters, v) +} + +func (vis *VulnInformationSelect) sqlScan(ctx context.Context, root *VulnInformationQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(vis.fns)) + for _, fn := range vis.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*vis.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := vis.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/ent/vulninformation_update.go b/ent/vulninformation_update.go new file mode 100644 index 0000000..b62d0f7 --- /dev/null +++ b/ent/vulninformation_update.go @@ -0,0 +1,548 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/dialect/sql/sqljson" + "entgo.io/ent/schema/field" + "github.com/zema1/watchvuln/ent/predicate" + "github.com/zema1/watchvuln/ent/vulninformation" +) + +// VulnInformationUpdate is the builder for updating VulnInformation entities. +type VulnInformationUpdate struct { + config + hooks []Hook + mutation *VulnInformationMutation +} + +// Where appends a list predicates to the VulnInformationUpdate builder. +func (viu *VulnInformationUpdate) Where(ps ...predicate.VulnInformation) *VulnInformationUpdate { + viu.mutation.Where(ps...) + return viu +} + +// SetKey sets the "key" field. +func (viu *VulnInformationUpdate) SetKey(s string) *VulnInformationUpdate { + viu.mutation.SetKey(s) + return viu +} + +// SetTitle sets the "title" field. +func (viu *VulnInformationUpdate) SetTitle(s string) *VulnInformationUpdate { + viu.mutation.SetTitle(s) + return viu +} + +// SetNillableTitle sets the "title" field if the given value is not nil. +func (viu *VulnInformationUpdate) SetNillableTitle(s *string) *VulnInformationUpdate { + if s != nil { + viu.SetTitle(*s) + } + return viu +} + +// SetDescription sets the "description" field. +func (viu *VulnInformationUpdate) SetDescription(s string) *VulnInformationUpdate { + viu.mutation.SetDescription(s) + return viu +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (viu *VulnInformationUpdate) SetNillableDescription(s *string) *VulnInformationUpdate { + if s != nil { + viu.SetDescription(*s) + } + return viu +} + +// SetSeverity sets the "severity" field. +func (viu *VulnInformationUpdate) SetSeverity(s string) *VulnInformationUpdate { + viu.mutation.SetSeverity(s) + return viu +} + +// SetNillableSeverity sets the "severity" field if the given value is not nil. +func (viu *VulnInformationUpdate) SetNillableSeverity(s *string) *VulnInformationUpdate { + if s != nil { + viu.SetSeverity(*s) + } + return viu +} + +// SetCve sets the "cve" field. +func (viu *VulnInformationUpdate) SetCve(s string) *VulnInformationUpdate { + viu.mutation.SetCve(s) + return viu +} + +// SetNillableCve sets the "cve" field if the given value is not nil. +func (viu *VulnInformationUpdate) SetNillableCve(s *string) *VulnInformationUpdate { + if s != nil { + viu.SetCve(*s) + } + return viu +} + +// SetDisclosure sets the "disclosure" field. +func (viu *VulnInformationUpdate) SetDisclosure(s string) *VulnInformationUpdate { + viu.mutation.SetDisclosure(s) + return viu +} + +// SetNillableDisclosure sets the "disclosure" field if the given value is not nil. +func (viu *VulnInformationUpdate) SetNillableDisclosure(s *string) *VulnInformationUpdate { + if s != nil { + viu.SetDisclosure(*s) + } + return viu +} + +// SetSolutions sets the "solutions" field. +func (viu *VulnInformationUpdate) SetSolutions(s string) *VulnInformationUpdate { + viu.mutation.SetSolutions(s) + return viu +} + +// SetNillableSolutions sets the "solutions" field if the given value is not nil. +func (viu *VulnInformationUpdate) SetNillableSolutions(s *string) *VulnInformationUpdate { + if s != nil { + viu.SetSolutions(*s) + } + return viu +} + +// SetReferences sets the "references" field. +func (viu *VulnInformationUpdate) SetReferences(s []string) *VulnInformationUpdate { + viu.mutation.SetReferences(s) + return viu +} + +// AppendReferences appends s to the "references" field. +func (viu *VulnInformationUpdate) AppendReferences(s []string) *VulnInformationUpdate { + viu.mutation.AppendReferences(s) + return viu +} + +// ClearReferences clears the value of the "references" field. +func (viu *VulnInformationUpdate) ClearReferences() *VulnInformationUpdate { + viu.mutation.ClearReferences() + return viu +} + +// SetTags sets the "tags" field. +func (viu *VulnInformationUpdate) SetTags(s []string) *VulnInformationUpdate { + viu.mutation.SetTags(s) + return viu +} + +// AppendTags appends s to the "tags" field. +func (viu *VulnInformationUpdate) AppendTags(s []string) *VulnInformationUpdate { + viu.mutation.AppendTags(s) + return viu +} + +// ClearTags clears the value of the "tags" field. +func (viu *VulnInformationUpdate) ClearTags() *VulnInformationUpdate { + viu.mutation.ClearTags() + return viu +} + +// SetFrom sets the "from" field. +func (viu *VulnInformationUpdate) SetFrom(s string) *VulnInformationUpdate { + viu.mutation.SetFrom(s) + return viu +} + +// SetNillableFrom sets the "from" field if the given value is not nil. +func (viu *VulnInformationUpdate) SetNillableFrom(s *string) *VulnInformationUpdate { + if s != nil { + viu.SetFrom(*s) + } + return viu +} + +// Mutation returns the VulnInformationMutation object of the builder. +func (viu *VulnInformationUpdate) Mutation() *VulnInformationMutation { + return viu.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (viu *VulnInformationUpdate) Save(ctx context.Context) (int, error) { + return withHooks[int, VulnInformationMutation](ctx, viu.sqlSave, viu.mutation, viu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (viu *VulnInformationUpdate) SaveX(ctx context.Context) int { + affected, err := viu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (viu *VulnInformationUpdate) Exec(ctx context.Context) error { + _, err := viu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (viu *VulnInformationUpdate) ExecX(ctx context.Context) { + if err := viu.Exec(ctx); err != nil { + panic(err) + } +} + +func (viu *VulnInformationUpdate) sqlSave(ctx context.Context) (n int, err error) { + _spec := sqlgraph.NewUpdateSpec(vulninformation.Table, vulninformation.Columns, sqlgraph.NewFieldSpec(vulninformation.FieldID, field.TypeInt)) + if ps := viu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := viu.mutation.Key(); ok { + _spec.SetField(vulninformation.FieldKey, field.TypeString, value) + } + if value, ok := viu.mutation.Title(); ok { + _spec.SetField(vulninformation.FieldTitle, field.TypeString, value) + } + if value, ok := viu.mutation.Description(); ok { + _spec.SetField(vulninformation.FieldDescription, field.TypeString, value) + } + if value, ok := viu.mutation.Severity(); ok { + _spec.SetField(vulninformation.FieldSeverity, field.TypeString, value) + } + if value, ok := viu.mutation.Cve(); ok { + _spec.SetField(vulninformation.FieldCve, field.TypeString, value) + } + if value, ok := viu.mutation.Disclosure(); ok { + _spec.SetField(vulninformation.FieldDisclosure, field.TypeString, value) + } + if value, ok := viu.mutation.Solutions(); ok { + _spec.SetField(vulninformation.FieldSolutions, field.TypeString, value) + } + if value, ok := viu.mutation.References(); ok { + _spec.SetField(vulninformation.FieldReferences, field.TypeJSON, value) + } + if value, ok := viu.mutation.AppendedReferences(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, vulninformation.FieldReferences, value) + }) + } + if viu.mutation.ReferencesCleared() { + _spec.ClearField(vulninformation.FieldReferences, field.TypeJSON) + } + if value, ok := viu.mutation.Tags(); ok { + _spec.SetField(vulninformation.FieldTags, field.TypeJSON, value) + } + if value, ok := viu.mutation.AppendedTags(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, vulninformation.FieldTags, value) + }) + } + if viu.mutation.TagsCleared() { + _spec.ClearField(vulninformation.FieldTags, field.TypeJSON) + } + if value, ok := viu.mutation.From(); ok { + _spec.SetField(vulninformation.FieldFrom, field.TypeString, value) + } + if n, err = sqlgraph.UpdateNodes(ctx, viu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{vulninformation.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + viu.mutation.done = true + return n, nil +} + +// VulnInformationUpdateOne is the builder for updating a single VulnInformation entity. +type VulnInformationUpdateOne struct { + config + fields []string + hooks []Hook + mutation *VulnInformationMutation +} + +// SetKey sets the "key" field. +func (viuo *VulnInformationUpdateOne) SetKey(s string) *VulnInformationUpdateOne { + viuo.mutation.SetKey(s) + return viuo +} + +// SetTitle sets the "title" field. +func (viuo *VulnInformationUpdateOne) SetTitle(s string) *VulnInformationUpdateOne { + viuo.mutation.SetTitle(s) + return viuo +} + +// SetNillableTitle sets the "title" field if the given value is not nil. +func (viuo *VulnInformationUpdateOne) SetNillableTitle(s *string) *VulnInformationUpdateOne { + if s != nil { + viuo.SetTitle(*s) + } + return viuo +} + +// SetDescription sets the "description" field. +func (viuo *VulnInformationUpdateOne) SetDescription(s string) *VulnInformationUpdateOne { + viuo.mutation.SetDescription(s) + return viuo +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (viuo *VulnInformationUpdateOne) SetNillableDescription(s *string) *VulnInformationUpdateOne { + if s != nil { + viuo.SetDescription(*s) + } + return viuo +} + +// SetSeverity sets the "severity" field. +func (viuo *VulnInformationUpdateOne) SetSeverity(s string) *VulnInformationUpdateOne { + viuo.mutation.SetSeverity(s) + return viuo +} + +// SetNillableSeverity sets the "severity" field if the given value is not nil. +func (viuo *VulnInformationUpdateOne) SetNillableSeverity(s *string) *VulnInformationUpdateOne { + if s != nil { + viuo.SetSeverity(*s) + } + return viuo +} + +// SetCve sets the "cve" field. +func (viuo *VulnInformationUpdateOne) SetCve(s string) *VulnInformationUpdateOne { + viuo.mutation.SetCve(s) + return viuo +} + +// SetNillableCve sets the "cve" field if the given value is not nil. +func (viuo *VulnInformationUpdateOne) SetNillableCve(s *string) *VulnInformationUpdateOne { + if s != nil { + viuo.SetCve(*s) + } + return viuo +} + +// SetDisclosure sets the "disclosure" field. +func (viuo *VulnInformationUpdateOne) SetDisclosure(s string) *VulnInformationUpdateOne { + viuo.mutation.SetDisclosure(s) + return viuo +} + +// SetNillableDisclosure sets the "disclosure" field if the given value is not nil. +func (viuo *VulnInformationUpdateOne) SetNillableDisclosure(s *string) *VulnInformationUpdateOne { + if s != nil { + viuo.SetDisclosure(*s) + } + return viuo +} + +// SetSolutions sets the "solutions" field. +func (viuo *VulnInformationUpdateOne) SetSolutions(s string) *VulnInformationUpdateOne { + viuo.mutation.SetSolutions(s) + return viuo +} + +// SetNillableSolutions sets the "solutions" field if the given value is not nil. +func (viuo *VulnInformationUpdateOne) SetNillableSolutions(s *string) *VulnInformationUpdateOne { + if s != nil { + viuo.SetSolutions(*s) + } + return viuo +} + +// SetReferences sets the "references" field. +func (viuo *VulnInformationUpdateOne) SetReferences(s []string) *VulnInformationUpdateOne { + viuo.mutation.SetReferences(s) + return viuo +} + +// AppendReferences appends s to the "references" field. +func (viuo *VulnInformationUpdateOne) AppendReferences(s []string) *VulnInformationUpdateOne { + viuo.mutation.AppendReferences(s) + return viuo +} + +// ClearReferences clears the value of the "references" field. +func (viuo *VulnInformationUpdateOne) ClearReferences() *VulnInformationUpdateOne { + viuo.mutation.ClearReferences() + return viuo +} + +// SetTags sets the "tags" field. +func (viuo *VulnInformationUpdateOne) SetTags(s []string) *VulnInformationUpdateOne { + viuo.mutation.SetTags(s) + return viuo +} + +// AppendTags appends s to the "tags" field. +func (viuo *VulnInformationUpdateOne) AppendTags(s []string) *VulnInformationUpdateOne { + viuo.mutation.AppendTags(s) + return viuo +} + +// ClearTags clears the value of the "tags" field. +func (viuo *VulnInformationUpdateOne) ClearTags() *VulnInformationUpdateOne { + viuo.mutation.ClearTags() + return viuo +} + +// SetFrom sets the "from" field. +func (viuo *VulnInformationUpdateOne) SetFrom(s string) *VulnInformationUpdateOne { + viuo.mutation.SetFrom(s) + return viuo +} + +// SetNillableFrom sets the "from" field if the given value is not nil. +func (viuo *VulnInformationUpdateOne) SetNillableFrom(s *string) *VulnInformationUpdateOne { + if s != nil { + viuo.SetFrom(*s) + } + return viuo +} + +// Mutation returns the VulnInformationMutation object of the builder. +func (viuo *VulnInformationUpdateOne) Mutation() *VulnInformationMutation { + return viuo.mutation +} + +// Where appends a list predicates to the VulnInformationUpdate builder. +func (viuo *VulnInformationUpdateOne) Where(ps ...predicate.VulnInformation) *VulnInformationUpdateOne { + viuo.mutation.Where(ps...) + return viuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (viuo *VulnInformationUpdateOne) Select(field string, fields ...string) *VulnInformationUpdateOne { + viuo.fields = append([]string{field}, fields...) + return viuo +} + +// Save executes the query and returns the updated VulnInformation entity. +func (viuo *VulnInformationUpdateOne) Save(ctx context.Context) (*VulnInformation, error) { + return withHooks[*VulnInformation, VulnInformationMutation](ctx, viuo.sqlSave, viuo.mutation, viuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (viuo *VulnInformationUpdateOne) SaveX(ctx context.Context) *VulnInformation { + node, err := viuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (viuo *VulnInformationUpdateOne) Exec(ctx context.Context) error { + _, err := viuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (viuo *VulnInformationUpdateOne) ExecX(ctx context.Context) { + if err := viuo.Exec(ctx); err != nil { + panic(err) + } +} + +func (viuo *VulnInformationUpdateOne) sqlSave(ctx context.Context) (_node *VulnInformation, err error) { + _spec := sqlgraph.NewUpdateSpec(vulninformation.Table, vulninformation.Columns, sqlgraph.NewFieldSpec(vulninformation.FieldID, field.TypeInt)) + id, ok := viuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "VulnInformation.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := viuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, vulninformation.FieldID) + for _, f := range fields { + if !vulninformation.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != vulninformation.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := viuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := viuo.mutation.Key(); ok { + _spec.SetField(vulninformation.FieldKey, field.TypeString, value) + } + if value, ok := viuo.mutation.Title(); ok { + _spec.SetField(vulninformation.FieldTitle, field.TypeString, value) + } + if value, ok := viuo.mutation.Description(); ok { + _spec.SetField(vulninformation.FieldDescription, field.TypeString, value) + } + if value, ok := viuo.mutation.Severity(); ok { + _spec.SetField(vulninformation.FieldSeverity, field.TypeString, value) + } + if value, ok := viuo.mutation.Cve(); ok { + _spec.SetField(vulninformation.FieldCve, field.TypeString, value) + } + if value, ok := viuo.mutation.Disclosure(); ok { + _spec.SetField(vulninformation.FieldDisclosure, field.TypeString, value) + } + if value, ok := viuo.mutation.Solutions(); ok { + _spec.SetField(vulninformation.FieldSolutions, field.TypeString, value) + } + if value, ok := viuo.mutation.References(); ok { + _spec.SetField(vulninformation.FieldReferences, field.TypeJSON, value) + } + if value, ok := viuo.mutation.AppendedReferences(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, vulninformation.FieldReferences, value) + }) + } + if viuo.mutation.ReferencesCleared() { + _spec.ClearField(vulninformation.FieldReferences, field.TypeJSON) + } + if value, ok := viuo.mutation.Tags(); ok { + _spec.SetField(vulninformation.FieldTags, field.TypeJSON, value) + } + if value, ok := viuo.mutation.AppendedTags(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, vulninformation.FieldTags, value) + }) + } + if viuo.mutation.TagsCleared() { + _spec.ClearField(vulninformation.FieldTags, field.TypeJSON) + } + if value, ok := viuo.mutation.From(); ok { + _spec.SetField(vulninformation.FieldFrom, field.TypeString, value) + } + _node = &VulnInformation{config: viuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, viuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{vulninformation.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + viuo.mutation.done = true + return _node, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..87a05d4 --- /dev/null +++ b/go.mod @@ -0,0 +1,63 @@ +module github.com/zema1/watchvuln + +go 1.19 + +require ( + entgo.io/ent v0.11.10 + github.com/CatchZeng/dingtalk v1.5.0 + github.com/PuerkitoBio/goquery v1.8.1 + github.com/imroc/req/v3 v3.33.1 + github.com/kataras/golog v0.1.8 + github.com/pkg/errors v0.9.1 + github.com/urfave/cli/v2 v2.25.0 + golang.org/x/net v0.7.0 + golang.org/x/sync v0.1.0 + modernc.org/sqlite v1.21.0 +) + +require ( + ariga.io/atlas v0.9.2-0.20230303073438-03a4779a6338 // indirect + github.com/agext/levenshtein v1.2.1 // indirect + github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-openapi/inflect v0.19.0 // indirect + github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/hcl/v2 v2.13.0 // indirect + github.com/kataras/pio v0.0.11 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect + github.com/onsi/ginkgo/v2 v2.2.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qtls-go1-18 v0.2.0 // indirect + github.com/quic-go/qtls-go1-19 v0.2.0 // indirect + github.com/quic-go/qtls-go1-20 v0.1.0 // indirect + github.com/quic-go/quic-go v0.32.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + github.com/zclconf/go-cty v1.8.0 // indirect + golang.org/x/crypto v0.4.0 // indirect + golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.7.0 // indirect + golang.org/x/tools v0.6.1-0.20230222164832-25d2519c8696 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.3 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3867fb0 --- /dev/null +++ b/go.sum @@ -0,0 +1,190 @@ +ariga.io/atlas v0.9.2-0.20230303073438-03a4779a6338 h1:8kmSV3mbQKn0niZ/EdE11uhFvFKiW1VlaqVBIYOyahM= +ariga.io/atlas v0.9.2-0.20230303073438-03a4779a6338/go.mod h1:T230JFcENj4ZZzMkZrXFDSkv+2kXkUgpJ5FQQ5hMcKU= +bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI= +entgo.io/ent v0.11.10 h1:iqn32ybY5HRW3xSAyMNdNKpZhKgMf1Zunsej9yPKUI8= +entgo.io/ent v0.11.10/go.mod h1:mzTZ0trE+jCQw/fnzijbm5Mck/l8Gbg7gC/+L1COyzM= +github.com/CatchZeng/dingtalk v1.5.0 h1:3YNpGIdeZ4pTfjjVyC2G1CCzWocMmYniBY8CXiOY9Ss= +github.com/CatchZeng/dingtalk v1.5.0/go.mod h1:/CT0Eskr26XfjAybLBLV+DjvPHaznwiFXRBndQp7zZ8= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= +github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= +github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= +github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= +github.com/imroc/req/v3 v3.33.1 h1:BZnyl+K0hXcJlZBHY2CqbPgmVc1pPJDzjn6aJfB6shI= +github.com/imroc/req/v3 v3.33.1/go.mod h1:cZ+7C3L/AYOr4tLGG16hZF90F1WzAdAdzt1xFSlizXY= +github.com/kataras/golog v0.1.8 h1:isP8th4PJH2SrbkciKnylaND9xoTtfxv++NB+DF0l9g= +github.com/kataras/golog v0.1.8/go.mod h1:rGPAin4hYROfk1qT9wZP6VY2rsb4zzc37QpdPjdkqVw= +github.com/kataras/pio v0.0.11 h1:kqreJ5KOEXGMwHAWHDwIl+mjfNCPhAwZPa8gK7MKlyw= +github.com/kataras/pio v0.0.11/go.mod h1:38hH6SWH6m4DKSYmRhlrCJ5WItwWgCVrTNU62XZyUvI= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= +github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U= +github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc= +github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk= +github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI= +github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA= +github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/urfave/cli/v2 v2.25.0 h1:ykdZKuQey2zq0yin/l7JOm9Mh+pg72ngYMeB0ABn6q8= +github.com/urfave/cli/v2 v2.25.0/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA= +github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= +golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.1-0.20230222164832-25d2519c8696 h1:8985/C5IvACpd9DDXckSnjSBLKDgbxXiyODgi94zOPM= +golang.org/x/tools v0.6.1-0.20230222164832-25d2519c8696/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= +modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= +modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= diff --git a/grab/avd.go b/grab/avd.go new file mode 100644 index 0000000..3a02648 --- /dev/null +++ b/grab/avd.go @@ -0,0 +1,258 @@ +package grab + +import ( + "bytes" + "context" + "fmt" + "github.com/PuerkitoBio/goquery" + "github.com/imroc/req/v3" + "github.com/kataras/golog" + "golang.org/x/net/html" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +var ( + cveIDRegexp = regexp.MustCompile(`^CVE-\d+-\d+$`) + pageRegexp = regexp.MustCompile(`第 \d+ 页 / (\d+) 页 `) +) + +type AVDCrawler struct { + client *req.Client + log *golog.Logger +} + +func NewAVDCrawler() Grabber { + client := NewHttpClient() + return &AVDCrawler{ + client: client, + log: golog.Child("[aliyun-avd]"), + } +} +func (a *AVDCrawler) SourceInfo() *SourceInfo { + return &SourceInfo{ + Name: "aliyun-avd", + DisplayName: "阿里云漏洞库", + Link: "https://avd.aliyun.com/high-risk/list", + } +} +func (a *AVDCrawler) GetPageCount(ctx context.Context, _ int) (int, error) { + u := `https://avd.aliyun.com/high-risk/list` + resp, err := a.client.R().SetContext(ctx).Get(u) + if err != nil { + return 0, err + } + results := pageRegexp.FindStringSubmatch(resp.String()) + if len(results) != 2 { + return 0, fmt.Errorf("failed to match page count") + } + return strconv.Atoi(results[1]) +} + +func (a *AVDCrawler) ParsePage(ctx context.Context, page, _ int) (chan *VulnInfo, error) { + u := fmt.Sprintf("https://avd.aliyun.com/high-risk/list?page=%d", page) + a.log.Infof("parsing page %s", u) + resp, err := a.client.R().SetContext(ctx).Get(u) + if err != nil { + return nil, err + } + doc, err := goquery.NewDocumentFromReader(bytes.NewReader(resp.Bytes())) + if err != nil { + return nil, err + } + sel := doc.Find("tbody > tr") + count := sel.Length() + if count == 0 { + return nil, fmt.Errorf("goquery find zero vulns") + } + a.log.Infof("page %d contains %d vulns", page, count) + + hrefs := make([]string, 0, count) + for i := 0; i < count; i++ { + linkSel := sel.Eq(i).Find("td > a") + if linkSel.Length() != 1 { + return nil, fmt.Errorf("can't find a tag") + } + + linkTag := linkSel.Get(0) + for _, attr := range linkTag.Attr { + if attr.Key == "href" { + hrefs = append(hrefs, attr.Val) + break + } + } + } + + if len(hrefs) != count { + return nil, fmt.Errorf("can't get all href") + } + + results := make(chan *VulnInfo, 1) + go func() { + defer close(results) + for _, href := range hrefs { + select { + case <-ctx.Done(): + return + default: + } + base, _ := url.Parse("https://avd.aliyun.com/") + uri, err := url.ParseRequestURI(href) + if err != nil { + a.log.Errorf("%s", err) + return + } + vulnLink := base.ResolveReference(uri).String() + avdInfo, err := a.parseSingle(ctx, vulnLink) + if err != nil { + a.log.Errorf("%s %s", err, vulnLink) + return + } + results <- avdInfo + } + }() + + return results, nil +} + +func (a *AVDCrawler) IsValuable(info *VulnInfo) bool { + return info.Severity == High || info.Severity == Critical +} + +func (a *AVDCrawler) parseSingle(ctx context.Context, vulnLink string) (*VulnInfo, error) { + a.log.Debugf("parsing vuln %s", vulnLink) + resp, err := a.client.R().SetContext(ctx).Get(vulnLink) + if err != nil { + return nil, err + } + + doc, err := goquery.NewDocumentFromReader(bytes.NewReader(resp.Bytes())) + if err != nil { + return nil, err + } + + title := "" + description := "" + fixSteps := "" + level := "" + cveID := "" + disclosure := "" + avd := "" + var refs []string + + // parse avd id + u, _ := url.Parse(vulnLink) + avd = strings.TrimSpace(u.Query().Get("id")) + + metaSel := doc.Find(`div[class="metric"]`) + for i := 0; i < metaSel.Length(); i++ { + metric := metaSel.Eq(i) + label := metric.Find(".metric-label").Text() + value := metric.Find(".metric-value").Text() + label = strings.TrimSpace(label) + value = strings.TrimSpace(value) + + if strings.HasPrefix(label, "CVE") { + cveID = value + } else if strings.HasSuffix(label, "披露时间") { + disclosure = value + } else { + } + } + + // validate + if !cveIDRegexp.MatchString(cveID) { + cveID = "" + a.log.Debugf("cve id not found in %s", vulnLink) + } + + _, err = time.Parse("2006-01-02", disclosure) + if err != nil { + disclosure = "" + } + + if cveID == "" && disclosure == "" { + // 数据有问题,不可能两个都是空的 + return nil, fmt.Errorf("invalid vuln data") + } + + // parse title + header := doc.Find(`h5[class="header__title"]`) + level = header.Find(".badge").Text() + level = strings.TrimSpace(level) + title = header.Find(".header__title__text").Text() + title = strings.TrimSpace(title) + + // parse main content + mainContent := doc.Find(`div[class="py-4 pl-4 pr-4 px-2 bg-white rounded shadow-sm"]`).Children() + for i := 0; i < mainContent.Length(); { + sentinel := mainContent.Eq(i).Text() + sentinel = strings.TrimSpace(sentinel) + + if sentinel == "漏洞描述" && i+1 < mainContent.Length() { + description = mainContent.Eq(i + 1).Find("div").Eq(0).Text() + description = strings.TrimSpace(description) + i += 2 + } else if sentinel == "解决建议" && i+1 < mainContent.Length() { + if mainContent.Eq(i+1).Length() != 1 { + i += 2 + continue + } + // 解决一下换行问题 + innerNode := mainContent.Eq(i + 1).Nodes[0].FirstChild + for ; innerNode != nil; innerNode = innerNode.NextSibling { + if innerNode.Type != html.TextNode { + continue + } + t := strings.TrimSpace(innerNode.Data) + if t != "" { + fixSteps += t + "\n" + } + } + fixSteps = strings.TrimSpace(fixSteps) + fixSteps = strings.ReplaceAll(fixSteps, "、", ". ") + i += 2 + } else { + i += 1 + } + } + refTags := mainContent.Find(`div.reference tbody > tr a`) + for i := 0; i < refTags.Length(); i++ { + refText, exist := refTags.Eq(i).Attr("href") + if !exist { + continue + } + refText = strings.TrimSpace(refText) + if strings.HasPrefix(refText, "http") { + refs = append(refs, refText) + } + } + severity := Low + switch level { + case "低危": + severity = Low + case "中危": + severity = Medium + case "高危": + severity = High + case "严重": + severity = Critical + } + + data := &VulnInfo{ + UniqueKey: avd, + Title: title, + Description: description, + Severity: severity, + CVE: cveID, + Disclosure: disclosure, + References: refs, + Solutions: fixSteps, + From: vulnLink, + Creator: a, + } + return data, nil +} diff --git a/grab/grab.go b/grab/grab.go new file mode 100644 index 0000000..4d9c9a6 --- /dev/null +++ b/grab/grab.go @@ -0,0 +1,87 @@ +package grab + +import ( + "context" + "errors" + "fmt" + "github.com/imroc/req/v3" + "github.com/kataras/golog" + "time" +) + +type SeverityLevel string + +const ( + Low SeverityLevel = "低危" + Medium SeverityLevel = "中危" + High SeverityLevel = "高危" + Critical SeverityLevel = "严重" +) + +type VulnInfo struct { + UniqueKey string `json:"unique_key"` + Title string `json:"title"` + Description string `json:"description"` + Severity SeverityLevel `json:"severity"` + CVE string `json:"cve"` + Disclosure string `json:"disclosure"` + Solutions string `json:"solutions"` + References []string `json:"references"` + Tags []string `json:"tags"` + From string `json:"from"` + + Creator Grabber `json:"-"` +} + +func (v *VulnInfo) String() string { + return fmt.Sprintf("%s (%s)", v.Title, v.From) +} + +type SourceInfo struct { + Name string + DisplayName string + Link string +} + +type Grabber interface { + SourceInfo() *SourceInfo + GetPageCount(ctx context.Context, size int) (int, error) + ParsePage(ctx context.Context, page int, size int) (chan *VulnInfo, error) + IsValuable(info *VulnInfo) bool +} + +func NewHttpClient() *req.Client { + client := req.C() + client. + SetCommonHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51"). + SetTimeout(10*time.Second). + SetCommonRetryCount(3). + SetCookieJar(nil). + SetCommonRetryBackoffInterval(1*time.Second, 5*time.Second). + SetCommonRetryHook(func(resp *req.Response, err error) { + golog.Warnf("retrying as %s", err) + }).SetCommonRetryCondition(func(resp *req.Response, err error) bool { + if err != nil { + if errors.Is(err, context.Canceled) { + return false + } + return true + } + return false + }) + return client +} + +func wrapApiClient(client *req.Client) *req.Client { + return client.SetCommonHeaders(map[string]string{ + "Accept": "application/json, text/plain, */*", + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", + "Content-Type": "application/json", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "sec-ch-ua": `"Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"`, + "sec-ch-ua-mobile": `?0`, + "sec-ch-ua-platform": `"Windows"`, + }) +} diff --git a/grab/oscs.go b/grab/oscs.go new file mode 100644 index 0000000..8b24041 --- /dev/null +++ b/grab/oscs.go @@ -0,0 +1,258 @@ +package grab + +import ( + "context" + "encoding/json" + "fmt" + "github.com/imroc/req/v3" + "github.com/kataras/golog" + "time" +) + +type OSCSCrawler struct { + client *req.Client + log *golog.Logger +} + +func NewOSCSCrawler() Grabber { + client := wrapApiClient(NewHttpClient()) + client.SetCommonHeader("Referer", "https://www.oscs1024.com/cm") + client.SetCommonHeader("Origin", "https://www.oscs1024.com") + c := &OSCSCrawler{ + client: client, + log: golog.Child("[oscs]"), + } + return c +} + +func (t *OSCSCrawler) SourceInfo() *SourceInfo { + return &SourceInfo{ + Name: "oscs", + DisplayName: "OSCS开源安全情报预警", + Link: "https://www.oscs1024.com/cm", + } +} + +func (t *OSCSCrawler) GetPageCount(ctx context.Context, size int) (int, error) { + resp, err := t.client.R(). + SetBodyBytes(t.buildListBody(1, 10)). + SetContext(ctx). + Post("https://www.oscs1024.com/oscs/v1/intelligence/list") + if err != nil { + return 0, err + } + var body oscsListResp + if err = resp.UnmarshalJson(&body); err != nil { + return 0, err + } + if body.Code != 200 || !body.Success { + return 0, fmt.Errorf("failed to get page count, msg: %s", body.Info) + } + total := body.Data.Total + pageCount := total / size + if total%pageCount != 0 { + pageCount += 1 + } + return pageCount, nil +} + +func (t *OSCSCrawler) ParsePage(ctx context.Context, page, size int) (chan *VulnInfo, error) { + t.log.Infof("parsing page %d", page) + resp, err := t.client.R(). + SetContext(ctx). + SetBodyBytes(t.buildListBody(page, size)). + Post("https://www.oscs1024.com/oscs/v1/intelligence/list") + if err != nil { + return nil, err + } + var body oscsListResp + if err = resp.UnmarshalJson(&body); err != nil { + return nil, err + } + t.log.Infof("page %d contains %d vulns", page, len(body.Data.Data)) + result := make(chan *VulnInfo, 1) + go func() { + defer close(result) + for _, d := range body.Data.Data { + select { + case <-ctx.Done(): + return + default: + } + + var tags []string + if d.IsPush == 1 { + tags = append(tags, "发布预警") + } + eventType := "公开漏洞" + switch d.IntelligenceType { + case 1: + eventType = "公开漏洞" + case 2: + eventType = "墨菲安全独家" + case 3: + eventType = "投毒情报" + } + tags = append(tags, eventType) + info, err := t.parseSingeVuln(ctx, d.Mps) + if err != nil { + t.log.Errorf("failed to parse %s, %s", d.Url, err) + continue + } + info.Tags = tags + result <- info + } + }() + return result, nil +} + +func (t *OSCSCrawler) IsValuable(info *VulnInfo) bool { + // 仅有预警的 或高危严重的 + if info.Severity == Critical { + return true + } + for _, tag := range info.Tags { + if tag == "发布预警" { + return true + } + } + return false +} + +func (t *OSCSCrawler) parseSingeVuln(ctx context.Context, mps string) (*VulnInfo, error) { + resp, err := t.client.R(). + SetContext(ctx). + SetBodyString(fmt.Sprintf(`{"vuln_no":"%s"}`, mps)). + Post("https://www.oscs1024.com/oscs/v1/vdb/info") + if err != nil { + return nil, err + } + var respBody oscsDetailResp + if err = resp.UnmarshalJson(&respBody); err != nil { + return nil, err + } + if respBody.Code != 200 || !respBody.Success || len(respBody.Data) == 0 { + return nil, fmt.Errorf("response error %s", respBody.Info) + } + data := respBody.Data[0] + severity := Low + switch data.Level { + case "Critical": + severity = Critical + case "High": + severity = High + case "Medium": + severity = Medium + case "Low": + severity = Low + } + disclosure := time.UnixMilli(int64(data.PublishTime)).Format("2006-01-02") + refs := make([]string, 0, len(data.References)) + for _, ref := range data.References { + refs = append(refs, ref.Url) + } + + // oscs 的修复方式是根据版本自动生成的没什么价值 + info := &VulnInfo{ + UniqueKey: data.VulnNo, + Title: data.VulnTitle, + Description: data.Description, + Severity: severity, + CVE: data.CveId, + Disclosure: disclosure, + References: refs, + Tags: nil, + Solutions: "", + From: t.SourceInfo().Link, + Creator: t, + } + return info, nil +} + +func (t *OSCSCrawler) buildListBody(page, size int) []byte { + m := map[string]interface{}{ + "page": page, + "per_page": size, + } + data, _ := json.Marshal(m) + return data +} + +type oscsListResp struct { + Data struct { + Total int `json:"total"` + Data []*struct { + Project []interface{} `json:"project"` + Id string `json:"id"` + Title string `json:"title"` + Url string `json:"url"` + Mps string `json:"mps"` + IntelligenceType int `json:"intelligence_type"` + PublicTime time.Time `json:"public_time"` + IsPush int `json:"is_push"` + IsPoc int `json:"is_poc"` + IsExp int `json:"is_exp"` + Level string `json:"level"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + IsSubscribe int `json:"is_subscribe"` + } `json:"data"` + } `json:"data"` + Success bool `json:"success"` + Code int `json:"code"` + Time int `json:"time"` + Info string `json:"info"` +} +type oscsDetailResp struct { + Data []*struct { + AttackVector string `json:"attack_vector"` + CvssVector string `json:"cvss_vector"` + Exp bool `json:"exp"` + ExploitRequirementCost string `json:"exploit_requirement_cost"` + Exploitability string `json:"exploitability"` + ScopeInfluence string `json:"scope_influence"` + Source string `json:"source"` + VulnType string `json:"vuln_type"` + CvssScore float64 `json:"cvss_score"` + CveId string `json:"cve_id"` + VulnCveId string `json:"vuln_cve_id"` + CnvdId string `json:"cnvd_id"` + IsOrigin bool `json:"is_origin"` + Languages []interface{} `json:"languages"` + Description string `json:"description"` + Effect []struct { + AffectedAllVersion bool `json:"affected_all_version"` + AffectedVersion string `json:"affected_version"` + EffectId int `json:"effect_id"` + JavaQnList []interface{} `json:"java_qn_list"` + MinFixedVersion string `json:"min_fixed_version"` + Name string `json:"name"` + Solutions []struct { + Compatibility int `json:"compatibility"` + Description string `json:"description"` + Type string `json:"type"` + } `json:"solutions"` + } `json:"effect"` + Influence int `json:"influence"` + Level string `json:"level"` + Patch string `json:"patch"` + Poc bool `json:"poc"` + PublishTime int64 `json:"publish_time"` + References []struct { + Name string `json:"name"` + Url string `json:"url"` + } `json:"references"` + SuggestLevel string `json:"suggest_level"` + VulnSuggest string `json:"vuln_suggest"` + Title string `json:"title"` + Troubleshooting []string `json:"troubleshooting"` + VulnTitle string `json:"vuln_title"` + VulnCodeUsage []interface{} `json:"vuln_code_usage"` + VulnNo string `json:"vuln_no"` + SoulutionData []string `json:"soulution_data"` + } `json:"data"` + Success bool `json:"success"` + Code int `json:"code"` + Time int `json:"time"` + Info string `json:"info"` +} diff --git a/grab/ti.go b/grab/ti.go new file mode 100644 index 0000000..397a6cb --- /dev/null +++ b/grab/ti.go @@ -0,0 +1,185 @@ +package grab + +import ( + "context" + "encoding/json" + "fmt" + "github.com/imroc/req/v3" + "github.com/kataras/golog" + "strings" +) + +type TiCrawler struct { + client *req.Client + log *golog.Logger +} + +func NewTiCrawler() Grabber { + client := wrapApiClient(NewHttpClient()) + client.SetCommonHeader("Referer", "https://ti.qianxin.com/vulnerability") + client.SetCommonHeader("Origin", "https://ti.qianxin.com") + c := &TiCrawler{ + log: golog.Child("[qianxin-ti]"), + client: client, + } + return c +} + +func (t *TiCrawler) SourceInfo() *SourceInfo { + return &SourceInfo{ + Name: "qianxin-ti", + DisplayName: "奇安信威胁情报中心", + Link: "https://ti.qianxin.com/vulnerability", + } +} + +func (t *TiCrawler) GetPageCount(ctx context.Context, size int) (int, error) { + resp, err := t.client.R(). + SetBodyBytes(t.buildBody(1, 10)). + SetContext(ctx). + Post("https://ti.qianxin.com/alpha-api/v2/nox/api/web/portal/key_vuln/list") + if err != nil { + return 0, err + } + var body tiListResp + if err = resp.UnmarshalJson(&body); err != nil { + return 0, err + } + if body.Status != 10000 { + return 0, fmt.Errorf("failed to get page count, msg: %s", body.Message) + } + total := body.Data.Total + pageCount := total / size + if total%pageCount != 0 { + pageCount += 1 + } + return pageCount, nil +} + +func (t *TiCrawler) ParsePage(ctx context.Context, page, size int) (chan *VulnInfo, error) { + t.log.Infof("parsing page %d", page) + resp, err := t.client.R(). + SetContext(ctx). + SetBodyBytes(t.buildBody(page, size)). + Post("https://ti.qianxin.com/alpha-api/v2/nox/api/web/portal/key_vuln/list") + if err != nil { + return nil, err + } + var body tiListResp + if err = resp.UnmarshalJson(&body); err != nil { + return nil, err + } + t.log.Infof("page %d contains %d vulns", page, len(body.Data.Data)) + result := make(chan *VulnInfo, 1) + go func() { + defer close(result) + for _, d := range body.Data.Data { + select { + case <-ctx.Done(): + return + default: + } + + tags := make([]string, 0, len(d.Tag)) + for _, tag := range d.Tag { + tags = append(tags, strings.TrimSpace(tag.Name)) + } + severity := Low + switch d.RatingLevel { + case "低危": + severity = Low + case "中危": + severity = Medium + case "高危": + severity = High + case "极危": + severity = Critical + } + info := &VulnInfo{ + UniqueKey: d.QvdCode, + Title: d.VulnName, + Description: d.Description, + Severity: severity, + CVE: d.CveCode, + Disclosure: d.PublishTime, + References: []string{}, + Tags: tags, + Solutions: "", + From: t.SourceInfo().Link, + Creator: t, + } + result <- info + } + }() + return result, nil +} + +func (t *TiCrawler) IsValuable(info *VulnInfo) bool { + if info.Severity != High && info.Severity != Critical { + return false + } + for _, tag := range info.Tags { + if tag == "奇安信CERT验证" || + tag == "POC公开" || + tag == "技术细节公布" { + return true + } + } + return false +} + +func (t *TiCrawler) buildBody(page, size int) []byte { + m := map[string]interface{}{ + "page_no": page, + "page_size": size, + "vuln_keyword": "", + } + data, _ := json.Marshal(m) + return data +} + +type tiListResp struct { + Status int `json:"status"` + Message string `json:"message"` + Data struct { + Data []*struct { + Id int `json:"id"` + VulnName string `json:"vuln_name"` + VulnNameEn string `json:"vuln_name_en"` + QvdCode string `json:"qvd_code"` + CveCode string `json:"cve_code"` + CnvdId *string `json:"cnvd_id"` + CnnvdId *string `json:"cnnvd_id"` + ThreatCategory string `json:"threat_category"` + TechnicalCategory string `json:"technical_category"` + ResidenceId int `json:"residence_id"` + RatingId int `json:"rating_id"` + NotShow int `json:"not_show"` + PublishTime string `json:"publish_time"` + Description string `json:"description"` + DescriptionEn string `json:"description_en"` + ChangeImpact int `json:"change_impact"` + OperatorHid string `json:"operator_hid"` + CreateHid *string `json:"create_hid"` + Temp int `json:"temp"` + OtherRating int `json:"other_rating"` + CreateTime string `json:"create_time"` + UpdateTime string `json:"update_time"` + LatestUpdateTime string `json:"latest_update_time"` + RatingLevel string `json:"rating_level"` + VulnType string `json:"vuln_type"` + PocFlag int `json:"poc_flag"` + Tag []struct { + Name string `json:"name"` + FontColor string `json:"font_color"` + BackColor string `json:"back_color"` + } `json:"tag"` + UsedFlag int `json:"used_flag"` + PublicFlag int `json:"public_flag"` + MaliciousType string `json:"malicious_type"` + QpeProdName string `json:"qpe_prod_name"` + QpeManufactureName string `json:"qpe_manufacture_name"` + } `json:"data"` + Total int `json:"total"` + } `json:"data"` +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..d8b2a64 --- /dev/null +++ b/main.go @@ -0,0 +1,329 @@ +package main + +import ( + "context" + "database/sql" + entSql "entgo.io/ent/dialect/sql" + "fmt" + "github.com/kataras/golog" + "github.com/pkg/errors" + "github.com/urfave/cli/v2" + "github.com/zema1/watchvuln/ent" + "github.com/zema1/watchvuln/ent/migrate" + "github.com/zema1/watchvuln/ent/vulninformation" + "github.com/zema1/watchvuln/grab" + "github.com/zema1/watchvuln/push" + "golang.org/x/sync/errgroup" + "modernc.org/sqlite" + "os" + "os/signal" + "sync" + "time" +) + +func init() { + sql.Register("sqlite3", &sqlite.Driver{}) +} + +var log = golog.Child("[main]") + +func main() { + golog.Default.SetLevel("info") + app := cli.NewApp() + app.Name = "watchvuln" + app.Usage = "A high valuable vulnerability watcher and pusher" + app.Version = "v0.1.0" + + app.Flags = []cli.Flag{ + &cli.BoolFlag{ + Name: "debug", + Aliases: []string{"d"}, + Usage: "set log level to debug, print more details", + Value: false, + }, + &cli.StringFlag{ + Name: "interval", + Aliases: []string{"i"}, + Usage: "checking every [interval], supported format like 30s, 30m, 1h", + Value: "30m", + }, + &cli.StringFlag{ + Name: "dingding-access-token", + Aliases: []string{"dt"}, + Usage: "access token of dingding bot", + }, + &cli.StringFlag{ + Name: "dingding-sign-secret", + Aliases: []string{"ds"}, + Usage: "sign secret of dingding bot", + }, + } + app.Before = func(c *cli.Context) error { + if c.Bool("debug") { + golog.Default.SetLevel("debug") + } + return nil + } + app.Action = Action + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} + +func Action(c *cli.Context) error { + ctx, cancel := signalCtx() + defer cancel() + + dingToken := c.String("dingding-access-token") + dingSecret := c.String("dingding-sign-secret") + if os.Getenv("DINGDING_ACCESS_TOKEN") != "" { + dingToken = os.Getenv("DINGDING_ACCESS_TOKEN") + } + if os.Getenv("DINGDING_SECRET") != "" { + dingSecret = os.Getenv("DINGDING_SECRET") + } + + if dingToken == "" || dingSecret == "" { + return fmt.Errorf("invalid dingding token, \nusage: %s -dt DINGDING_ACCESS_TOKEN -ds DINGDING_SECRET \nor set via env", os.Args[0]) + } + + debug := c.Bool("debug") + iv := c.String("interval") + if os.Getenv("INTERVAL") != "" { + iv = os.Getenv("INTERVAL") + } + interval, err := time.ParseDuration(iv) + if err != nil { + return err + } + + if interval.Minutes() < 1 && !debug { + return fmt.Errorf("interval is too small, at least 1m") + } + + drv, err := entSql.Open("sqlite3", "file:vuln_v1.sqlite3?cache=shared&_pragma=foreign_keys(1)") + if err != nil { + return errors.Wrap(err, "failed opening connection to sqlite") + } + db := drv.DB() + db.SetMaxOpenConns(1) + dbClient := ent.NewClient(ent.Driver(drv)) + + defer dbClient.Close() + if err := dbClient.Schema.Create(ctx, migrate.WithDropIndex(true), migrate.WithDropColumn(true)); err != nil { + return errors.Wrap(err, "failed creating schema resources") + } + + grabbers := []grab.Grabber{ + grab.NewAVDCrawler(), + grab.NewTiCrawler(), + grab.NewOSCSCrawler(), + } + + count, err := dbClient.VulnInformation.Query().Count(ctx) + if err != nil { + return errors.Wrap(err, "failed creating schema resources") + } + log.Infof("local database has %d vulns", count) + if count < 20000 { + log.Infof("local data is outdated, init database") + eg, initCtx := errgroup.WithContext(ctx) + eg.SetLimit(len(grabbers)) + for _, grabber := range grabbers { + grabber := grabber + eg.Go(func() error { + return initData(initCtx, dbClient, grabber) + }) + } + err = eg.Wait() + if err != nil { + return errors.Wrap(err, "init data") + } + log.Infof("grabber finished successfully, local db has %d vulns", dbClient.VulnInformation.Query().CountX(ctx)) + } + + // 初次启动不要推送数据, 以免长时间没运行狂发消息 + vulns, err := collectUpdate(ctx, dbClient, grabbers) + if err != nil { + return errors.Wrap(err, "initial collect") + } + + log.Infof("system init finished, found %d new vulns (skip pushing)", len(vulns)) + log.Infof("ticking every %s", interval) + + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + log.Infof("next checking at %s\n", time.Now().Add(interval).Format("2006-01-02 15:04:05")) + + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + hour := time.Now().Hour() + if hour > 0 && hour < 7 { + // we must sleep in this period + log.Infof("sleeping..") + continue + } + + vulns, err = collectUpdate(ctx, dbClient, grabbers) + if err != nil { + log.Errorf("failed to get updates, %s", err) + continue + } + log.Infof("found %d new vulns in this ticking", len(vulns)) + for _, v := range vulns { + if v.Creator.IsValuable(v) { + log.Infof("publishing new vuln %s", v) + err = push.DingDingSend(v, dingToken, dingSecret) + if err != nil { + log.Errorf("send dingding msg error, %s", err) + break + } + } + } + } + } +} + +func initData(ctx context.Context, dbClient *ent.Client, grabber grab.Grabber) error { + pageSize := 100 + source := grabber.SourceInfo() + total, err := grabber.GetPageCount(ctx, pageSize) + if err != nil { + return nil + } + log.Infof("%s total page: %d", source.Name, total) + + eg, ctx := errgroup.WithContext(ctx) + eg.SetLimit(20) + + for i := 1; i <= total; i++ { + i := i + eg.Go(func() error { + dataChan, err := grabber.ParsePage(ctx, i, pageSize) + if err != nil { + return err + } + for data := range dataChan { + if _, err = createOrUpdate(ctx, dbClient, source, data); err != nil { + return errors.Wrap(err, data.String()) + } + } + return nil + }) + } + err = eg.Wait() + if err != nil { + return err + } + return nil +} + +func collectUpdate(ctx context.Context, dbClient *ent.Client, grabbers []grab.Grabber) ([]*grab.VulnInfo, error) { + pageSize := 10 + eg, ctx := errgroup.WithContext(ctx) + eg.SetLimit(len(grabbers)) + + var mu sync.Mutex + var newVulns []*grab.VulnInfo + + for _, grabber := range grabbers { + grabber := grabber + eg.Go(func() error { + source := grabber.SourceInfo() + pageCount, err := grabber.GetPageCount(ctx, pageSize) + if err != nil { + return err + } + for i := 1; i <= pageCount; i++ { + dataChan, err := grabber.ParsePage(ctx, i, pageSize) + if err != nil { + return err + } + hasNewVuln := false + + for data := range dataChan { + isNewVuln, err := createOrUpdate(ctx, dbClient, source, data) + if err != nil { + return err + } + if isNewVuln { + log.Infof("found new vuln: %s", data) + mu.Lock() + newVulns = append(newVulns, data) + mu.Unlock() + hasNewVuln = true + } + } + + // 如果一整页漏洞都是旧的,说明没有更新,不必再继续下一页了 + if !hasNewVuln { + return nil + } + } + return nil + }) + } + err := eg.Wait() + return newVulns, err +} + +func createOrUpdate(ctx context.Context, dbClient *ent.Client, source *grab.SourceInfo, data *grab.VulnInfo) (bool, error) { + vuln, err := dbClient.VulnInformation.Query(). + Where(vulninformation.Key(data.UniqueKey)). + First(ctx) + // not exist + if err != nil { + newVuln, err := dbClient.VulnInformation. + Create(). + SetKey(data.UniqueKey). + SetTitle(data.Title). + SetDescription(data.Description). + SetSeverity(string(data.Severity)). + SetCve(data.CVE). + SetDisclosure(data.Disclosure). + SetSolutions(data.Solutions). + SetReferences(data.References). + SetTags(data.Tags). + SetFrom(data.From). + Save(ctx) + if err != nil { + return false, err + } + log.Debugf("vuln %d created from %s %s", newVuln.ID, newVuln.Key, source.Name) + return true, nil + } + + // update + newVuln, err := vuln.Update().SetKey(data.UniqueKey). + SetTitle(data.Title). + SetDescription(data.Description). + SetSeverity(string(data.Severity)). + SetCve(data.CVE). + SetDisclosure(data.Disclosure). + SetSolutions(data.Solutions). + SetReferences(data.References). + SetTags(data.Tags). + SetFrom(data.From). + Save(ctx) + if err != nil { + return false, err + } + log.Debugf("vuln %d updated from %s %s", newVuln.ID, newVuln.Key, source.Name) + return false, nil +} + +func signalCtx() (context.Context, func()) { + ctx, cancel := context.WithCancel(context.Background()) + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt) + go func() { + <-ch + cancel() + }() + return ctx, cancel +} diff --git a/push/dingding.go b/push/dingding.go new file mode 100644 index 0000000..159b5c5 --- /dev/null +++ b/push/dingding.go @@ -0,0 +1,51 @@ +package push + +import ( + "github.com/CatchZeng/dingtalk/pkg/dingtalk" + "github.com/kataras/golog" + "github.com/zema1/watchvuln/grab" + "strings" + "text/template" +) + +const msgTemplate = ` +# {{ .Title }} +  + +- CVE编号: **{{ .CVE }}** +- 危害定级: **{{ .Severity }}** +- 漏洞标签: {{ range .Tags }}**{{ . }}** {{ end }} +- 披露日期: **{{ .Disclosure }}** +- 信息来源: [{{ .From }}]({{ .From }}) + +  +### **漏洞描述** +{{ .Description }} + +  +### **参考链接** +{{ range $i, $ref := .References }} +{{ inc $i }}. [{{ $ref }}]({{ $ref }}) +{{- end }} +` + +func DingDingSend(info *grab.VulnInfo, accessToken, secret string) error { + client := dingtalk.NewClient(accessToken, secret) + + funcMap := template.FuncMap{ + // The name "inc" is what the function will be called in the template text. + "inc": func(i int) int { + return i + 1 + }, + } + + tpl := template.Must(template.New("markdown").Funcs(funcMap).Parse(msgTemplate)) + var builder strings.Builder + if err := tpl.Execute(&builder, info); err != nil { + return err + } + golog.Infof("sending %s", builder.String()) + msg := dingtalk.NewMarkdownMessage().SetMarkdown(info.Title, builder.String()) + _, _, err := client.Send(msg) + return err +}