From be0532d64aa7df62557bb2d0aa0192bd2c760604 Mon Sep 17 00:00:00 2001 From: Richard Boucher <58948528+rboucher-me@users.noreply.github.com> Date: Fri, 2 Aug 2024 15:16:54 -0400 Subject: [PATCH 01/13] Update README.md Fixed inconsistent use of `yml` and `yaml` --- diode-server/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/diode-server/README.md b/diode-server/README.md index 3d74d391..39128f1d 100644 --- a/diode-server/README.md +++ b/diode-server/README.md @@ -40,7 +40,7 @@ Diode server requires Docker version 27.0.3 or above. Diode requires a configuration file and an environment file to execute successfully: -* `docker-compose.yml` - to configure and run the Diode server containers +* `docker-compose.yaml` - to configure and run the Diode server containers * `.env` - to store the specific environmental settings We recommend placing both files in a clean directory: @@ -50,10 +50,10 @@ mkdir /opt/diode cd /opt/diode ``` -Download the default `docker-compose.yml` and `.env` files from this repository: +Download the default `docker-compose.yaml` and `.env` files from this repository: ```bash -curl -o docker-compose.yml https://raw.githubusercontent.com/netboxlabs/diode/develop/diode-server/docker/docker-compose.yaml +curl -o docker-compose.yaml https://raw.githubusercontent.com/netboxlabs/diode/develop/diode-server/docker/docker-compose.yaml curl -o .env https://raw.githubusercontent.com/netboxlabs/diode/develop/diode-server/docker/sample.env ``` From f3eb39d77946449c1b7b129c898d6e3ee8b9bbbc Mon Sep 17 00:00:00 2001 From: Michal Fiedorowicz Date: Mon, 5 Aug 2024 11:55:50 +0100 Subject: [PATCH 02/13] fix: docker - configure redis append only file directory setting to /data to match mounted volume so data is loaded between docker compose down and up also set snapshotting to 60s for at least 1 write operation Signed-off-by: Michal Fiedorowicz --- diode-server/docker/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diode-server/docker/docker-compose.yaml b/diode-server/docker/docker-compose.yaml index 6221e486..480f17db 100644 --- a/diode-server/docker/docker-compose.yaml +++ b/diode-server/docker/docker-compose.yaml @@ -65,7 +65,7 @@ services: command: - sh - -c - - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD --loadmodule /opt/redis-stack/lib/rejson.so --loadmodule /opt/redis-stack/lib/redisearch.so --port $$REDIS_PORT + - redis-server --appendonly yes --dir /data --save 60 1 --requirepass $$REDIS_PASSWORD --loadmodule /opt/redis-stack/lib/rejson.so --loadmodule /opt/redis-stack/lib/redisearch.so --port $$REDIS_PORT environment: - REDIS_PASSWORD=${REDIS_PASSWORD} - REDIS_PORT=${REDIS_PORT} From 7a139e356f7ed60c0ad501c7bcea30d875ea018f Mon Sep 17 00:00:00 2001 From: Leonardo Parente <23251360+leoparente@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:16:03 -0300 Subject: [PATCH 03/13] feat: Add new schema data to ingest-entity (#150) --- diode-server/Makefile | 4 ++-- diode-server/docker/docker-compose.dev.yaml | 10 ++++++++++ diode-server/docker/docker-compose.yaml | 12 +++++++++++- diode-server/reconciler/ingestion_processor.go | 8 ++++++-- .../reconciler/ingestion_processor_internal_test.go | 4 ++-- diode-server/reconciler/ingestion_processor_test.go | 2 +- 6 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 diode-server/docker/docker-compose.dev.yaml diff --git a/diode-server/Makefile b/diode-server/Makefile index fc4926a2..35f31fee 100644 --- a/diode-server/Makefile +++ b/diode-server/Makefile @@ -72,12 +72,12 @@ docker-compose-down: .PHONY: docker-compose-dev-up docker-compose-dev-up: docker-all @DIODE_VERSION=$(DIODE_VERSION) COMMIT_SHA=$(COMMIT_SHA) DIODE_TAG=$(DIODE_VERSION)-$(COMMIT_SHA) PROJECT_NAME=diode-dev \ - $(DOCKER_COMPOSE) --env-file docker/sample.env -f docker/docker-compose.yaml up -d --build + $(DOCKER_COMPOSE) --env-file docker/sample.env -f docker/docker-compose.yaml -f docker/docker-compose.dev.yaml up -d --build .PHONY: docker-compose-dev-down docker-compose-dev-down: @DIODE_VERSION=$(DIODE_VERSION) COMMIT_SHA=$(COMMIT_SHA) DIODE_TAG=$(DIODE_VERSION)-$(COMMIT_SHA) PROJECT_NAME=diode-dev \ - $(DOCKER_COMPOSE) --env-file docker/sample.env -f docker/docker-compose.yaml down --remove-orphans + $(DOCKER_COMPOSE) --env-file docker/sample.env -f docker/docker-compose.yaml -f docker/docker-compose.dev.yaml down --remove-orphans docker-compose-netbox-up: $(DOCKER_COMPOSE) -f docker/docker-compose.netbox.yaml up -d --build diff --git a/diode-server/docker/docker-compose.dev.yaml b/diode-server/docker/docker-compose.dev.yaml new file mode 100644 index 00000000..29379ace --- /dev/null +++ b/diode-server/docker/docker-compose.dev.yaml @@ -0,0 +1,10 @@ +name: ${PROJECT_NAME:-diode} +services: + redis-commander: + hostname: redis-commander + image: ghcr.io/joeferner/redis-commander:latest + restart: always + environment: + - REDIS_HOSTS=local:$REDIS_HOST:$REDIS_PORT:0:$REDIS_PASSWORD + ports: + - "8082:8081" \ No newline at end of file diff --git a/diode-server/docker/docker-compose.yaml b/diode-server/docker/docker-compose.yaml index 480f17db..30a2f587 100644 --- a/diode-server/docker/docker-compose.yaml +++ b/diode-server/docker/docker-compose.yaml @@ -76,7 +76,17 @@ services: image: redis/redis-stack-server:latest links: - diode-redis - command: redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_PASSWORD" FT.CREATE ingest-entity ON JSON PREFIX 1 "ingest-entity:" SCHEMA $$.data_type AS data_type TEXT $$.state AS state NUMERIC + entrypoint: + - sh + - -c + - | + redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_PASSWORD" < Date: Fri, 9 Aug 2024 12:58:06 +0200 Subject: [PATCH 04/13] fix: docker - ingress-nginx listening to correct port (#152) Signed-off-by: Michal Fiedorowicz --- diode-server/docker/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diode-server/docker/docker-compose.yaml b/diode-server/docker/docker-compose.yaml index 30a2f587..313264c2 100644 --- a/diode-server/docker/docker-compose.yaml +++ b/diode-server/docker/docker-compose.yaml @@ -8,7 +8,7 @@ services: } server { - listen ${DIODE_NGINX_PORT}; + listen 80; http2 on; server_name localhost; client_max_body_size 25m; From a3316e53a59af0318e92e8079dca3ef28115dbd6 Mon Sep 17 00:00:00 2001 From: Michal Fiedorowicz Date: Fri, 9 Aug 2024 13:00:20 +0200 Subject: [PATCH 05/13] feat: store reconciliation errors in redis (#153) Signed-off-by: Michal Fiedorowicz --- diode-server/netboxdiodeplugin/client.go | 36 +++++++++++-- diode-server/netboxdiodeplugin/client_test.go | 7 +-- .../reconciler/ingestion_processor.go | 50 +++++++++++-------- 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/diode-server/netboxdiodeplugin/client.go b/diode-server/netboxdiodeplugin/client.go index f5b158bc..1f136832 100644 --- a/diode-server/netboxdiodeplugin/client.go +++ b/diode-server/netboxdiodeplugin/client.go @@ -46,6 +46,9 @@ const ( var ( // ErrInvalidTimeout is an error for invalid timeout value ErrInvalidTimeout = errors.New("invalid timeout value") + + // ErrApplyChangeSetFailed is an error for failed to apply change set + ErrApplyChangeSetFailed = errors.New("failed to apply change set") ) type apiRoundTripper struct { @@ -83,6 +86,28 @@ func (rt *apiRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) return rt.transport.RoundTrip(req2) } +// ApplyChangeSetError represents an error when applying a change set +type ApplyChangeSetError struct { + Message string + Code int + Details ChangeSetResponse +} + +// Error returns the NetBoxDiodePluginError message +func (e *ApplyChangeSetError) Error() string { + detailsErrorsJSON, _ := json.Marshal(e.Details.Errors) + return fmt.Sprintf("msg: %s, code: %d, change set id: %s, result: %s, errors: %s", e.Message, e.Code, e.Details.ChangeSetID, e.Details.Result, detailsErrorsJSON) +} + +// NewApplyChangeSetError creates a new ApplyChangeSetError +func NewApplyChangeSetError(msg string, code int, response ChangeSetResponse) error { + return &ApplyChangeSetError{ + Message: msg, + Code: code, + Details: response, + } +} + // NetBoxAPI is the interface for the NetBox Diode plugin API type NetBoxAPI interface { // RetrieveObjectState retrieves the object state @@ -364,7 +389,7 @@ func (c *Client) ApplyChangeSet(ctx context.Context, payload ChangeSetRequest) ( return nil, err } - c.logger.Info("apply change set", "payload", string(reqBody)) + c.logger.Debug("apply change set", "payload", string(reqBody)) req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpointURL.String(), bytes.NewBuffer(reqBody)) if err != nil { @@ -387,17 +412,18 @@ func (c *Client) ApplyChangeSet(ctx context.Context, payload ChangeSetRequest) ( return nil, fmt.Errorf("failed to read response body %w", err) } - c.logger.Info("apply change set", "response", string(respBytes)) + c.logger.Debug("apply change set", "response", string(respBytes)) var changeSetResponse ChangeSetResponse if err = json.Unmarshal(respBytes, &changeSetResponse); err != nil { return nil, fmt.Errorf("failed to unmarshal response body %w", err) } - if resp.StatusCode != http.StatusOK { - c.logger.Info(fmt.Sprintf("request POST %s failed", req.URL.String()), "statusCode", resp.StatusCode, "response", changeSetResponse) - return &changeSetResponse, fmt.Errorf("request POST %s failed - %q", req.URL.String(), resp.Status) + // return errors with 4xx status code + if resp.StatusCode >= http.StatusBadRequest { + return nil, NewApplyChangeSetError(ErrApplyChangeSetFailed.Error(), resp.StatusCode, changeSetResponse) } + return &changeSetResponse, nil } diff --git a/diode-server/netboxdiodeplugin/client_test.go b/diode-server/netboxdiodeplugin/client_test.go index 46733bf0..97c645d3 100644 --- a/diode-server/netboxdiodeplugin/client_test.go +++ b/diode-server/netboxdiodeplugin/client_test.go @@ -574,11 +574,8 @@ func TestApplyChangeSet(t *testing.T) { }, mockServerResponse: `{"change_set_id":"00000000-0000-0000-0000-000000000000","result":"error"}`, mockStatusCode: http.StatusBadRequest, - response: &netboxdiodeplugin.ChangeSetResponse{ - ChangeSetID: "00000000-0000-0000-0000-000000000000", - Result: "error", - }, - shouldError: true, + response: nil, + shouldError: true, }, { name: "unmarshal error", diff --git a/diode-server/reconciler/ingestion_processor.go b/diode-server/reconciler/ingestion_processor.go index 44d86d19..9a102477 100644 --- a/diode-server/reconciler/ingestion_processor.go +++ b/diode-server/reconciler/ingestion_processor.go @@ -8,7 +8,6 @@ import ( "log/slog" "os" "strconv" - "strings" "github.com/google/uuid" "github.com/kelseyhightower/envconfig" @@ -43,6 +42,9 @@ const ( // IngestEntityStateReconciliationFailed is the state of an entity after it has failed to be reconciled IngestEntityStateReconciliationFailed + + // IngestEntityStateNoChangesToApply is the state of an entity without changes to apply after reconciliation + IngestEntityStateNoChangesToApply ) // RedisClient is an interface that represents the methods used from redis.Client @@ -174,24 +176,24 @@ func (p *IngestionProcessor) handleStreamMessage(ctx context.Context, msg redis. return err } - errs := make([]string, 0) + errs := make([]error, 0) - ingestionTs, err := strconv.Atoi(fmt.Sprintf("%v", msg.Values["ingestion_ts"])) + ingestionTs, err := strconv.Atoi(msg.Values["ingestion_ts"].(string)) if err != nil { - return err + errs = append(errs, fmt.Errorf("failed to convert ingestion timestamp: %v", err)) } p.logger.Debug("handling ingest request", "request", ingestReq) for i, v := range ingestReq.GetEntities() { if v.GetEntity() == nil { - errs = append(errs, fmt.Sprintf("entity at index %d is nil", i)) + errs = append(errs, fmt.Errorf("entity at index %d is nil", i)) continue } objectType, err := extractObjectType(v) if err != nil { - errs = append(errs, fmt.Sprintf("failed to extract data type for index %d: %v", i, err)) + errs = append(errs, fmt.Errorf("failed to extract data type for index %d: %v", i, err)) continue } @@ -212,36 +214,44 @@ func (p *IngestionProcessor) handleStreamMessage(ctx context.Context, msg redis. encodedValue, err := p.writeJSON(ctx, key, val) if err != nil { - errs = append(errs, fmt.Sprintf("failed to write JSON: %v", err)) + errs = append(errs, fmt.Errorf("failed to write JSON: %v", err)) continue } changeSet, err := p.reconcileEntity(ctx, encodedValue) if err != nil { - errs = append(errs, fmt.Sprintf("failed to reconcile entity: %v", err)) + errs = append(errs, err) + val["state"] = IngestEntityStateReconciliationFailed - _, err = p.writeJSON(ctx, key, val) - if err != nil { - errs = append(errs, fmt.Sprintf("failed to write JSON: %v", err)) + val["error"] = err + if _, err = p.writeJSON(ctx, key, val); err != nil { + errs = append(errs, err) } continue } if changeSet != nil { val["state"] = IngestEntityStateReconciled - val["change_set"] = changeSet - _, err = p.writeJSON(ctx, key, val) - if err != nil { - errs = append(errs, fmt.Sprintf("failed to write JSON: %v", err)) - continue - } + val["change_set_id"] = changeSet.ChangeSetID + + } else { + val["state"] = IngestEntityStateNoChangesToApply + } + if _, err = p.writeJSON(ctx, key, val); err != nil { + errs = append(errs, fmt.Errorf("failed to write JSON: %v", err)) + continue } } p.redisStreamClient.XAck(ctx, redisStreamID, redisConsumerGroup, msg.ID) if len(errs) > 0 { - p.logger.Error("failed to handle ingest request", "errors", strings.Join(errs, ", ")) + errsStr := make([]string, 0) + for _, err := range errs { + errsStr = append(errsStr, err.Error()) + } + p.logger.Warn("failed to handle ingest request", slog.String("request_id", ingestReq.Id), slog.Any("errors", errsStr)) + contextMap := map[string]any{ "redis_stream_msg_id": msg.ID, "consumer": fmt.Sprintf("%s-%s", redisConsumerGroup, p.hostname), @@ -273,7 +283,7 @@ func (p *IngestionProcessor) reconcileEntity(ctx context.Context, encodedValue [ } if len(cs.ChangeSet) == 0 { - p.logger.Info("no changes to apply") + p.logger.Debug("no changes to apply", "request_id", ingestEntity.RequestID) return nil, nil } @@ -296,7 +306,7 @@ func (p *IngestionProcessor) reconcileEntity(ctx context.Context, encodedValue [ resp, err := p.nbClient.ApplyChangeSet(ctx, req) if err != nil { - return nil, fmt.Errorf("failed to apply change set: %v", err) + return nil, err } p.logger.Debug("apply change set response", "response", resp) From adcbcfd00214db244afcf50f0f4f3d3c9b308453 Mon Sep 17 00:00:00 2001 From: Leonardo Parente <23251360+leoparente@users.noreply.github.com> Date: Mon, 19 Aug 2024 09:29:44 -0300 Subject: [PATCH 06/13] chore: add BaseDataWrapper to reduce netbox package duplicated code (#156) --- diode-server/netbox/ipam_wrappers.go | 42 +------ diode-server/netbox/wrappers.go | 160 ++++++--------------------- 2 files changed, 39 insertions(+), 163 deletions(-) diff --git a/diode-server/netbox/ipam_wrappers.go b/diode-server/netbox/ipam_wrappers.go index 3d0ea936..0266cab7 100644 --- a/diode-server/netbox/ipam_wrappers.go +++ b/diode-server/netbox/ipam_wrappers.go @@ -10,14 +10,8 @@ import ( // IpamIPAddressDataWrapper represents the IPAM IP address data wrapper type IpamIPAddressDataWrapper struct { + BaseDataWrapper IPAddress *IpamIPAddress - - placeholder bool - hasParent bool - intended bool - hasChanged bool - nestedObjects []ComparableData - objectsToReconcile []ComparableData } func (*IpamIPAddressDataWrapper) comparableData() {} @@ -74,7 +68,7 @@ func (dw *IpamIPAddressDataWrapper) NestedObjects() ([]ComparableData, error) { if dw.IPAddress.AssignedObject != nil { switch dw.IPAddress.AssignedObject.(type) { case *IPAddressInterface: - assignedObject = &DcimInterfaceDataWrapper{Interface: dw.IPAddress.AssignedObject.(*IPAddressInterface).Interface, placeholder: dw.placeholder, hasParent: true, intended: dw.intended} + assignedObject = &DcimInterfaceDataWrapper{Interface: dw.IPAddress.AssignedObject.(*IPAddressInterface).Interface, BaseDataWrapper: BaseDataWrapper{placeholder: dw.placeholder, hasParent: true, intended: dw.intended}} } } @@ -139,16 +133,6 @@ func (dw *IpamIPAddressDataWrapper) ID() int { return dw.IPAddress.ID } -// HasChanged returns true if the data has changed -func (dw *IpamIPAddressDataWrapper) HasChanged() bool { - return dw.hasChanged -} - -// IsPlaceholder returns true if the data is a placeholder -func (dw *IpamIPAddressDataWrapper) IsPlaceholder() bool { - return dw.placeholder -} - func (dw *IpamIPAddressDataWrapper) hash() string { var interfaceName, deviceName, siteName string if dw.IPAddress.AssignedObject != nil { @@ -187,7 +171,7 @@ func (dw *IpamIPAddressDataWrapper) Patch(cmp ComparableData, intendedNestedObje if dw.IPAddress.AssignedObject != nil { switch dw.IPAddress.AssignedObject.(type) { case *IPAddressInterface: - assignedObject := &DcimInterfaceDataWrapper{Interface: dw.IPAddress.AssignedObject.(*IPAddressInterface).Interface, placeholder: dw.placeholder, hasParent: true, intended: dw.intended} + assignedObject := &DcimInterfaceDataWrapper{Interface: dw.IPAddress.AssignedObject.(*IPAddressInterface).Interface, BaseDataWrapper: BaseDataWrapper{placeholder: dw.placeholder, hasParent: true, intended: dw.intended}} actualAssignedObject = extractFromObjectsMap(actualNestedObjectsMap, fmt.Sprintf("%p", assignedObject.Data())) intendedAssignedObject = extractFromObjectsMap(intendedNestedObjects, fmt.Sprintf("%p", assignedObject.Data())) } @@ -360,14 +344,8 @@ func (dw *IpamIPAddressDataWrapper) SetDefaults() { // IpamPrefixDataWrapper represents the IPAM Prefix data wrapper type IpamPrefixDataWrapper struct { + BaseDataWrapper Prefix *IpamPrefix - - placeholder bool - hasParent bool - intended bool - hasChanged bool - nestedObjects []ComparableData - objectsToReconcile []ComparableData } func (*IpamPrefixDataWrapper) comparableData() {} @@ -420,7 +398,7 @@ func (dw *IpamPrefixDataWrapper) NestedObjects() ([]ComparableData, error) { dw.placeholder = true } - site := DcimSiteDataWrapper{Site: dw.Prefix.Site, placeholder: dw.placeholder, hasParent: true, intended: dw.intended} + site := DcimSiteDataWrapper{Site: dw.Prefix.Site, BaseDataWrapper: BaseDataWrapper{placeholder: dw.placeholder, hasParent: true, intended: dw.intended}} so, err := site.NestedObjects() if err != nil { @@ -464,16 +442,6 @@ func (dw *IpamPrefixDataWrapper) ID() int { return dw.Prefix.ID } -// HasChanged returns true if the data has changed -func (dw *IpamPrefixDataWrapper) HasChanged() bool { - return dw.hasChanged -} - -// IsPlaceholder returns true if the data is a placeholder -func (dw *IpamPrefixDataWrapper) IsPlaceholder() bool { - return dw.placeholder -} - // Patch creates patches between the actual, intended and current data func (dw *IpamPrefixDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { intended, ok := cmp.(*IpamPrefixDataWrapper) diff --git a/diode-server/netbox/wrappers.go b/diode-server/netbox/wrappers.go index 4c3e93e8..e71024d2 100644 --- a/diode-server/netbox/wrappers.go +++ b/diode-server/netbox/wrappers.go @@ -48,6 +48,26 @@ type ComparableData interface { HasChanged() bool } +// BaseDataWrapper is the base struct for all data wrappers +type BaseDataWrapper struct { + placeholder bool + hasParent bool + intended bool + hasChanged bool + nestedObjects []ComparableData + objectsToReconcile []ComparableData +} + +// IsPlaceholder returns true if the data is a placeholder +func (bw *BaseDataWrapper) IsPlaceholder() bool { + return bw.placeholder +} + +// HasChanged returns true if the data has changed +func (bw *BaseDataWrapper) HasChanged() bool { + return bw.hasChanged +} + func copyData[T any](srcData *T) (*T, error) { var dstData T if err := copier.Copy(&dstData, srcData); err != nil { @@ -58,14 +78,8 @@ func copyData[T any](srcData *T) (*T, error) { // DcimDeviceDataWrapper represents a DCIM device data wrapper type DcimDeviceDataWrapper struct { + BaseDataWrapper Device *DcimDevice - - placeholder bool - hasParent bool - intended bool - hasChanged bool - nestedObjects []ComparableData - objectsToReconcile []ComparableData } func (*DcimDeviceDataWrapper) comparableData() {} @@ -116,7 +130,7 @@ func (dw *DcimDeviceDataWrapper) NestedObjects() ([]ComparableData, error) { dw.Device.PrimaryIPv4 = nil dw.Device.PrimaryIPv6 = nil - site := DcimSiteDataWrapper{Site: dw.Device.Site, placeholder: dw.placeholder, hasParent: true, intended: dw.intended} + site := DcimSiteDataWrapper{Site: dw.Device.Site, BaseDataWrapper: BaseDataWrapper{placeholder: dw.placeholder, hasParent: true, intended: dw.intended}} so, err := site.NestedObjects() if err != nil { @@ -128,7 +142,7 @@ func (dw *DcimDeviceDataWrapper) NestedObjects() ([]ComparableData, error) { dw.Device.Site = site.Site if dw.Device.Platform != nil { - platform := DcimPlatformDataWrapper{Platform: dw.Device.Platform, placeholder: dw.placeholder, hasParent: true, intended: dw.intended} + platform := DcimPlatformDataWrapper{Platform: dw.Device.Platform, BaseDataWrapper: BaseDataWrapper{placeholder: dw.placeholder, hasParent: true, intended: dw.intended}} po, err := platform.NestedObjects() if err != nil { @@ -140,7 +154,7 @@ func (dw *DcimDeviceDataWrapper) NestedObjects() ([]ComparableData, error) { dw.Device.Platform = platform.Platform } - deviceType := DcimDeviceTypeDataWrapper{DeviceType: dw.Device.DeviceType, placeholder: dw.placeholder, hasParent: true, intended: dw.intended} + deviceType := DcimDeviceTypeDataWrapper{DeviceType: dw.Device.DeviceType, BaseDataWrapper: BaseDataWrapper{placeholder: dw.placeholder, hasParent: true, intended: dw.intended}} dto, err := deviceType.NestedObjects() if err != nil { @@ -151,7 +165,7 @@ func (dw *DcimDeviceDataWrapper) NestedObjects() ([]ComparableData, error) { dw.Device.DeviceType = deviceType.DeviceType - deviceRole := DcimDeviceRoleDataWrapper{DeviceRole: dw.Device.Role, placeholder: dw.placeholder, hasParent: true, intended: dw.intended} + deviceRole := DcimDeviceRoleDataWrapper{DeviceRole: dw.Device.Role, BaseDataWrapper: BaseDataWrapper{placeholder: dw.placeholder, hasParent: true, intended: dw.intended}} dro, err := deviceRole.NestedObjects() if err != nil { @@ -199,16 +213,6 @@ func (dw *DcimDeviceDataWrapper) ID() int { return dw.Device.ID } -// HasChanged returns true if the data has changed -func (dw *DcimDeviceDataWrapper) HasChanged() bool { - return dw.hasChanged -} - -// IsPlaceholder returns true if the data is a placeholder -func (dw *DcimDeviceDataWrapper) IsPlaceholder() bool { - return dw.placeholder -} - // Patch creates patches between the actual, intended and current data func (dw *DcimDeviceDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { intended, ok := cmp.(*DcimDeviceDataWrapper) @@ -554,14 +558,8 @@ func (dw *DcimDeviceDataWrapper) SetDefaults() { // DcimDeviceRoleDataWrapper represents a DCIM device role data wrapper type DcimDeviceRoleDataWrapper struct { + BaseDataWrapper DeviceRole *DcimDeviceRole - - placeholder bool - hasParent bool - intended bool - hasChanged bool - nestedObjects []ComparableData - objectsToReconcile []ComparableData } func (*DcimDeviceRoleDataWrapper) comparableData() {} @@ -645,16 +643,6 @@ func (dw *DcimDeviceRoleDataWrapper) ID() int { return dw.DeviceRole.ID } -// HasChanged returns true if the data has changed -func (dw *DcimDeviceRoleDataWrapper) HasChanged() bool { - return dw.hasChanged -} - -// IsPlaceholder returns true if the data is a placeholder -func (dw *DcimDeviceRoleDataWrapper) IsPlaceholder() bool { - return dw.placeholder -} - // Patch creates patches between the actual, intended and current data func (dw *DcimDeviceRoleDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { intended, ok := cmp.(*DcimDeviceRoleDataWrapper) @@ -732,14 +720,8 @@ func (dw *DcimDeviceRoleDataWrapper) SetDefaults() { // DcimDeviceTypeDataWrapper represents a DCIM device type data wrapper type DcimDeviceTypeDataWrapper struct { + BaseDataWrapper DeviceType *DcimDeviceType - - placeholder bool - hasParent bool - intended bool - hasChanged bool - nestedObjects []ComparableData - objectsToReconcile []ComparableData } func (*DcimDeviceTypeDataWrapper) comparableData() {} @@ -786,16 +768,6 @@ func (dw *DcimDeviceTypeDataWrapper) ID() int { return dw.DeviceType.ID } -// HasChanged returns true if the data has changed -func (dw *DcimDeviceTypeDataWrapper) HasChanged() bool { - return dw.hasChanged -} - -// IsPlaceholder returns true if the data is a placeholder -func (dw *DcimDeviceTypeDataWrapper) IsPlaceholder() bool { - return dw.placeholder -} - // NestedObjects returns all nested objects func (dw *DcimDeviceTypeDataWrapper) NestedObjects() ([]ComparableData, error) { if len(dw.nestedObjects) > 0 { @@ -821,7 +793,7 @@ func (dw *DcimDeviceTypeDataWrapper) NestedObjects() ([]ComparableData, error) { dw.DeviceType.Slug = slug.Make(dw.DeviceType.Model) } - manufacturer := DcimManufacturerDataWrapper{Manufacturer: dw.DeviceType.Manufacturer, placeholder: dw.placeholder, hasParent: true, intended: dw.intended} + manufacturer := DcimManufacturerDataWrapper{Manufacturer: dw.DeviceType.Manufacturer, BaseDataWrapper: BaseDataWrapper{placeholder: dw.placeholder, hasParent: true, intended: dw.intended}} mo, err := manufacturer.NestedObjects() if err != nil { @@ -993,14 +965,8 @@ func (dw *DcimDeviceTypeDataWrapper) SetDefaults() {} // DcimInterfaceDataWrapper represents a DCIM interface data wrapper type DcimInterfaceDataWrapper struct { + BaseDataWrapper Interface *DcimInterface - - placeholder bool - hasParent bool - intended bool - hasChanged bool - nestedObjects []ComparableData - objectsToReconcile []ComparableData } func (*DcimInterfaceDataWrapper) comparableData() {} @@ -1054,7 +1020,7 @@ func (dw *DcimInterfaceDataWrapper) NestedObjects() ([]ComparableData, error) { dw.placeholder = true } - device := DcimDeviceDataWrapper{Device: dw.Interface.Device, placeholder: dw.placeholder, hasParent: true, intended: dw.intended} + device := DcimDeviceDataWrapper{Device: dw.Interface.Device, BaseDataWrapper: BaseDataWrapper{placeholder: dw.placeholder, hasParent: true, intended: dw.intended}} do, err := device.NestedObjects() if err != nil { @@ -1106,16 +1072,6 @@ func (dw *DcimInterfaceDataWrapper) ID() int { return dw.Interface.ID } -// HasChanged returns true if the data has changed -func (dw *DcimInterfaceDataWrapper) HasChanged() bool { - return dw.hasChanged -} - -// IsPlaceholder returns true if the data is a placeholder -func (dw *DcimInterfaceDataWrapper) IsPlaceholder() bool { - return dw.placeholder -} - func (dw *DcimInterfaceDataWrapper) hash() string { var deviceName, siteName string if dw.Interface.Device != nil { @@ -1304,14 +1260,8 @@ func (dw *DcimInterfaceDataWrapper) SetDefaults() { // DcimManufacturerDataWrapper represents a DCIM manufacturer data wrapper type DcimManufacturerDataWrapper struct { + BaseDataWrapper Manufacturer *DcimManufacturer - - placeholder bool - hasParent bool - intended bool - hasChanged bool - nestedObjects []ComparableData - objectsToReconcile []ComparableData } func (*DcimManufacturerDataWrapper) comparableData() {} @@ -1395,16 +1345,6 @@ func (dw *DcimManufacturerDataWrapper) ID() int { return dw.Manufacturer.ID } -// HasChanged returns true if the data has changed -func (dw *DcimManufacturerDataWrapper) HasChanged() bool { - return dw.hasChanged -} - -// IsPlaceholder returns true if the data is a placeholder -func (dw *DcimManufacturerDataWrapper) IsPlaceholder() bool { - return dw.placeholder -} - // Patch creates patches between the actual, intended and current data func (dw *DcimManufacturerDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { intended, ok := cmp.(*DcimManufacturerDataWrapper) @@ -1496,14 +1436,8 @@ func (dw *DcimManufacturerDataWrapper) SetDefaults() {} // DcimPlatformDataWrapper represents a DCIM platform data wrapper type DcimPlatformDataWrapper struct { + BaseDataWrapper Platform *DcimPlatform - - placeholder bool - hasParent bool - intended bool - hasChanged bool - nestedObjects []ComparableData - objectsToReconcile []ComparableData } func (*DcimPlatformDataWrapper) comparableData() {} @@ -1555,7 +1489,7 @@ func (dw *DcimPlatformDataWrapper) NestedObjects() ([]ComparableData, error) { } if dw.Platform.Manufacturer != nil { - manufacturer := DcimManufacturerDataWrapper{Manufacturer: dw.Platform.Manufacturer, placeholder: dw.placeholder, hasParent: true, intended: dw.intended} + manufacturer := DcimManufacturerDataWrapper{Manufacturer: dw.Platform.Manufacturer, BaseDataWrapper: BaseDataWrapper{placeholder: dw.placeholder, hasParent: true, intended: dw.intended}} mo, err := manufacturer.NestedObjects() if err != nil { @@ -1604,16 +1538,6 @@ func (dw *DcimPlatformDataWrapper) ID() int { return dw.Platform.ID } -// HasChanged returns true if the data has changed -func (dw *DcimPlatformDataWrapper) HasChanged() bool { - return dw.hasChanged -} - -// IsPlaceholder returns true if the data is a placeholder -func (dw *DcimPlatformDataWrapper) IsPlaceholder() bool { - return dw.placeholder -} - // Patch creates patches between the actual, intended and current data func (dw *DcimPlatformDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { intended, ok := cmp.(*DcimPlatformDataWrapper) @@ -1762,14 +1686,8 @@ func (dw *DcimPlatformDataWrapper) SetDefaults() {} // DcimSiteDataWrapper represents a DCIM site data wrapper type DcimSiteDataWrapper struct { + BaseDataWrapper Site *DcimSite - - placeholder bool - hasParent bool - intended bool - hasChanged bool - nestedObjects []ComparableData - objectsToReconcile []ComparableData } func (*DcimSiteDataWrapper) comparableData() {} @@ -1853,16 +1771,6 @@ func (dw *DcimSiteDataWrapper) ID() int { return dw.Site.ID } -// HasChanged returns true if the data has changed -func (dw *DcimSiteDataWrapper) HasChanged() bool { - return dw.hasChanged -} - -// IsPlaceholder returns true if the data is a placeholder -func (dw *DcimSiteDataWrapper) IsPlaceholder() bool { - return dw.placeholder -} - // Patch creates patches between the actual, intended and current data func (dw *DcimSiteDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { intended, ok := cmp.(*DcimSiteDataWrapper) From be4fd4d06bd08afc959b99d7024535179dd8a306 Mon Sep 17 00:00:00 2001 From: Leonardo Parente <23251360+leoparente@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:50:53 -0300 Subject: [PATCH 07/13] feat: add virtualization object types support (#155) --- Makefile | 3 +- diode-proto/diode/v1/ingester.proto | 116 + .../gen/diode/v1/diodepb/ingester.pb.go | 1490 ++++++++++--- .../diode/v1/diodepb/ingester.pb.validate.go | 1691 +++++++++++++++ diode-server/netbox/virtualization.go | 190 ++ .../netbox/virtualization_wrappers.go | 1680 +++++++++++++++ diode-server/netbox/wrappers.go | 12 + diode-server/netboxdiodeplugin/client.go | 36 + diode-server/netboxdiodeplugin/client_test.go | 108 + ...angeset_test.go => changeset_dcim_test.go} | 1843 +--------------- .../changeset/changeset_ipam_test.go | 1868 +++++++++++++++++ .../changeset/changeset_virt_test.go | 1683 +++++++++++++++ .../reconciler/ingestion_processor.go | 12 + 13 files changed, 8610 insertions(+), 2122 deletions(-) create mode 100644 diode-server/netbox/virtualization.go create mode 100644 diode-server/netbox/virtualization_wrappers.go rename diode-server/reconciler/changeset/{changeset_test.go => changeset_dcim_test.go} (69%) create mode 100644 diode-server/reconciler/changeset/changeset_ipam_test.go create mode 100644 diode-server/reconciler/changeset/changeset_virt_test.go diff --git a/Makefile b/Makefile index f7058b03..1514d3a9 100644 --- a/Makefile +++ b/Makefile @@ -9,4 +9,5 @@ gen-diode-go-internal: gen-diode-sdk-python: @cd diode-proto/ && buf format -w && buf generate --template buf.gen.py.yaml --include-imports @find ../diode-sdk-python/netboxlabs/diode/sdk \( -name '*.py' -o -name '*.pyi' \) \ - -exec sed -i '' 's/^from diode.v1/from netboxlabs.diode.sdk.diode.v1/; s/^from validate/from netboxlabs.diode.sdk.validate/' {} \; + -exec sed -i.bak -e 's/^from diode.v1/from netboxlabs.diode.sdk.diode.v1/' \ + -e 's/^from validate/from netboxlabs.diode.sdk.validate/' {} \; -exec rm -f {}.bak \; \ No newline at end of file diff --git a/diode-proto/diode/v1/ingester.proto b/diode-proto/diode/v1/ingester.proto index 3b7b003e..2d9074e9 100644 --- a/diode-proto/diode/v1/ingester.proto +++ b/diode-proto/diode/v1/ingester.proto @@ -189,6 +189,116 @@ message Interface { repeated Tag tags = 14; } +//A Cluster +message Cluster { + string name = 1 [(validate.rules).string = { + min_len: 1 + max_len: 100 + }]; + ClusterType type = 2; + ClusterGroup group = 3; + Site site = 4; + string status = 5 [(validate.rules).string = { + in: [ + "offline", + "active", + "planned", + "staged", + "failed", + "decommissioning" + ] + }]; + optional string description = 6 [(validate.rules).string = {max_len: 200}]; + repeated Tag tags = 7; +} + +//A Cluster Type +message ClusterType { + string name = 1 [(validate.rules).string = { + min_len: 1 + max_len: 100 + }]; + string slug = 2 [(validate.rules).string = { + min_len: 1 + max_len: 100 + pattern: "^[-a-zA-Z0-9_]+$" + }]; + optional string description = 3 [(validate.rules).string = {max_len: 200}]; + repeated Tag tags = 4; +} + +//A Cluster Group +message ClusterGroup { + string name = 1 [(validate.rules).string = { + min_len: 1 + max_len: 100 + }]; + string slug = 2 [(validate.rules).string = { + min_len: 1 + max_len: 100 + pattern: "^[-a-zA-Z0-9_]+$" + }]; + optional string description = 3 [(validate.rules).string = {max_len: 200}]; + repeated Tag tags = 4; +} + +//A Virtual Machine +message VirtualMachine { + string name = 1 [(validate.rules).string = {max_len: 64}]; + string status = 2 [(validate.rules).string = { + in: [ + "offline", + "active", + "planned", + "staged", + "failed", + "decommissioning" + ] + }]; + Site site = 3; + Cluster cluster = 4; + Role role = 5; + Device device = 6; + Platform platform = 7; + IPAddress primary_ip4 = 8; + IPAddress primary_ip6 = 9; + optional int32 vcpus = 10 [(validate.rules).int32 = {gte: 0}]; + optional int32 memory = 11 [(validate.rules).int32 = {gte: 0}]; + optional int32 disk = 12 [(validate.rules).int32 = {gte: 0}]; + optional string description = 13 [(validate.rules).string = {max_len: 200}]; + optional string comments = 14; + repeated Tag tags = 15; +} + +//A Virtual Machine Interface +message VMInterface { + VirtualMachine virtual_machine = 1 [(validate.rules).any.required = true]; + string name = 2 [(validate.rules).string = { + min_len: 1 + max_len: 64 + }]; + optional bool enabled = 3; + optional int32 mtu = 4 [(validate.rules).int32 = { + gte: 1 + lte: 65536 + }]; + optional string mac_address = 5; + optional string description = 6 [(validate.rules).string = {max_len: 200}]; + repeated Tag tags = 7; +} + +//A Virtual Disk +message VirtualDisk { + VirtualMachine virtual_machine = 1 [(validate.rules).any.required = true]; + string name = 2 [(validate.rules).string = { + min_len: 1 + max_len: 100 + }]; + int32 size = 3 [(validate.rules).int32 = {gte: 0}]; + optional string description = 4 [(validate.rules).string = {max_len: 200}]; + repeated Tag tags = 5; +} + // An IP address. message IPAddress { string address = 1 [(validate.rules).string.ip = true]; @@ -370,6 +480,12 @@ message Entity { Interface interface = 7; IPAddress ip_address = 9; Prefix prefix = 10; + ClusterGroup cluster_group = 11; + ClusterType cluster_type = 12; + Cluster cluster = 13; + VirtualMachine virtual_machine = 14; + VMInterface vminterface = 15; + VirtualDisk virtual_disk = 16; } // The timestamp of the data discovery at source diff --git a/diode-server/gen/diode/v1/diodepb/ingester.pb.go b/diode-server/gen/diode/v1/diodepb/ingester.pb.go index 3bf215ab..a20ddd22 100644 --- a/diode-server/gen/diode/v1/diodepb/ingester.pb.go +++ b/diode-server/gen/diode/v1/diodepb/ingester.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.34.2 // protoc (unknown) // source: diode/v1/ingester.proto @@ -326,6 +326,582 @@ func (x *Interface) GetTags() []*Tag { return nil } +// A Cluster +type Cluster struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type *ClusterType `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Group *ClusterGroup `protobuf:"bytes,3,opt,name=group,proto3" json:"group,omitempty"` + Site *Site `protobuf:"bytes,4,opt,name=site,proto3" json:"site,omitempty"` + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + Description *string `protobuf:"bytes,6,opt,name=description,proto3,oneof" json:"description,omitempty"` + Tags []*Tag `protobuf:"bytes,7,rep,name=tags,proto3" json:"tags,omitempty"` +} + +func (x *Cluster) Reset() { + *x = Cluster{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_ingester_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Cluster) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Cluster) ProtoMessage() {} + +func (x *Cluster) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_ingester_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Cluster.ProtoReflect.Descriptor instead. +func (*Cluster) Descriptor() ([]byte, []int) { + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{2} +} + +func (x *Cluster) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Cluster) GetType() *ClusterType { + if x != nil { + return x.Type + } + return nil +} + +func (x *Cluster) GetGroup() *ClusterGroup { + if x != nil { + return x.Group + } + return nil +} + +func (x *Cluster) GetSite() *Site { + if x != nil { + return x.Site + } + return nil +} + +func (x *Cluster) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *Cluster) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *Cluster) GetTags() []*Tag { + if x != nil { + return x.Tags + } + return nil +} + +// A Cluster Type +type ClusterType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Slug string `protobuf:"bytes,2,opt,name=slug,proto3" json:"slug,omitempty"` + Description *string `protobuf:"bytes,3,opt,name=description,proto3,oneof" json:"description,omitempty"` + Tags []*Tag `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty"` +} + +func (x *ClusterType) Reset() { + *x = ClusterType{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_ingester_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClusterType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClusterType) ProtoMessage() {} + +func (x *ClusterType) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_ingester_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClusterType.ProtoReflect.Descriptor instead. +func (*ClusterType) Descriptor() ([]byte, []int) { + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{3} +} + +func (x *ClusterType) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ClusterType) GetSlug() string { + if x != nil { + return x.Slug + } + return "" +} + +func (x *ClusterType) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *ClusterType) GetTags() []*Tag { + if x != nil { + return x.Tags + } + return nil +} + +// A Cluster Group +type ClusterGroup struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Slug string `protobuf:"bytes,2,opt,name=slug,proto3" json:"slug,omitempty"` + Description *string `protobuf:"bytes,3,opt,name=description,proto3,oneof" json:"description,omitempty"` + Tags []*Tag `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty"` +} + +func (x *ClusterGroup) Reset() { + *x = ClusterGroup{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_ingester_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClusterGroup) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClusterGroup) ProtoMessage() {} + +func (x *ClusterGroup) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_ingester_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClusterGroup.ProtoReflect.Descriptor instead. +func (*ClusterGroup) Descriptor() ([]byte, []int) { + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{4} +} + +func (x *ClusterGroup) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ClusterGroup) GetSlug() string { + if x != nil { + return x.Slug + } + return "" +} + +func (x *ClusterGroup) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *ClusterGroup) GetTags() []*Tag { + if x != nil { + return x.Tags + } + return nil +} + +// A Virtual Machine +type VirtualMachine struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + Site *Site `protobuf:"bytes,3,opt,name=site,proto3" json:"site,omitempty"` + Cluster *Cluster `protobuf:"bytes,4,opt,name=cluster,proto3" json:"cluster,omitempty"` + Role *Role `protobuf:"bytes,5,opt,name=role,proto3" json:"role,omitempty"` + Device *Device `protobuf:"bytes,6,opt,name=device,proto3" json:"device,omitempty"` + Platform *Platform `protobuf:"bytes,7,opt,name=platform,proto3" json:"platform,omitempty"` + PrimaryIp4 *IPAddress `protobuf:"bytes,8,opt,name=primary_ip4,json=primaryIp4,proto3" json:"primary_ip4,omitempty"` + PrimaryIp6 *IPAddress `protobuf:"bytes,9,opt,name=primary_ip6,json=primaryIp6,proto3" json:"primary_ip6,omitempty"` + Vcpus *int32 `protobuf:"varint,10,opt,name=vcpus,proto3,oneof" json:"vcpus,omitempty"` + Memory *int32 `protobuf:"varint,11,opt,name=memory,proto3,oneof" json:"memory,omitempty"` + Disk *int32 `protobuf:"varint,12,opt,name=disk,proto3,oneof" json:"disk,omitempty"` + Description *string `protobuf:"bytes,13,opt,name=description,proto3,oneof" json:"description,omitempty"` + Comments *string `protobuf:"bytes,14,opt,name=comments,proto3,oneof" json:"comments,omitempty"` + Tags []*Tag `protobuf:"bytes,15,rep,name=tags,proto3" json:"tags,omitempty"` +} + +func (x *VirtualMachine) Reset() { + *x = VirtualMachine{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_ingester_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VirtualMachine) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VirtualMachine) ProtoMessage() {} + +func (x *VirtualMachine) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_ingester_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VirtualMachine.ProtoReflect.Descriptor instead. +func (*VirtualMachine) Descriptor() ([]byte, []int) { + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{5} +} + +func (x *VirtualMachine) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *VirtualMachine) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *VirtualMachine) GetSite() *Site { + if x != nil { + return x.Site + } + return nil +} + +func (x *VirtualMachine) GetCluster() *Cluster { + if x != nil { + return x.Cluster + } + return nil +} + +func (x *VirtualMachine) GetRole() *Role { + if x != nil { + return x.Role + } + return nil +} + +func (x *VirtualMachine) GetDevice() *Device { + if x != nil { + return x.Device + } + return nil +} + +func (x *VirtualMachine) GetPlatform() *Platform { + if x != nil { + return x.Platform + } + return nil +} + +func (x *VirtualMachine) GetPrimaryIp4() *IPAddress { + if x != nil { + return x.PrimaryIp4 + } + return nil +} + +func (x *VirtualMachine) GetPrimaryIp6() *IPAddress { + if x != nil { + return x.PrimaryIp6 + } + return nil +} + +func (x *VirtualMachine) GetVcpus() int32 { + if x != nil && x.Vcpus != nil { + return *x.Vcpus + } + return 0 +} + +func (x *VirtualMachine) GetMemory() int32 { + if x != nil && x.Memory != nil { + return *x.Memory + } + return 0 +} + +func (x *VirtualMachine) GetDisk() int32 { + if x != nil && x.Disk != nil { + return *x.Disk + } + return 0 +} + +func (x *VirtualMachine) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *VirtualMachine) GetComments() string { + if x != nil && x.Comments != nil { + return *x.Comments + } + return "" +} + +func (x *VirtualMachine) GetTags() []*Tag { + if x != nil { + return x.Tags + } + return nil +} + +// A Virtual Machine Interface +type VMInterface struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + VirtualMachine *VirtualMachine `protobuf:"bytes,1,opt,name=virtual_machine,json=virtualMachine,proto3" json:"virtual_machine,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Enabled *bool `protobuf:"varint,3,opt,name=enabled,proto3,oneof" json:"enabled,omitempty"` + Mtu *int32 `protobuf:"varint,4,opt,name=mtu,proto3,oneof" json:"mtu,omitempty"` + MacAddress *string `protobuf:"bytes,5,opt,name=mac_address,json=macAddress,proto3,oneof" json:"mac_address,omitempty"` + Description *string `protobuf:"bytes,6,opt,name=description,proto3,oneof" json:"description,omitempty"` + Tags []*Tag `protobuf:"bytes,7,rep,name=tags,proto3" json:"tags,omitempty"` +} + +func (x *VMInterface) Reset() { + *x = VMInterface{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_ingester_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VMInterface) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VMInterface) ProtoMessage() {} + +func (x *VMInterface) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_ingester_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VMInterface.ProtoReflect.Descriptor instead. +func (*VMInterface) Descriptor() ([]byte, []int) { + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{6} +} + +func (x *VMInterface) GetVirtualMachine() *VirtualMachine { + if x != nil { + return x.VirtualMachine + } + return nil +} + +func (x *VMInterface) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *VMInterface) GetEnabled() bool { + if x != nil && x.Enabled != nil { + return *x.Enabled + } + return false +} + +func (x *VMInterface) GetMtu() int32 { + if x != nil && x.Mtu != nil { + return *x.Mtu + } + return 0 +} + +func (x *VMInterface) GetMacAddress() string { + if x != nil && x.MacAddress != nil { + return *x.MacAddress + } + return "" +} + +func (x *VMInterface) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *VMInterface) GetTags() []*Tag { + if x != nil { + return x.Tags + } + return nil +} + +// A Virtual Disk +type VirtualDisk struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + VirtualMachine *VirtualMachine `protobuf:"bytes,1,opt,name=virtual_machine,json=virtualMachine,proto3" json:"virtual_machine,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Size int32 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` + Description *string `protobuf:"bytes,4,opt,name=description,proto3,oneof" json:"description,omitempty"` + Tags []*Tag `protobuf:"bytes,5,rep,name=tags,proto3" json:"tags,omitempty"` +} + +func (x *VirtualDisk) Reset() { + *x = VirtualDisk{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_ingester_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VirtualDisk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VirtualDisk) ProtoMessage() {} + +func (x *VirtualDisk) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_ingester_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VirtualDisk.ProtoReflect.Descriptor instead. +func (*VirtualDisk) Descriptor() ([]byte, []int) { + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{7} +} + +func (x *VirtualDisk) GetVirtualMachine() *VirtualMachine { + if x != nil { + return x.VirtualMachine + } + return nil +} + +func (x *VirtualDisk) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *VirtualDisk) GetSize() int32 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *VirtualDisk) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *VirtualDisk) GetTags() []*Tag { + if x != nil { + return x.Tags + } + return nil +} + // An IP address. type IPAddress struct { state protoimpl.MessageState @@ -348,7 +924,7 @@ type IPAddress struct { func (x *IPAddress) Reset() { *x = IPAddress{} if protoimpl.UnsafeEnabled { - mi := &file_diode_v1_ingester_proto_msgTypes[2] + mi := &file_diode_v1_ingester_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -361,7 +937,7 @@ func (x *IPAddress) String() string { func (*IPAddress) ProtoMessage() {} func (x *IPAddress) ProtoReflect() protoreflect.Message { - mi := &file_diode_v1_ingester_proto_msgTypes[2] + mi := &file_diode_v1_ingester_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -374,7 +950,7 @@ func (x *IPAddress) ProtoReflect() protoreflect.Message { // Deprecated: Use IPAddress.ProtoReflect.Descriptor instead. func (*IPAddress) Descriptor() ([]byte, []int) { - return file_diode_v1_ingester_proto_rawDescGZIP(), []int{2} + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{8} } func (x *IPAddress) GetAddress() string { @@ -468,7 +1044,7 @@ type DeviceType struct { func (x *DeviceType) Reset() { *x = DeviceType{} if protoimpl.UnsafeEnabled { - mi := &file_diode_v1_ingester_proto_msgTypes[3] + mi := &file_diode_v1_ingester_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -481,7 +1057,7 @@ func (x *DeviceType) String() string { func (*DeviceType) ProtoMessage() {} func (x *DeviceType) ProtoReflect() protoreflect.Message { - mi := &file_diode_v1_ingester_proto_msgTypes[3] + mi := &file_diode_v1_ingester_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -494,7 +1070,7 @@ func (x *DeviceType) ProtoReflect() protoreflect.Message { // Deprecated: Use DeviceType.ProtoReflect.Descriptor instead. func (*DeviceType) Descriptor() ([]byte, []int) { - return file_diode_v1_ingester_proto_rawDescGZIP(), []int{3} + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{9} } func (x *DeviceType) GetModel() string { @@ -561,7 +1137,7 @@ type Manufacturer struct { func (x *Manufacturer) Reset() { *x = Manufacturer{} if protoimpl.UnsafeEnabled { - mi := &file_diode_v1_ingester_proto_msgTypes[4] + mi := &file_diode_v1_ingester_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -574,7 +1150,7 @@ func (x *Manufacturer) String() string { func (*Manufacturer) ProtoMessage() {} func (x *Manufacturer) ProtoReflect() protoreflect.Message { - mi := &file_diode_v1_ingester_proto_msgTypes[4] + mi := &file_diode_v1_ingester_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -587,7 +1163,7 @@ func (x *Manufacturer) ProtoReflect() protoreflect.Message { // Deprecated: Use Manufacturer.ProtoReflect.Descriptor instead. func (*Manufacturer) Descriptor() ([]byte, []int) { - return file_diode_v1_ingester_proto_rawDescGZIP(), []int{4} + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{10} } func (x *Manufacturer) GetName() string { @@ -634,7 +1210,7 @@ type Platform struct { func (x *Platform) Reset() { *x = Platform{} if protoimpl.UnsafeEnabled { - mi := &file_diode_v1_ingester_proto_msgTypes[5] + mi := &file_diode_v1_ingester_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -647,7 +1223,7 @@ func (x *Platform) String() string { func (*Platform) ProtoMessage() {} func (x *Platform) ProtoReflect() protoreflect.Message { - mi := &file_diode_v1_ingester_proto_msgTypes[5] + mi := &file_diode_v1_ingester_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -660,7 +1236,7 @@ func (x *Platform) ProtoReflect() protoreflect.Message { // Deprecated: Use Platform.ProtoReflect.Descriptor instead. func (*Platform) Descriptor() ([]byte, []int) { - return file_diode_v1_ingester_proto_rawDescGZIP(), []int{5} + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{11} } func (x *Platform) GetName() string { @@ -717,7 +1293,7 @@ type Prefix struct { func (x *Prefix) Reset() { *x = Prefix{} if protoimpl.UnsafeEnabled { - mi := &file_diode_v1_ingester_proto_msgTypes[6] + mi := &file_diode_v1_ingester_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -730,7 +1306,7 @@ func (x *Prefix) String() string { func (*Prefix) ProtoMessage() {} func (x *Prefix) ProtoReflect() protoreflect.Message { - mi := &file_diode_v1_ingester_proto_msgTypes[6] + mi := &file_diode_v1_ingester_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -743,7 +1319,7 @@ func (x *Prefix) ProtoReflect() protoreflect.Message { // Deprecated: Use Prefix.ProtoReflect.Descriptor instead. func (*Prefix) Descriptor() ([]byte, []int) { - return file_diode_v1_ingester_proto_rawDescGZIP(), []int{6} + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{12} } func (x *Prefix) GetPrefix() string { @@ -818,7 +1394,7 @@ type Role struct { func (x *Role) Reset() { *x = Role{} if protoimpl.UnsafeEnabled { - mi := &file_diode_v1_ingester_proto_msgTypes[7] + mi := &file_diode_v1_ingester_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -831,7 +1407,7 @@ func (x *Role) String() string { func (*Role) ProtoMessage() {} func (x *Role) ProtoReflect() protoreflect.Message { - mi := &file_diode_v1_ingester_proto_msgTypes[7] + mi := &file_diode_v1_ingester_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -844,7 +1420,7 @@ func (x *Role) ProtoReflect() protoreflect.Message { // Deprecated: Use Role.ProtoReflect.Descriptor instead. func (*Role) Descriptor() ([]byte, []int) { - return file_diode_v1_ingester_proto_rawDescGZIP(), []int{7} + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{13} } func (x *Role) GetName() string { @@ -901,7 +1477,7 @@ type Site struct { func (x *Site) Reset() { *x = Site{} if protoimpl.UnsafeEnabled { - mi := &file_diode_v1_ingester_proto_msgTypes[8] + mi := &file_diode_v1_ingester_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -914,7 +1490,7 @@ func (x *Site) String() string { func (*Site) ProtoMessage() {} func (x *Site) ProtoReflect() protoreflect.Message { - mi := &file_diode_v1_ingester_proto_msgTypes[8] + mi := &file_diode_v1_ingester_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -927,7 +1503,7 @@ func (x *Site) ProtoReflect() protoreflect.Message { // Deprecated: Use Site.ProtoReflect.Descriptor instead. func (*Site) Descriptor() ([]byte, []int) { - return file_diode_v1_ingester_proto_rawDescGZIP(), []int{8} + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{14} } func (x *Site) GetName() string { @@ -1000,7 +1576,7 @@ type Tag struct { func (x *Tag) Reset() { *x = Tag{} if protoimpl.UnsafeEnabled { - mi := &file_diode_v1_ingester_proto_msgTypes[9] + mi := &file_diode_v1_ingester_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1013,7 +1589,7 @@ func (x *Tag) String() string { func (*Tag) ProtoMessage() {} func (x *Tag) ProtoReflect() protoreflect.Message { - mi := &file_diode_v1_ingester_proto_msgTypes[9] + mi := &file_diode_v1_ingester_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1026,7 +1602,7 @@ func (x *Tag) ProtoReflect() protoreflect.Message { // Deprecated: Use Tag.ProtoReflect.Descriptor instead. func (*Tag) Descriptor() ([]byte, []int) { - return file_diode_v1_ingester_proto_rawDescGZIP(), []int{9} + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{15} } func (x *Tag) GetName() string { @@ -1067,6 +1643,12 @@ type Entity struct { // *Entity_Interface // *Entity_IpAddress // *Entity_Prefix + // *Entity_ClusterGroup + // *Entity_ClusterType + // *Entity_Cluster + // *Entity_VirtualMachine + // *Entity_Vminterface + // *Entity_VirtualDisk Entity isEntity_Entity `protobuf_oneof:"entity"` // The timestamp of the data discovery at source Timestamp *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=timestamp,proto3" json:"timestamp,omitempty"` @@ -1075,7 +1657,7 @@ type Entity struct { func (x *Entity) Reset() { *x = Entity{} if protoimpl.UnsafeEnabled { - mi := &file_diode_v1_ingester_proto_msgTypes[10] + mi := &file_diode_v1_ingester_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1088,7 +1670,7 @@ func (x *Entity) String() string { func (*Entity) ProtoMessage() {} func (x *Entity) ProtoReflect() protoreflect.Message { - mi := &file_diode_v1_ingester_proto_msgTypes[10] + mi := &file_diode_v1_ingester_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1101,7 +1683,7 @@ func (x *Entity) ProtoReflect() protoreflect.Message { // Deprecated: Use Entity.ProtoReflect.Descriptor instead. func (*Entity) Descriptor() ([]byte, []int) { - return file_diode_v1_ingester_proto_rawDescGZIP(), []int{10} + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{16} } func (m *Entity) GetEntity() isEntity_Entity { @@ -1174,6 +1756,48 @@ func (x *Entity) GetPrefix() *Prefix { return nil } +func (x *Entity) GetClusterGroup() *ClusterGroup { + if x, ok := x.GetEntity().(*Entity_ClusterGroup); ok { + return x.ClusterGroup + } + return nil +} + +func (x *Entity) GetClusterType() *ClusterType { + if x, ok := x.GetEntity().(*Entity_ClusterType); ok { + return x.ClusterType + } + return nil +} + +func (x *Entity) GetCluster() *Cluster { + if x, ok := x.GetEntity().(*Entity_Cluster); ok { + return x.Cluster + } + return nil +} + +func (x *Entity) GetVirtualMachine() *VirtualMachine { + if x, ok := x.GetEntity().(*Entity_VirtualMachine); ok { + return x.VirtualMachine + } + return nil +} + +func (x *Entity) GetVminterface() *VMInterface { + if x, ok := x.GetEntity().(*Entity_Vminterface); ok { + return x.Vminterface + } + return nil +} + +func (x *Entity) GetVirtualDisk() *VirtualDisk { + if x, ok := x.GetEntity().(*Entity_VirtualDisk); ok { + return x.VirtualDisk + } + return nil +} + func (x *Entity) GetTimestamp() *timestamppb.Timestamp { if x != nil { return x.Timestamp @@ -1221,6 +1845,30 @@ type Entity_Prefix struct { Prefix *Prefix `protobuf:"bytes,10,opt,name=prefix,proto3,oneof"` } +type Entity_ClusterGroup struct { + ClusterGroup *ClusterGroup `protobuf:"bytes,11,opt,name=cluster_group,json=clusterGroup,proto3,oneof"` +} + +type Entity_ClusterType struct { + ClusterType *ClusterType `protobuf:"bytes,12,opt,name=cluster_type,json=clusterType,proto3,oneof"` +} + +type Entity_Cluster struct { + Cluster *Cluster `protobuf:"bytes,13,opt,name=cluster,proto3,oneof"` +} + +type Entity_VirtualMachine struct { + VirtualMachine *VirtualMachine `protobuf:"bytes,14,opt,name=virtual_machine,json=virtualMachine,proto3,oneof"` +} + +type Entity_Vminterface struct { + Vminterface *VMInterface `protobuf:"bytes,15,opt,name=vminterface,proto3,oneof"` +} + +type Entity_VirtualDisk struct { + VirtualDisk *VirtualDisk `protobuf:"bytes,16,opt,name=virtual_disk,json=virtualDisk,proto3,oneof"` +} + func (*Entity_Site) isEntity_Entity() {} func (*Entity_Platform) isEntity_Entity() {} @@ -1239,6 +1887,18 @@ func (*Entity_IpAddress) isEntity_Entity() {} func (*Entity_Prefix) isEntity_Entity() {} +func (*Entity_ClusterGroup) isEntity_Entity() {} + +func (*Entity_ClusterType) isEntity_Entity() {} + +func (*Entity_Cluster) isEntity_Entity() {} + +func (*Entity_VirtualMachine) isEntity_Entity() {} + +func (*Entity_Vminterface) isEntity_Entity() {} + +func (*Entity_VirtualDisk) isEntity_Entity() {} + // The request to ingest the data type IngestRequest struct { state protoimpl.MessageState @@ -1257,7 +1917,7 @@ type IngestRequest struct { func (x *IngestRequest) Reset() { *x = IngestRequest{} if protoimpl.UnsafeEnabled { - mi := &file_diode_v1_ingester_proto_msgTypes[11] + mi := &file_diode_v1_ingester_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1270,7 +1930,7 @@ func (x *IngestRequest) String() string { func (*IngestRequest) ProtoMessage() {} func (x *IngestRequest) ProtoReflect() protoreflect.Message { - mi := &file_diode_v1_ingester_proto_msgTypes[11] + mi := &file_diode_v1_ingester_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1283,7 +1943,7 @@ func (x *IngestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestRequest.ProtoReflect.Descriptor instead. func (*IngestRequest) Descriptor() ([]byte, []int) { - return file_diode_v1_ingester_proto_rawDescGZIP(), []int{11} + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{17} } func (x *IngestRequest) GetStream() string { @@ -1347,7 +2007,7 @@ type IngestResponse struct { func (x *IngestResponse) Reset() { *x = IngestResponse{} if protoimpl.UnsafeEnabled { - mi := &file_diode_v1_ingester_proto_msgTypes[12] + mi := &file_diode_v1_ingester_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1360,7 +2020,7 @@ func (x *IngestResponse) String() string { func (*IngestResponse) ProtoMessage() {} func (x *IngestResponse) ProtoReflect() protoreflect.Message { - mi := &file_diode_v1_ingester_proto_msgTypes[12] + mi := &file_diode_v1_ingester_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1373,7 +2033,7 @@ func (x *IngestResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestResponse.ProtoReflect.Descriptor instead. func (*IngestResponse) Descriptor() ([]byte, []int) { - return file_diode_v1_ingester_proto_rawDescGZIP(), []int{12} + return file_diode_v1_ingester_proto_rawDescGZIP(), []int{18} } func (x *IngestResponse) GetErrors() []string { @@ -1581,194 +2241,348 @@ var file_diode_v1_ingester_proto_rawDesc = []byte{ 0x04, 0x5f, 0x77, 0x77, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x67, 0x6d, 0x74, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x8c, 0x04, 0x0a, 0x09, 0x49, 0x50, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x70, 0x01, 0x52, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x66, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x69, 0x6f, - 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x48, - 0x00, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x30, 0xfa, 0x42, - 0x2d, 0x72, 0x2b, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, - 0x64, 0x52, 0x04, 0x64, 0x68, 0x63, 0x70, 0x52, 0x05, 0x73, 0x6c, 0x61, 0x61, 0x63, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x54, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x40, 0xfa, 0x42, 0x3d, 0x72, 0x3b, 0x52, 0x08, 0x6c, 0x6f, 0x6f, - 0x70, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, - 0x52, 0x07, 0x61, 0x6e, 0x79, 0x63, 0x61, 0x73, 0x74, 0x52, 0x03, 0x76, 0x69, 0x70, 0x52, 0x04, - 0x76, 0x72, 0x72, 0x70, 0x52, 0x04, 0x68, 0x73, 0x72, 0x70, 0x52, 0x04, 0x67, 0x6c, 0x62, 0x70, - 0x52, 0x04, 0x63, 0x61, 0x72, 0x70, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x55, 0x0a, 0x08, - 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x35, - 0xfa, 0x42, 0x32, 0x72, 0x30, 0x18, 0xff, 0x01, 0x32, 0x2b, 0x5e, 0x28, 0x5b, 0x30, 0x2d, 0x39, - 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x5f, 0x2d, 0x5d, 0x2b, 0x7c, 0x5c, 0x2a, 0x29, 0x28, 0x5c, - 0x2e, 0x5b, 0x30, 0x2d, 0x39, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x5f, 0x2d, 0x5d, 0x2b, 0x29, - 0x2a, 0x5c, 0x2e, 0x3f, 0x24, 0x48, 0x01, 0x52, 0x07, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, - 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, - 0xc8, 0x01, 0x48, 0x02, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x08, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, - 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x61, 0x73, 0x73, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x0b, 0x0a, 0x09, 0x5f, - 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6d, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xeb, 0x02, 0x0a, 0x0a, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x05, - 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x1b, 0xfa, 0x42, 0x18, 0x72, 0x16, 0x10, 0x01, 0x18, 0x64, 0x32, 0x10, - 0x5e, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, 0x2b, 0x24, - 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x3a, 0x0a, 0x0c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, - 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, - 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, - 0x75, 0x72, 0x65, 0x72, 0x52, 0x0c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, - 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, - 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x5f, 0x6e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, - 0x18, 0x32, 0x48, 0x02, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, - 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, - 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x5f, 0x6e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x22, 0xc2, 0x01, 0x0a, 0x0c, 0x4d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, - 0x75, 0x72, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x1b, 0xfa, 0x42, 0x18, 0x72, 0x16, 0x10, 0x01, 0x18, 0x64, 0x32, 0x10, 0x5e, 0x5b, - 0x2d, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, - 0x73, 0x6c, 0x75, 0x67, 0x12, 0x2f, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, - 0x18, 0xc8, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, - 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xfa, 0x01, 0x0a, 0x08, 0x50, 0x6c, 0x61, - 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x1b, 0xfa, 0x42, 0x18, 0x72, 0x16, 0x10, 0x01, 0x18, 0x64, 0x32, 0x10, 0x5e, - 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, 0x2b, 0x24, 0x52, - 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x3a, 0x0a, 0x0c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, - 0x74, 0x75, 0x72, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x69, - 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, - 0x72, 0x65, 0x72, 0x52, 0x0c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, - 0x72, 0x12, 0x2f, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, + 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xe3, 0x02, 0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x05, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x69, + 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x22, 0x0a, 0x04, 0x73, 0x69, + 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x74, 0x65, 0x52, 0x04, 0x73, 0x69, 0x74, 0x65, 0x12, 0x58, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x40, + 0xfa, 0x42, 0x3d, 0x72, 0x3b, 0x52, 0x07, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x06, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x67, 0x65, 0x64, 0x52, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x52, + 0x0f, 0x64, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2f, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, + 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, + 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, 0x0e, 0x0a, 0x0c, + 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xc1, 0x01, 0x0a, + 0x0b, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, + 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, + 0x6c, 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1b, 0xfa, 0x42, 0x18, 0x72, 0x16, + 0x10, 0x01, 0x18, 0x64, 0x32, 0x10, 0x5e, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, + 0x2d, 0x39, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x2f, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, + 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, + 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, + 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0xc2, 0x01, 0x0a, 0x0c, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1b, + 0xfa, 0x42, 0x18, 0x72, 0x16, 0x10, 0x01, 0x18, 0x64, 0x32, 0x10, 0x5e, 0x5b, 0x2d, 0x61, 0x2d, + 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x73, 0x6c, 0x75, + 0x67, 0x12, 0x2f, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, - 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x8d, 0x03, 0x0a, 0x06, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, - 0x12, 0x1f, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x70, 0x01, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, - 0x78, 0x12, 0x22, 0x0a, 0x04, 0x73, 0x69, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0e, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x74, 0x65, 0x52, - 0x04, 0x73, 0x69, 0x74, 0x65, 0x12, 0x46, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x52, 0x06, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x65, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, - 0x08, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, - 0x63, 0x61, 0x74, 0x65, 0x64, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, - 0x07, 0x69, 0x73, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, - 0x52, 0x06, 0x69, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, 0x0d, 0x6d, - 0x61, 0x72, 0x6b, 0x5f, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x08, 0x48, 0x01, 0x52, 0x0c, 0x6d, 0x61, 0x72, 0x6b, 0x55, 0x74, 0x69, 0x6c, 0x69, 0x7a, - 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, - 0x03, 0x18, 0xc8, 0x01, 0x48, 0x02, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, - 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x69, - 0x73, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x5f, - 0x75, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6d, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xea, 0x01, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x1d, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xde, 0x05, 0x0a, 0x0e, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, + 0x6c, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x18, 0x40, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x40, 0xfa, 0x42, 0x3d, 0x72, 0x3b, 0x52, 0x07, 0x6f, 0x66, + 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x07, 0x70, + 0x6c, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x06, 0x73, 0x74, 0x61, 0x67, 0x65, 0x64, 0x52, 0x06, + 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x52, 0x0f, 0x64, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x22, 0x0a, 0x04, 0x73, 0x69, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x74, 0x65, 0x52, 0x04, 0x73, + 0x69, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x12, 0x22, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, + 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x28, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2e, + 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x74, + 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x34, + 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x70, 0x34, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, + 0x50, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, + 0x79, 0x49, 0x70, 0x34, 0x12, 0x34, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, + 0x69, 0x70, 0x36, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x69, 0x6f, 0x64, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x50, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0a, + 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x49, 0x70, 0x36, 0x12, 0x22, 0x0a, 0x05, 0x76, 0x63, + 0x70, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, + 0x28, 0x00, 0x48, 0x00, 0x52, 0x05, 0x76, 0x63, 0x70, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x24, + 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, + 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x48, 0x01, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, + 0x79, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x04, 0x64, 0x69, 0x73, 0x6b, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x48, 0x02, 0x52, 0x04, 0x64, + 0x69, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, + 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x03, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x08, 0x63, 0x6f, 0x6d, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, + 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x5f, + 0x76, 0x63, 0x70, 0x75, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, + 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x64, 0x69, 0x73, 0x6b, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xea, 0x02, 0x0a, 0x0b, 0x56, 0x4d, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, + 0x6c, 0x5f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x72, 0x74, 0x75, + 0x61, 0x6c, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 0xa2, 0x01, + 0x02, 0x08, 0x01, 0x52, 0x0e, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x4d, 0x61, 0x63, 0x68, + 0x69, 0x6e, 0x65, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, 0x40, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x88, 0x01, + 0x01, 0x12, 0x22, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0b, + 0xfa, 0x42, 0x08, 0x1a, 0x06, 0x18, 0x80, 0x80, 0x04, 0x28, 0x01, 0x48, 0x01, 0x52, 0x03, 0x6d, + 0x74, 0x75, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0b, 0x6d, 0x61, 0x63, 0x5f, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x0a, 0x6d, 0x61, + 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x03, 0x52, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, + 0x74, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, + 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, + 0x0a, 0x0a, 0x08, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x5f, + 0x6d, 0x74, 0x75, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x6d, 0x61, 0x63, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0xfa, 0x01, 0x0a, 0x0b, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, + 0x69, 0x73, 0x6b, 0x12, 0x4b, 0x0a, 0x0f, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x6d, + 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x64, + 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x4d, + 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 0xa2, 0x01, 0x02, 0x08, 0x01, + 0x52, 0x0e, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, + 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, + 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x1b, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, + 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x2f, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, + 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, + 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, + 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x8c, 0x04, 0x0a, 0x09, 0x49, 0x50, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x70, 0x01, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x48, 0x00, 0x52, 0x09, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x30, 0xfa, 0x42, 0x2d, 0x72, 0x2b, 0x52, 0x06, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, + 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x52, 0x04, 0x64, 0x68, 0x63, + 0x70, 0x52, 0x05, 0x73, 0x6c, 0x61, 0x61, 0x63, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x54, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x40, + 0xfa, 0x42, 0x3d, 0x72, 0x3b, 0x52, 0x08, 0x6c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, 0x52, + 0x09, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x52, 0x07, 0x61, 0x6e, 0x79, 0x63, + 0x61, 0x73, 0x74, 0x52, 0x03, 0x76, 0x69, 0x70, 0x52, 0x04, 0x76, 0x72, 0x72, 0x70, 0x52, 0x04, + 0x68, 0x73, 0x72, 0x70, 0x52, 0x04, 0x67, 0x6c, 0x62, 0x70, 0x52, 0x04, 0x63, 0x61, 0x72, 0x70, + 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x55, 0x0a, 0x08, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x35, 0xfa, 0x42, 0x32, 0x72, 0x30, 0x18, + 0xff, 0x01, 0x32, 0x2b, 0x5e, 0x28, 0x5b, 0x30, 0x2d, 0x39, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, + 0x5f, 0x2d, 0x5d, 0x2b, 0x7c, 0x5c, 0x2a, 0x29, 0x28, 0x5c, 0x2e, 0x5b, 0x30, 0x2d, 0x39, 0x41, + 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x5f, 0x2d, 0x5d, 0x2b, 0x29, 0x2a, 0x5c, 0x2e, 0x3f, 0x24, 0x48, + 0x01, 0x52, 0x07, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x02, 0x52, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1f, + 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x03, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x88, 0x01, 0x01, 0x12, + 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, + 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, + 0x67, 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, + 0xeb, 0x02, 0x0a, 0x0a, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, + 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, + 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, + 0x2f, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1b, 0xfa, + 0x42, 0x18, 0x72, 0x16, 0x10, 0x01, 0x18, 0x64, 0x32, 0x10, 0x5e, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, + 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, + 0x12, 0x3a, 0x0a, 0x0c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x4d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x52, 0x0c, + 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, + 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x01, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x88, 0x01, 0x01, 0x12, 0x2d, + 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x18, 0x32, 0x48, 0x02, 0x52, 0x0a, + 0x70, 0x61, 0x72, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, + 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, + 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, + 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x0e, 0x0a, + 0x0c, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0xc2, 0x01, + 0x0a, 0x0c, 0x4d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1b, 0xfa, 0x42, 0x18, 0x72, 0x16, 0x10, 0x01, 0x18, 0x64, 0x32, 0x10, 0x5e, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x41, 0x2d, - 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x2e, - 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xfa, - 0x42, 0x15, 0x72, 0x13, 0x10, 0x06, 0x18, 0x06, 0x32, 0x0d, 0x5e, 0x5b, 0x30, 0x2d, 0x39, 0x61, - 0x2d, 0x66, 0x5d, 0x7b, 0x36, 0x7d, 0x24, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x2f, - 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x2f, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, - 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, + 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0xa2, 0x03, 0x0a, 0x04, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, - 0x10, 0x01, 0x18, 0x64, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x6c, - 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1b, 0xfa, 0x42, 0x18, 0x72, 0x16, 0x10, - 0x01, 0x18, 0x64, 0x32, 0x10, 0x5e, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, - 0x39, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x51, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x39, 0xfa, 0x42, 0x36, - 0x72, 0x34, 0x52, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x07, 0x73, 0x74, 0x61, - 0x67, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x0f, 0x64, 0x65, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x72, - 0x65, 0x74, 0x69, 0x72, 0x65, 0x64, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, - 0x0a, 0x08, 0x66, 0x61, 0x63, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x18, 0x32, 0x48, 0x00, 0x52, 0x08, 0x66, 0x61, 0x63, - 0x69, 0x6c, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, - 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x08, 0x74, - 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, 0x65, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x02, 0x52, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x63, - 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, - 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, - 0x74, 0x61, 0x67, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, - 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, - 0x0b, 0x0a, 0x09, 0x5f, 0x66, 0x61, 0x63, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x0c, 0x0a, 0x0a, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, - 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x85, 0x01, 0x0a, 0x03, 0x54, 0x61, 0x67, 0x12, + 0x6f, 0x6e, 0x22, 0xfa, 0x01, 0x0a, 0x08, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1b, 0xfa, 0x42, 0x18, 0x72, 0x16, 0x10, 0x01, 0x18, 0x64, 0x32, 0x10, 0x5e, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, - 0x2e, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, - 0xfa, 0x42, 0x15, 0x72, 0x13, 0x10, 0x06, 0x18, 0x06, 0x32, 0x0d, 0x5e, 0x5b, 0x30, 0x2d, 0x39, - 0x61, 0x2d, 0x66, 0x5d, 0x7b, 0x36, 0x7d, 0x24, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x22, - 0x9d, 0x04, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x24, 0x0a, 0x04, 0x73, 0x69, - 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, 0x73, 0x69, 0x74, 0x65, - 0x12, 0x30, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, - 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x48, 0x00, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, - 0x72, 0x6d, 0x12, 0x3c, 0x0a, 0x0c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, - 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, - 0x48, 0x00, 0x52, 0x0c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, - 0x12, 0x2a, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x10, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x31, 0x0a, 0x0b, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, - 0x65, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, - 0x37, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x69, - 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, - 0x48, 0x00, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x34, 0x0a, - 0x0a, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x50, 0x41, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x00, 0x52, 0x09, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x72, 0x65, 0x66, 0x69, 0x78, 0x48, 0x00, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, + 0x3a, 0x0a, 0x0c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x4d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x52, 0x0c, 0x6d, + 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, + 0x74, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, + 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, + 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, + 0x8d, 0x03, 0x0a, 0x06, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1f, 0x0a, 0x06, 0x70, 0x72, + 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, + 0x02, 0x70, 0x01, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x22, 0x0a, 0x04, 0x73, + 0x69, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x69, 0x6f, 0x64, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x74, 0x65, 0x52, 0x04, 0x73, 0x69, 0x74, 0x65, 0x12, + 0x46, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x09, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x08, 0x72, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x70, 0x6f, + 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x69, 0x73, 0x50, 0x6f, + 0x6f, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, 0x0d, 0x6d, 0x61, 0x72, 0x6b, 0x5f, 0x75, 0x74, + 0x69, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x0c, + 0x6d, 0x61, 0x72, 0x6b, 0x55, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, + 0x2f, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x02, + 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, + 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x03, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x88, 0x01, + 0x01, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x0d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, + 0x74, 0x61, 0x67, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x69, 0x73, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, + 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x5f, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, + 0xea, 0x01, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, + 0x64, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1b, 0xfa, 0x42, 0x18, 0x72, 0x16, 0x10, 0x01, 0x18, 0x64, + 0x32, 0x10, 0x5e, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, + 0x2b, 0x24, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x2e, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, + 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xfa, 0x42, 0x15, 0x72, 0x13, 0x10, 0x06, + 0x18, 0x06, 0x32, 0x0d, 0x5e, 0x5b, 0x30, 0x2d, 0x39, 0x61, 0x2d, 0x66, 0x5d, 0x7b, 0x36, 0x7d, + 0x24, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x2f, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, + 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, 0x0e, 0x0a, 0x0c, + 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xa2, 0x03, 0x0a, + 0x04, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x1b, 0xfa, 0x42, 0x18, 0x72, 0x16, 0x10, 0x01, 0x18, 0x64, 0x32, 0x10, 0x5e, + 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5d, 0x2b, 0x24, 0x52, + 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x51, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x39, 0xfa, 0x42, 0x36, 0x72, 0x34, 0x52, 0x07, 0x70, 0x6c, + 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x07, 0x73, 0x74, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x52, 0x06, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x0f, 0x64, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x72, 0x65, 0x74, 0x69, 0x72, 0x65, 0x64, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, 0x08, 0x66, 0x61, 0x63, 0x69, + 0x6c, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, + 0x02, 0x18, 0x32, 0x48, 0x00, 0x52, 0x08, 0x66, 0x61, 0x63, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x88, + 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, + 0x65, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, + 0x18, 0xc8, 0x01, 0x48, 0x02, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x08, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x66, 0x61, + 0x63, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, + 0x7a, 0x6f, 0x6e, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x22, 0x85, 0x01, 0x0a, 0x03, 0x54, 0x61, 0x67, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x01, + 0x18, 0x64, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1b, 0xfa, 0x42, 0x18, 0x72, 0x16, 0x10, 0x01, 0x18, + 0x64, 0x32, 0x10, 0x5e, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, + 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x2e, 0x0a, 0x05, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xfa, 0x42, 0x15, 0x72, 0x13, 0x10, + 0x06, 0x18, 0x06, 0x32, 0x0d, 0x5e, 0x5b, 0x30, 0x2d, 0x39, 0x61, 0x2d, 0x66, 0x5d, 0x7b, 0x36, + 0x7d, 0x24, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x83, 0x07, 0x0a, 0x06, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x12, 0x24, 0x0a, 0x04, 0x73, 0x69, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, + 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, 0x73, 0x69, 0x74, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x6c, + 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, + 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x48, 0x00, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x3c, 0x0a, 0x0c, + 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, + 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0c, 0x6d, 0x61, + 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x06, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x64, 0x69, 0x6f, + 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x48, 0x00, 0x52, 0x06, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x31, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x69, + 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x64, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x48, 0x00, 0x52, 0x09, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x69, 0x70, 0x5f, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x69, + 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x50, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x48, 0x00, 0x52, 0x09, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2a, 0x0a, + 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x48, + 0x00, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x3d, 0x0a, 0x0d, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x6d, + 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x64, + 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x4d, + 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, + 0x6c, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x76, 0x6d, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x4d, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x76, 0x6d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, + 0x61, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x64, + 0x69, 0x73, 0x6b, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x64, 0x69, 0x6f, 0x64, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x6b, + 0x48, 0x00, 0x52, 0x0b, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x6b, 0x12, 0x44, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x0a, @@ -1823,62 +2637,92 @@ func file_diode_v1_ingester_proto_rawDescGZIP() []byte { return file_diode_v1_ingester_proto_rawDescData } -var file_diode_v1_ingester_proto_msgTypes = make([]protoimpl.MessageInfo, 13) -var file_diode_v1_ingester_proto_goTypes = []interface{}{ +var file_diode_v1_ingester_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_diode_v1_ingester_proto_goTypes = []any{ (*Device)(nil), // 0: diode.v1.Device (*Interface)(nil), // 1: diode.v1.Interface - (*IPAddress)(nil), // 2: diode.v1.IPAddress - (*DeviceType)(nil), // 3: diode.v1.DeviceType - (*Manufacturer)(nil), // 4: diode.v1.Manufacturer - (*Platform)(nil), // 5: diode.v1.Platform - (*Prefix)(nil), // 6: diode.v1.Prefix - (*Role)(nil), // 7: diode.v1.Role - (*Site)(nil), // 8: diode.v1.Site - (*Tag)(nil), // 9: diode.v1.Tag - (*Entity)(nil), // 10: diode.v1.Entity - (*IngestRequest)(nil), // 11: diode.v1.IngestRequest - (*IngestResponse)(nil), // 12: diode.v1.IngestResponse - (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp + (*Cluster)(nil), // 2: diode.v1.Cluster + (*ClusterType)(nil), // 3: diode.v1.ClusterType + (*ClusterGroup)(nil), // 4: diode.v1.ClusterGroup + (*VirtualMachine)(nil), // 5: diode.v1.VirtualMachine + (*VMInterface)(nil), // 6: diode.v1.VMInterface + (*VirtualDisk)(nil), // 7: diode.v1.VirtualDisk + (*IPAddress)(nil), // 8: diode.v1.IPAddress + (*DeviceType)(nil), // 9: diode.v1.DeviceType + (*Manufacturer)(nil), // 10: diode.v1.Manufacturer + (*Platform)(nil), // 11: diode.v1.Platform + (*Prefix)(nil), // 12: diode.v1.Prefix + (*Role)(nil), // 13: diode.v1.Role + (*Site)(nil), // 14: diode.v1.Site + (*Tag)(nil), // 15: diode.v1.Tag + (*Entity)(nil), // 16: diode.v1.Entity + (*IngestRequest)(nil), // 17: diode.v1.IngestRequest + (*IngestResponse)(nil), // 18: diode.v1.IngestResponse + (*timestamppb.Timestamp)(nil), // 19: google.protobuf.Timestamp } var file_diode_v1_ingester_proto_depIdxs = []int32{ - 3, // 0: diode.v1.Device.device_type:type_name -> diode.v1.DeviceType - 7, // 1: diode.v1.Device.role:type_name -> diode.v1.Role - 5, // 2: diode.v1.Device.platform:type_name -> diode.v1.Platform - 8, // 3: diode.v1.Device.site:type_name -> diode.v1.Site - 9, // 4: diode.v1.Device.tags:type_name -> diode.v1.Tag - 2, // 5: diode.v1.Device.primary_ip4:type_name -> diode.v1.IPAddress - 2, // 6: diode.v1.Device.primary_ip6:type_name -> diode.v1.IPAddress + 9, // 0: diode.v1.Device.device_type:type_name -> diode.v1.DeviceType + 13, // 1: diode.v1.Device.role:type_name -> diode.v1.Role + 11, // 2: diode.v1.Device.platform:type_name -> diode.v1.Platform + 14, // 3: diode.v1.Device.site:type_name -> diode.v1.Site + 15, // 4: diode.v1.Device.tags:type_name -> diode.v1.Tag + 8, // 5: diode.v1.Device.primary_ip4:type_name -> diode.v1.IPAddress + 8, // 6: diode.v1.Device.primary_ip6:type_name -> diode.v1.IPAddress 0, // 7: diode.v1.Interface.device:type_name -> diode.v1.Device - 9, // 8: diode.v1.Interface.tags:type_name -> diode.v1.Tag - 1, // 9: diode.v1.IPAddress.interface:type_name -> diode.v1.Interface - 9, // 10: diode.v1.IPAddress.tags:type_name -> diode.v1.Tag - 4, // 11: diode.v1.DeviceType.manufacturer:type_name -> diode.v1.Manufacturer - 9, // 12: diode.v1.DeviceType.tags:type_name -> diode.v1.Tag - 9, // 13: diode.v1.Manufacturer.tags:type_name -> diode.v1.Tag - 4, // 14: diode.v1.Platform.manufacturer:type_name -> diode.v1.Manufacturer - 9, // 15: diode.v1.Platform.tags:type_name -> diode.v1.Tag - 8, // 16: diode.v1.Prefix.site:type_name -> diode.v1.Site - 9, // 17: diode.v1.Prefix.tags:type_name -> diode.v1.Tag - 9, // 18: diode.v1.Role.tags:type_name -> diode.v1.Tag - 9, // 19: diode.v1.Site.tags:type_name -> diode.v1.Tag - 8, // 20: diode.v1.Entity.site:type_name -> diode.v1.Site - 5, // 21: diode.v1.Entity.platform:type_name -> diode.v1.Platform - 4, // 22: diode.v1.Entity.manufacturer:type_name -> diode.v1.Manufacturer - 0, // 23: diode.v1.Entity.device:type_name -> diode.v1.Device - 7, // 24: diode.v1.Entity.device_role:type_name -> diode.v1.Role - 3, // 25: diode.v1.Entity.device_type:type_name -> diode.v1.DeviceType - 1, // 26: diode.v1.Entity.interface:type_name -> diode.v1.Interface - 2, // 27: diode.v1.Entity.ip_address:type_name -> diode.v1.IPAddress - 6, // 28: diode.v1.Entity.prefix:type_name -> diode.v1.Prefix - 13, // 29: diode.v1.Entity.timestamp:type_name -> google.protobuf.Timestamp - 10, // 30: diode.v1.IngestRequest.entities:type_name -> diode.v1.Entity - 11, // 31: diode.v1.IngesterService.Ingest:input_type -> diode.v1.IngestRequest - 12, // 32: diode.v1.IngesterService.Ingest:output_type -> diode.v1.IngestResponse - 32, // [32:33] is the sub-list for method output_type - 31, // [31:32] is the sub-list for method input_type - 31, // [31:31] is the sub-list for extension type_name - 31, // [31:31] is the sub-list for extension extendee - 0, // [0:31] is the sub-list for field type_name + 15, // 8: diode.v1.Interface.tags:type_name -> diode.v1.Tag + 3, // 9: diode.v1.Cluster.type:type_name -> diode.v1.ClusterType + 4, // 10: diode.v1.Cluster.group:type_name -> diode.v1.ClusterGroup + 14, // 11: diode.v1.Cluster.site:type_name -> diode.v1.Site + 15, // 12: diode.v1.Cluster.tags:type_name -> diode.v1.Tag + 15, // 13: diode.v1.ClusterType.tags:type_name -> diode.v1.Tag + 15, // 14: diode.v1.ClusterGroup.tags:type_name -> diode.v1.Tag + 14, // 15: diode.v1.VirtualMachine.site:type_name -> diode.v1.Site + 2, // 16: diode.v1.VirtualMachine.cluster:type_name -> diode.v1.Cluster + 13, // 17: diode.v1.VirtualMachine.role:type_name -> diode.v1.Role + 0, // 18: diode.v1.VirtualMachine.device:type_name -> diode.v1.Device + 11, // 19: diode.v1.VirtualMachine.platform:type_name -> diode.v1.Platform + 8, // 20: diode.v1.VirtualMachine.primary_ip4:type_name -> diode.v1.IPAddress + 8, // 21: diode.v1.VirtualMachine.primary_ip6:type_name -> diode.v1.IPAddress + 15, // 22: diode.v1.VirtualMachine.tags:type_name -> diode.v1.Tag + 5, // 23: diode.v1.VMInterface.virtual_machine:type_name -> diode.v1.VirtualMachine + 15, // 24: diode.v1.VMInterface.tags:type_name -> diode.v1.Tag + 5, // 25: diode.v1.VirtualDisk.virtual_machine:type_name -> diode.v1.VirtualMachine + 15, // 26: diode.v1.VirtualDisk.tags:type_name -> diode.v1.Tag + 1, // 27: diode.v1.IPAddress.interface:type_name -> diode.v1.Interface + 15, // 28: diode.v1.IPAddress.tags:type_name -> diode.v1.Tag + 10, // 29: diode.v1.DeviceType.manufacturer:type_name -> diode.v1.Manufacturer + 15, // 30: diode.v1.DeviceType.tags:type_name -> diode.v1.Tag + 15, // 31: diode.v1.Manufacturer.tags:type_name -> diode.v1.Tag + 10, // 32: diode.v1.Platform.manufacturer:type_name -> diode.v1.Manufacturer + 15, // 33: diode.v1.Platform.tags:type_name -> diode.v1.Tag + 14, // 34: diode.v1.Prefix.site:type_name -> diode.v1.Site + 15, // 35: diode.v1.Prefix.tags:type_name -> diode.v1.Tag + 15, // 36: diode.v1.Role.tags:type_name -> diode.v1.Tag + 15, // 37: diode.v1.Site.tags:type_name -> diode.v1.Tag + 14, // 38: diode.v1.Entity.site:type_name -> diode.v1.Site + 11, // 39: diode.v1.Entity.platform:type_name -> diode.v1.Platform + 10, // 40: diode.v1.Entity.manufacturer:type_name -> diode.v1.Manufacturer + 0, // 41: diode.v1.Entity.device:type_name -> diode.v1.Device + 13, // 42: diode.v1.Entity.device_role:type_name -> diode.v1.Role + 9, // 43: diode.v1.Entity.device_type:type_name -> diode.v1.DeviceType + 1, // 44: diode.v1.Entity.interface:type_name -> diode.v1.Interface + 8, // 45: diode.v1.Entity.ip_address:type_name -> diode.v1.IPAddress + 12, // 46: diode.v1.Entity.prefix:type_name -> diode.v1.Prefix + 4, // 47: diode.v1.Entity.cluster_group:type_name -> diode.v1.ClusterGroup + 3, // 48: diode.v1.Entity.cluster_type:type_name -> diode.v1.ClusterType + 2, // 49: diode.v1.Entity.cluster:type_name -> diode.v1.Cluster + 5, // 50: diode.v1.Entity.virtual_machine:type_name -> diode.v1.VirtualMachine + 6, // 51: diode.v1.Entity.vminterface:type_name -> diode.v1.VMInterface + 7, // 52: diode.v1.Entity.virtual_disk:type_name -> diode.v1.VirtualDisk + 19, // 53: diode.v1.Entity.timestamp:type_name -> google.protobuf.Timestamp + 16, // 54: diode.v1.IngestRequest.entities:type_name -> diode.v1.Entity + 17, // 55: diode.v1.IngesterService.Ingest:input_type -> diode.v1.IngestRequest + 18, // 56: diode.v1.IngesterService.Ingest:output_type -> diode.v1.IngestResponse + 56, // [56:57] is the sub-list for method output_type + 55, // [55:56] is the sub-list for method input_type + 55, // [55:55] is the sub-list for extension type_name + 55, // [55:55] is the sub-list for extension extendee + 0, // [0:55] is the sub-list for field type_name } func init() { file_diode_v1_ingester_proto_init() } @@ -1887,7 +2731,7 @@ func file_diode_v1_ingester_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_diode_v1_ingester_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Device); i { case 0: return &v.state @@ -1899,7 +2743,7 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*Interface); i { case 0: return &v.state @@ -1911,7 +2755,79 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*Cluster); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_ingester_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*ClusterType); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_ingester_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*ClusterGroup); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_ingester_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*VirtualMachine); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_ingester_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*VMInterface); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_ingester_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*VirtualDisk); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_ingester_proto_msgTypes[8].Exporter = func(v any, i int) any { switch v := v.(*IPAddress); i { case 0: return &v.state @@ -1923,7 +2839,7 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[9].Exporter = func(v any, i int) any { switch v := v.(*DeviceType); i { case 0: return &v.state @@ -1935,7 +2851,7 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[10].Exporter = func(v any, i int) any { switch v := v.(*Manufacturer); i { case 0: return &v.state @@ -1947,7 +2863,7 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[11].Exporter = func(v any, i int) any { switch v := v.(*Platform); i { case 0: return &v.state @@ -1959,7 +2875,7 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*Prefix); i { case 0: return &v.state @@ -1971,7 +2887,7 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[13].Exporter = func(v any, i int) any { switch v := v.(*Role); i { case 0: return &v.state @@ -1983,7 +2899,7 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[14].Exporter = func(v any, i int) any { switch v := v.(*Site); i { case 0: return &v.state @@ -1995,7 +2911,7 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[15].Exporter = func(v any, i int) any { switch v := v.(*Tag); i { case 0: return &v.state @@ -2007,7 +2923,7 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[16].Exporter = func(v any, i int) any { switch v := v.(*Entity); i { case 0: return &v.state @@ -2019,7 +2935,7 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[17].Exporter = func(v any, i int) any { switch v := v.(*IngestRequest); i { case 0: return &v.state @@ -2031,7 +2947,7 @@ func file_diode_v1_ingester_proto_init() { return nil } } - file_diode_v1_ingester_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_ingester_proto_msgTypes[18].Exporter = func(v any, i int) any { switch v := v.(*IngestResponse); i { case 0: return &v.state @@ -2044,18 +2960,24 @@ func file_diode_v1_ingester_proto_init() { } } } - file_diode_v1_ingester_proto_msgTypes[0].OneofWrappers = []interface{}{} - file_diode_v1_ingester_proto_msgTypes[1].OneofWrappers = []interface{}{} - file_diode_v1_ingester_proto_msgTypes[2].OneofWrappers = []interface{}{ + file_diode_v1_ingester_proto_msgTypes[0].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[1].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[2].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[3].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[4].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[5].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[6].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[7].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[8].OneofWrappers = []any{ (*IPAddress_Interface)(nil), } - file_diode_v1_ingester_proto_msgTypes[3].OneofWrappers = []interface{}{} - file_diode_v1_ingester_proto_msgTypes[4].OneofWrappers = []interface{}{} - file_diode_v1_ingester_proto_msgTypes[5].OneofWrappers = []interface{}{} - file_diode_v1_ingester_proto_msgTypes[6].OneofWrappers = []interface{}{} - file_diode_v1_ingester_proto_msgTypes[7].OneofWrappers = []interface{}{} - file_diode_v1_ingester_proto_msgTypes[8].OneofWrappers = []interface{}{} - file_diode_v1_ingester_proto_msgTypes[10].OneofWrappers = []interface{}{ + file_diode_v1_ingester_proto_msgTypes[9].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[10].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[11].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[12].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[13].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[14].OneofWrappers = []any{} + file_diode_v1_ingester_proto_msgTypes[16].OneofWrappers = []any{ (*Entity_Site)(nil), (*Entity_Platform)(nil), (*Entity_Manufacturer)(nil), @@ -2065,6 +2987,12 @@ func file_diode_v1_ingester_proto_init() { (*Entity_Interface)(nil), (*Entity_IpAddress)(nil), (*Entity_Prefix)(nil), + (*Entity_ClusterGroup)(nil), + (*Entity_ClusterType)(nil), + (*Entity_Cluster)(nil), + (*Entity_VirtualMachine)(nil), + (*Entity_Vminterface)(nil), + (*Entity_VirtualDisk)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -2072,7 +3000,7 @@ func file_diode_v1_ingester_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_diode_v1_ingester_proto_rawDesc, NumEnums: 0, - NumMessages: 13, + NumMessages: 19, NumExtensions: 0, NumServices: 1, }, diff --git a/diode-server/gen/diode/v1/diodepb/ingester.pb.validate.go b/diode-server/gen/diode/v1/diodepb/ingester.pb.validate.go index 5db634be..102869a1 100644 --- a/diode-server/gen/diode/v1/diodepb/ingester.pb.validate.go +++ b/diode-server/gen/diode/v1/diodepb/ingester.pb.validate.go @@ -825,6 +825,1451 @@ var _Interface_Mode_InLookup = map[string]struct{}{ "tagged-all": {}, } +// Validate checks the field values on Cluster with the rules defined in the +// proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *Cluster) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on Cluster with the rules defined in the +// proto definition for this message. If any rules are violated, the result is +// a list of violation errors wrapped in ClusterMultiError, or nil if none found. +func (m *Cluster) ValidateAll() error { + return m.validate(true) +} + +func (m *Cluster) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if l := utf8.RuneCountInString(m.GetName()); l < 1 || l > 100 { + err := ClusterValidationError{ + field: "Name", + reason: "value length must be between 1 and 100 runes, inclusive", + } + if !all { + return err + } + errors = append(errors, err) + } + + if all { + switch v := interface{}(m.GetType()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ClusterValidationError{ + field: "Type", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ClusterValidationError{ + field: "Type", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetType()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ClusterValidationError{ + field: "Type", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetGroup()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ClusterValidationError{ + field: "Group", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ClusterValidationError{ + field: "Group", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetGroup()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ClusterValidationError{ + field: "Group", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetSite()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ClusterValidationError{ + field: "Site", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ClusterValidationError{ + field: "Site", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetSite()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ClusterValidationError{ + field: "Site", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if _, ok := _Cluster_Status_InLookup[m.GetStatus()]; !ok { + err := ClusterValidationError{ + field: "Status", + reason: "value must be in list [offline active planned staged failed decommissioning]", + } + if !all { + return err + } + errors = append(errors, err) + } + + for idx, item := range m.GetTags() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ClusterValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ClusterValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ClusterValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if m.Description != nil { + + if utf8.RuneCountInString(m.GetDescription()) > 200 { + err := ClusterValidationError{ + field: "Description", + reason: "value length must be at most 200 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + } + + if len(errors) > 0 { + return ClusterMultiError(errors) + } + + return nil +} + +// ClusterMultiError is an error wrapping multiple validation errors returned +// by Cluster.ValidateAll() if the designated constraints aren't met. +type ClusterMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ClusterMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ClusterMultiError) AllErrors() []error { return m } + +// ClusterValidationError is the validation error returned by Cluster.Validate +// if the designated constraints aren't met. +type ClusterValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ClusterValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ClusterValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ClusterValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ClusterValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ClusterValidationError) ErrorName() string { return "ClusterValidationError" } + +// Error satisfies the builtin error interface +func (e ClusterValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sCluster.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ClusterValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ClusterValidationError{} + +var _Cluster_Status_InLookup = map[string]struct{}{ + "offline": {}, + "active": {}, + "planned": {}, + "staged": {}, + "failed": {}, + "decommissioning": {}, +} + +// Validate checks the field values on ClusterType with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *ClusterType) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ClusterType with the rules defined in +// the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in ClusterTypeMultiError, or +// nil if none found. +func (m *ClusterType) ValidateAll() error { + return m.validate(true) +} + +func (m *ClusterType) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if l := utf8.RuneCountInString(m.GetName()); l < 1 || l > 100 { + err := ClusterTypeValidationError{ + field: "Name", + reason: "value length must be between 1 and 100 runes, inclusive", + } + if !all { + return err + } + errors = append(errors, err) + } + + if l := utf8.RuneCountInString(m.GetSlug()); l < 1 || l > 100 { + err := ClusterTypeValidationError{ + field: "Slug", + reason: "value length must be between 1 and 100 runes, inclusive", + } + if !all { + return err + } + errors = append(errors, err) + } + + if !_ClusterType_Slug_Pattern.MatchString(m.GetSlug()) { + err := ClusterTypeValidationError{ + field: "Slug", + reason: "value does not match regex pattern \"^[-a-zA-Z0-9_]+$\"", + } + if !all { + return err + } + errors = append(errors, err) + } + + for idx, item := range m.GetTags() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ClusterTypeValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ClusterTypeValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ClusterTypeValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if m.Description != nil { + + if utf8.RuneCountInString(m.GetDescription()) > 200 { + err := ClusterTypeValidationError{ + field: "Description", + reason: "value length must be at most 200 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + } + + if len(errors) > 0 { + return ClusterTypeMultiError(errors) + } + + return nil +} + +// ClusterTypeMultiError is an error wrapping multiple validation errors +// returned by ClusterType.ValidateAll() if the designated constraints aren't met. +type ClusterTypeMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ClusterTypeMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ClusterTypeMultiError) AllErrors() []error { return m } + +// ClusterTypeValidationError is the validation error returned by +// ClusterType.Validate if the designated constraints aren't met. +type ClusterTypeValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ClusterTypeValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ClusterTypeValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ClusterTypeValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ClusterTypeValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ClusterTypeValidationError) ErrorName() string { return "ClusterTypeValidationError" } + +// Error satisfies the builtin error interface +func (e ClusterTypeValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sClusterType.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ClusterTypeValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ClusterTypeValidationError{} + +var _ClusterType_Slug_Pattern = regexp.MustCompile("^[-a-zA-Z0-9_]+$") + +// Validate checks the field values on ClusterGroup with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *ClusterGroup) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ClusterGroup with the rules defined +// in the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in ClusterGroupMultiError, or +// nil if none found. +func (m *ClusterGroup) ValidateAll() error { + return m.validate(true) +} + +func (m *ClusterGroup) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if l := utf8.RuneCountInString(m.GetName()); l < 1 || l > 100 { + err := ClusterGroupValidationError{ + field: "Name", + reason: "value length must be between 1 and 100 runes, inclusive", + } + if !all { + return err + } + errors = append(errors, err) + } + + if l := utf8.RuneCountInString(m.GetSlug()); l < 1 || l > 100 { + err := ClusterGroupValidationError{ + field: "Slug", + reason: "value length must be between 1 and 100 runes, inclusive", + } + if !all { + return err + } + errors = append(errors, err) + } + + if !_ClusterGroup_Slug_Pattern.MatchString(m.GetSlug()) { + err := ClusterGroupValidationError{ + field: "Slug", + reason: "value does not match regex pattern \"^[-a-zA-Z0-9_]+$\"", + } + if !all { + return err + } + errors = append(errors, err) + } + + for idx, item := range m.GetTags() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ClusterGroupValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ClusterGroupValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ClusterGroupValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if m.Description != nil { + + if utf8.RuneCountInString(m.GetDescription()) > 200 { + err := ClusterGroupValidationError{ + field: "Description", + reason: "value length must be at most 200 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + } + + if len(errors) > 0 { + return ClusterGroupMultiError(errors) + } + + return nil +} + +// ClusterGroupMultiError is an error wrapping multiple validation errors +// returned by ClusterGroup.ValidateAll() if the designated constraints aren't met. +type ClusterGroupMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ClusterGroupMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ClusterGroupMultiError) AllErrors() []error { return m } + +// ClusterGroupValidationError is the validation error returned by +// ClusterGroup.Validate if the designated constraints aren't met. +type ClusterGroupValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ClusterGroupValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ClusterGroupValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ClusterGroupValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ClusterGroupValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ClusterGroupValidationError) ErrorName() string { return "ClusterGroupValidationError" } + +// Error satisfies the builtin error interface +func (e ClusterGroupValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sClusterGroup.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ClusterGroupValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ClusterGroupValidationError{} + +var _ClusterGroup_Slug_Pattern = regexp.MustCompile("^[-a-zA-Z0-9_]+$") + +// Validate checks the field values on VirtualMachine with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *VirtualMachine) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on VirtualMachine with the rules defined +// in the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in VirtualMachineMultiError, +// or nil if none found. +func (m *VirtualMachine) ValidateAll() error { + return m.validate(true) +} + +func (m *VirtualMachine) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if utf8.RuneCountInString(m.GetName()) > 64 { + err := VirtualMachineValidationError{ + field: "Name", + reason: "value length must be at most 64 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if _, ok := _VirtualMachine_Status_InLookup[m.GetStatus()]; !ok { + err := VirtualMachineValidationError{ + field: "Status", + reason: "value must be in list [offline active planned staged failed decommissioning]", + } + if !all { + return err + } + errors = append(errors, err) + } + + if all { + switch v := interface{}(m.GetSite()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "Site", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "Site", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetSite()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return VirtualMachineValidationError{ + field: "Site", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetCluster()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "Cluster", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "Cluster", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetCluster()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return VirtualMachineValidationError{ + field: "Cluster", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetRole()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "Role", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "Role", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetRole()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return VirtualMachineValidationError{ + field: "Role", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetDevice()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "Device", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "Device", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetDevice()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return VirtualMachineValidationError{ + field: "Device", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetPlatform()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "Platform", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "Platform", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetPlatform()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return VirtualMachineValidationError{ + field: "Platform", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetPrimaryIp4()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "PrimaryIp4", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "PrimaryIp4", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetPrimaryIp4()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return VirtualMachineValidationError{ + field: "PrimaryIp4", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetPrimaryIp6()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "PrimaryIp6", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: "PrimaryIp6", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetPrimaryIp6()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return VirtualMachineValidationError{ + field: "PrimaryIp6", + reason: "embedded message failed validation", + cause: err, + } + } + } + + for idx, item := range m.GetTags() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, VirtualMachineValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return VirtualMachineValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if m.Vcpus != nil { + + if m.GetVcpus() < 0 { + err := VirtualMachineValidationError{ + field: "Vcpus", + reason: "value must be greater than or equal to 0", + } + if !all { + return err + } + errors = append(errors, err) + } + + } + + if m.Memory != nil { + + if m.GetMemory() < 0 { + err := VirtualMachineValidationError{ + field: "Memory", + reason: "value must be greater than or equal to 0", + } + if !all { + return err + } + errors = append(errors, err) + } + + } + + if m.Disk != nil { + + if m.GetDisk() < 0 { + err := VirtualMachineValidationError{ + field: "Disk", + reason: "value must be greater than or equal to 0", + } + if !all { + return err + } + errors = append(errors, err) + } + + } + + if m.Description != nil { + + if utf8.RuneCountInString(m.GetDescription()) > 200 { + err := VirtualMachineValidationError{ + field: "Description", + reason: "value length must be at most 200 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + } + + if m.Comments != nil { + // no validation rules for Comments + } + + if len(errors) > 0 { + return VirtualMachineMultiError(errors) + } + + return nil +} + +// VirtualMachineMultiError is an error wrapping multiple validation errors +// returned by VirtualMachine.ValidateAll() if the designated constraints +// aren't met. +type VirtualMachineMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m VirtualMachineMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m VirtualMachineMultiError) AllErrors() []error { return m } + +// VirtualMachineValidationError is the validation error returned by +// VirtualMachine.Validate if the designated constraints aren't met. +type VirtualMachineValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e VirtualMachineValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e VirtualMachineValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e VirtualMachineValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e VirtualMachineValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e VirtualMachineValidationError) ErrorName() string { return "VirtualMachineValidationError" } + +// Error satisfies the builtin error interface +func (e VirtualMachineValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sVirtualMachine.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = VirtualMachineValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = VirtualMachineValidationError{} + +var _VirtualMachine_Status_InLookup = map[string]struct{}{ + "offline": {}, + "active": {}, + "planned": {}, + "staged": {}, + "failed": {}, + "decommissioning": {}, +} + +// Validate checks the field values on VMInterface with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *VMInterface) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on VMInterface with the rules defined in +// the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in VMInterfaceMultiError, or +// nil if none found. +func (m *VMInterface) ValidateAll() error { + return m.validate(true) +} + +func (m *VMInterface) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if m.GetVirtualMachine() == nil { + err := VMInterfaceValidationError{ + field: "VirtualMachine", + reason: "value is required", + } + if !all { + return err + } + errors = append(errors, err) + } + + if a := m.GetVirtualMachine(); a != nil { + + } + + if l := utf8.RuneCountInString(m.GetName()); l < 1 || l > 64 { + err := VMInterfaceValidationError{ + field: "Name", + reason: "value length must be between 1 and 64 runes, inclusive", + } + if !all { + return err + } + errors = append(errors, err) + } + + for idx, item := range m.GetTags() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, VMInterfaceValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, VMInterfaceValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return VMInterfaceValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if m.Enabled != nil { + // no validation rules for Enabled + } + + if m.Mtu != nil { + + if val := m.GetMtu(); val < 1 || val > 65536 { + err := VMInterfaceValidationError{ + field: "Mtu", + reason: "value must be inside range [1, 65536]", + } + if !all { + return err + } + errors = append(errors, err) + } + + } + + if m.MacAddress != nil { + // no validation rules for MacAddress + } + + if m.Description != nil { + + if utf8.RuneCountInString(m.GetDescription()) > 200 { + err := VMInterfaceValidationError{ + field: "Description", + reason: "value length must be at most 200 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + } + + if len(errors) > 0 { + return VMInterfaceMultiError(errors) + } + + return nil +} + +// VMInterfaceMultiError is an error wrapping multiple validation errors +// returned by VMInterface.ValidateAll() if the designated constraints aren't met. +type VMInterfaceMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m VMInterfaceMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m VMInterfaceMultiError) AllErrors() []error { return m } + +// VMInterfaceValidationError is the validation error returned by +// VMInterface.Validate if the designated constraints aren't met. +type VMInterfaceValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e VMInterfaceValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e VMInterfaceValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e VMInterfaceValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e VMInterfaceValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e VMInterfaceValidationError) ErrorName() string { return "VMInterfaceValidationError" } + +// Error satisfies the builtin error interface +func (e VMInterfaceValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sVMInterface.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = VMInterfaceValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = VMInterfaceValidationError{} + +// Validate checks the field values on VirtualDisk with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *VirtualDisk) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on VirtualDisk with the rules defined in +// the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in VirtualDiskMultiError, or +// nil if none found. +func (m *VirtualDisk) ValidateAll() error { + return m.validate(true) +} + +func (m *VirtualDisk) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if m.GetVirtualMachine() == nil { + err := VirtualDiskValidationError{ + field: "VirtualMachine", + reason: "value is required", + } + if !all { + return err + } + errors = append(errors, err) + } + + if a := m.GetVirtualMachine(); a != nil { + + } + + if l := utf8.RuneCountInString(m.GetName()); l < 1 || l > 100 { + err := VirtualDiskValidationError{ + field: "Name", + reason: "value length must be between 1 and 100 runes, inclusive", + } + if !all { + return err + } + errors = append(errors, err) + } + + if m.GetSize() < 0 { + err := VirtualDiskValidationError{ + field: "Size", + reason: "value must be greater than or equal to 0", + } + if !all { + return err + } + errors = append(errors, err) + } + + for idx, item := range m.GetTags() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, VirtualDiskValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, VirtualDiskValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return VirtualDiskValidationError{ + field: fmt.Sprintf("Tags[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if m.Description != nil { + + if utf8.RuneCountInString(m.GetDescription()) > 200 { + err := VirtualDiskValidationError{ + field: "Description", + reason: "value length must be at most 200 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + } + + if len(errors) > 0 { + return VirtualDiskMultiError(errors) + } + + return nil +} + +// VirtualDiskMultiError is an error wrapping multiple validation errors +// returned by VirtualDisk.ValidateAll() if the designated constraints aren't met. +type VirtualDiskMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m VirtualDiskMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m VirtualDiskMultiError) AllErrors() []error { return m } + +// VirtualDiskValidationError is the validation error returned by +// VirtualDisk.Validate if the designated constraints aren't met. +type VirtualDiskValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e VirtualDiskValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e VirtualDiskValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e VirtualDiskValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e VirtualDiskValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e VirtualDiskValidationError) ErrorName() string { return "VirtualDiskValidationError" } + +// Error satisfies the builtin error interface +func (e VirtualDiskValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sVirtualDisk.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = VirtualDiskValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = VirtualDiskValidationError{} + // Validate checks the field values on IPAddress with the rules defined in the // proto definition for this message. If any rules are violated, the first // error encountered is returned, or nil if there are no violations. @@ -2967,6 +4412,252 @@ func (m *Entity) validate(all bool) error { } } + case *Entity_ClusterGroup: + if v == nil { + err := EntityValidationError{ + field: "Entity", + reason: "oneof value cannot be a typed-nil", + } + if !all { + return err + } + errors = append(errors, err) + } + + if all { + switch v := interface{}(m.GetClusterGroup()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, EntityValidationError{ + field: "ClusterGroup", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, EntityValidationError{ + field: "ClusterGroup", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetClusterGroup()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return EntityValidationError{ + field: "ClusterGroup", + reason: "embedded message failed validation", + cause: err, + } + } + } + + case *Entity_ClusterType: + if v == nil { + err := EntityValidationError{ + field: "Entity", + reason: "oneof value cannot be a typed-nil", + } + if !all { + return err + } + errors = append(errors, err) + } + + if all { + switch v := interface{}(m.GetClusterType()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, EntityValidationError{ + field: "ClusterType", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, EntityValidationError{ + field: "ClusterType", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetClusterType()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return EntityValidationError{ + field: "ClusterType", + reason: "embedded message failed validation", + cause: err, + } + } + } + + case *Entity_Cluster: + if v == nil { + err := EntityValidationError{ + field: "Entity", + reason: "oneof value cannot be a typed-nil", + } + if !all { + return err + } + errors = append(errors, err) + } + + if all { + switch v := interface{}(m.GetCluster()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, EntityValidationError{ + field: "Cluster", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, EntityValidationError{ + field: "Cluster", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetCluster()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return EntityValidationError{ + field: "Cluster", + reason: "embedded message failed validation", + cause: err, + } + } + } + + case *Entity_VirtualMachine: + if v == nil { + err := EntityValidationError{ + field: "Entity", + reason: "oneof value cannot be a typed-nil", + } + if !all { + return err + } + errors = append(errors, err) + } + + if all { + switch v := interface{}(m.GetVirtualMachine()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, EntityValidationError{ + field: "VirtualMachine", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, EntityValidationError{ + field: "VirtualMachine", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetVirtualMachine()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return EntityValidationError{ + field: "VirtualMachine", + reason: "embedded message failed validation", + cause: err, + } + } + } + + case *Entity_Vminterface: + if v == nil { + err := EntityValidationError{ + field: "Entity", + reason: "oneof value cannot be a typed-nil", + } + if !all { + return err + } + errors = append(errors, err) + } + + if all { + switch v := interface{}(m.GetVminterface()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, EntityValidationError{ + field: "Vminterface", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, EntityValidationError{ + field: "Vminterface", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetVminterface()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return EntityValidationError{ + field: "Vminterface", + reason: "embedded message failed validation", + cause: err, + } + } + } + + case *Entity_VirtualDisk: + if v == nil { + err := EntityValidationError{ + field: "Entity", + reason: "oneof value cannot be a typed-nil", + } + if !all { + return err + } + errors = append(errors, err) + } + + if all { + switch v := interface{}(m.GetVirtualDisk()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, EntityValidationError{ + field: "VirtualDisk", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, EntityValidationError{ + field: "VirtualDisk", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetVirtualDisk()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return EntityValidationError{ + field: "VirtualDisk", + reason: "embedded message failed validation", + cause: err, + } + } + } + default: _ = v // ensures v is used } diff --git a/diode-server/netbox/virtualization.go b/diode-server/netbox/virtualization.go new file mode 100644 index 00000000..15018f9b --- /dev/null +++ b/diode-server/netbox/virtualization.go @@ -0,0 +1,190 @@ +package netbox + +import "errors" + +const ( + // VirtualizationClusterObjectType represents the Virtualization Cluster object type + VirtualizationClusterObjectType = "virtualization.cluster" + + // VirtualizationClusterGroupObjectType represents the Virtualization Cluster Group object type + VirtualizationClusterGroupObjectType = "virtualization.clustergroup" + + // VirtualizationClusterTypeObjectType represents the Virtualization Cluster Type object type + VirtualizationClusterTypeObjectType = "virtualization.clustertype" + + // VirtualizationVirtualMachineObjectType represents the Virtualization Virtual Machine object type + VirtualizationVirtualMachineObjectType = "virtualization.virtualmachine" + + // VirtualizationVMInterfaceObjectType represents the Virtualization Interface object type + VirtualizationVMInterfaceObjectType = "virtualization.vminterface" + + // VirtualizationVirtualDiskObjectType represents the Virtualization Virtual Disk object type + VirtualizationVirtualDiskObjectType = "virtualization.virtualdisk" +) + +var ( + // ErrInvalidVirtualizationStatus is returned when the virtualization status is invalid + ErrInvalidVirtualizationStatus = errors.New("invalid virtualization status") + + // DefaultVirtualizationStatus is the default status for Virtualization objects + DefaultVirtualizationStatus = "active" +) + +var virtualizationStatusMap = map[string]struct{}{ + "offline": {}, + "active": {}, + "planned": {}, + "staged": {}, + "failed": {}, + "decommissioning": {}, +} + +func validateVirtualizationStatus(s string) bool { + _, ok := virtualizationStatusMap[s] + return ok +} + +// VirtualizationClusterGroup represents a Virtualization Cluster Group +type VirtualizationClusterGroup struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Slug string `json:"slug,omitempty"` + Description *string `json:"description,omitempty"` + Tags []*Tag `json:"tags,omitempty"` +} + +// VirtualizationClusterType represents a Virtualization Cluster Type +type VirtualizationClusterType struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Slug string `json:"slug,omitempty"` + Description *string `json:"description,omitempty"` + Tags []*Tag `json:"tags,omitempty"` +} + +// VirtualizationCluster represents a Virtualization Cluster +type VirtualizationCluster struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type *VirtualizationClusterType `json:"type,omitempty" mapstructure:"type"` + Group *VirtualizationClusterGroup `json:"group,omitempty" mapstructure:"group"` + Site *DcimSite `json:"site,omitempty"` + Status *string `json:"status,omitempty"` + Description *string `json:"description,omitempty"` + Tags []*Tag `json:"tags,omitempty"` +} + +// Validate checks if the Virtualization Cluster is valid +func (cluster *VirtualizationCluster) Validate() error { + if cluster.Status != nil && !validateVirtualizationStatus(*cluster.Status) { + return ErrInvalidVirtualizationStatus + } + return nil +} + +// VirtualizationVirtualMachine represents a Virtualization Virtual Machine +type VirtualizationVirtualMachine struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Status *string `json:"status,omitempty"` + Site *DcimSite `json:"site,omitempty"` + Cluster *VirtualizationCluster `json:"cluster,omitempty"` + Role *DcimDeviceRole `json:"role,omitempty" mapstructure:"role"` + Device *DcimDevice `json:"device,omitempty"` + Platform *DcimPlatform `json:"platform,omitempty"` + PrimaryIPv4 *IpamIPAddress `json:"primary_ip4,omitempty" mapstructure:"primary_ip4"` + PrimaryIPv6 *IpamIPAddress `json:"primary_ip6,omitempty" mapstructure:"primary_ip6"` + Vcpus *int `json:"vcpus,omitempty"` + Memory *int `json:"memory,omitempty"` + Disk *int `json:"disk,omitempty"` + Description *string `json:"description,omitempty"` + Comments *string `json:"comments,omitempty"` + Tags []*Tag `json:"tags,omitempty"` +} + +// VirtualizationVMInterface represents a Virtualization Interface +type VirtualizationVMInterface struct { + ID int `json:"id,omitempty"` + VirtualMachine *VirtualizationVirtualMachine `json:"virtual_machine,omitempty" mapstructure:"virtual_machine"` + Name string `json:"name,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + MTU *int `json:"mtu,omitempty"` + MACAddress *string `json:"mac_address,omitempty" mapstructure:"mac_address,omitempty"` + Description *string `json:"description,omitempty"` + Tags []*Tag `json:"tags,omitempty"` +} + +// VirtualizationVirtualDisk represents a Virtualization Virtual Disk +type VirtualizationVirtualDisk struct { + ID int `json:"id,omitempty"` + VirtualMachine *VirtualizationVirtualMachine `json:"virtual_machine,omitempty" mapstructure:"virtual_machine"` + Name string `json:"name,omitempty"` + Size int `json:"size,omitempty"` + Description *string `json:"description,omitempty"` + Tags []*Tag `json:"tags,omitempty"` +} + +// Validate checks if the Virtualization Virtual Machine is valid +func (vm *VirtualizationVirtualMachine) Validate() error { + if vm.Status != nil && !validateVirtualizationStatus(*vm.Status) { + return ErrInvalidVirtualizationStatus + } + return nil +} + +// NewVirtualizationClusterGroup creates a new virtualization cluster group placeholder +func NewVirtualizationClusterGroup() *VirtualizationClusterGroup { + return &VirtualizationClusterGroup{ + Name: "undefined", + Slug: "undefined", + } +} + +// NewVirtualizationClusterType creates a new virtualization cluster type placeholder +func NewVirtualizationClusterType() *VirtualizationClusterType { + return &VirtualizationClusterType{ + Name: "undefined", + Slug: "undefined", + } +} + +// NewVirtualizationCluster creates a new virtualization cluster placeholder +func NewVirtualizationCluster() *VirtualizationCluster { + status := "active" + return &VirtualizationCluster{ + Name: "undefined", + Type: NewVirtualizationClusterType(), + Group: NewVirtualizationClusterGroup(), + Site: NewDcimSite(), + Status: &status, + } +} + +// NewVirtualizationVirtualMachine creates a new virtualization virtual machine placeholder +func NewVirtualizationVirtualMachine() *VirtualizationVirtualMachine { + status := "active" + return &VirtualizationVirtualMachine{ + Name: "undefined", + Status: &status, + Site: NewDcimSite(), + Cluster: NewVirtualizationCluster(), + Role: NewDcimDeviceRole(), + } +} + +// NewVirtualizationVMInterface creates a new virtualization VM interface placeholder +func NewVirtualizationVMInterface() *VirtualizationVMInterface { + return &VirtualizationVMInterface{ + Name: "undefined", + VirtualMachine: NewVirtualizationVirtualMachine(), + } +} + +// NewVirtualizationVirtualDisk creates a new virtualization virtual disk placeholder +func NewVirtualizationVirtualDisk() *VirtualizationVirtualDisk { + return &VirtualizationVirtualDisk{ + Name: "undefined", + VirtualMachine: NewVirtualizationVirtualMachine(), + Size: 0, + } +} diff --git a/diode-server/netbox/virtualization_wrappers.go b/diode-server/netbox/virtualization_wrappers.go new file mode 100644 index 00000000..019aa0e7 --- /dev/null +++ b/diode-server/netbox/virtualization_wrappers.go @@ -0,0 +1,1680 @@ +package netbox + +import ( + "errors" + "fmt" + + "github.com/gosimple/slug" + "github.com/mitchellh/hashstructure/v2" +) + +// VirtualizationClusterGroupDataWrapper represents a virtualization cluster group data wrapper +type VirtualizationClusterGroupDataWrapper struct { + BaseDataWrapper + ClusterGroup *VirtualizationClusterGroup +} + +func (*VirtualizationClusterGroupDataWrapper) comparableData() {} + +// Data returns the DeviceRole +func (vw *VirtualizationClusterGroupDataWrapper) Data() any { + return vw.ClusterGroup +} + +// IsValid returns true if the DeviceRole is not nil +func (vw *VirtualizationClusterGroupDataWrapper) IsValid() bool { + if vw.ClusterGroup != nil && !vw.hasParent && vw.ClusterGroup.Name == "" { + vw.ClusterGroup = nil + } + return vw.ClusterGroup != nil +} + +// Normalise normalises the data +func (vw *VirtualizationClusterGroupDataWrapper) Normalise() { + if vw.IsValid() && vw.ClusterGroup.Tags != nil && len(vw.ClusterGroup.Tags) == 0 { + vw.ClusterGroup.Tags = nil + } + vw.intended = true +} + +// NestedObjects returns all nested objects +func (vw *VirtualizationClusterGroupDataWrapper) NestedObjects() ([]ComparableData, error) { + if len(vw.nestedObjects) > 0 { + return vw.nestedObjects, nil + } + + if vw.ClusterGroup != nil && vw.hasParent && vw.ClusterGroup.Name == "" { + vw.ClusterGroup = nil + } + + objects := make([]ComparableData, 0) + + if vw.ClusterGroup == nil && vw.intended { + return objects, nil + } + + if vw.ClusterGroup == nil && vw.hasParent { + vw.ClusterGroup = NewVirtualizationClusterGroup() + vw.placeholder = true + } + + if vw.ClusterGroup.Slug == "" { + vw.ClusterGroup.Slug = slug.Make(vw.ClusterGroup.Name) + } + + if vw.ClusterGroup.Tags != nil { + for _, t := range vw.ClusterGroup.Tags { + if t.Slug == "" { + t.Slug = slug.Make(t.Name) + } + objects = append(objects, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + vw.nestedObjects = objects + + objects = append(objects, vw) + + return objects, nil +} + +// DataType returns the data type +func (vw *VirtualizationClusterGroupDataWrapper) DataType() string { + return VirtualizationClusterGroupObjectType +} + +// ObjectStateQueryParams returns the query parameters needed to retrieve its object state +func (vw *VirtualizationClusterGroupDataWrapper) ObjectStateQueryParams() map[string]string { + return map[string]string{ + "q": vw.ClusterGroup.Name, + } +} + +// ID returns the ID of the data +func (vw *VirtualizationClusterGroupDataWrapper) ID() int { + return vw.ClusterGroup.ID +} + +// Patch creates patches between the actual, intended and current data +func (vw *VirtualizationClusterGroupDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { + intended, ok := cmp.(*VirtualizationClusterGroupDataWrapper) + + if !ok && intended != nil { + return nil, errors.New("invalid data type") + } + + reconciliationRequired := true + + if intended != nil { + vw.ClusterGroup.ID = intended.ClusterGroup.ID + vw.ClusterGroup.Name = intended.ClusterGroup.Name + vw.ClusterGroup.Slug = intended.ClusterGroup.Slug + + if vw.ClusterGroup.Description == nil { + vw.ClusterGroup.Description = intended.ClusterGroup.Description + } + + tagsToMerge := mergeTags(vw.ClusterGroup.Tags, intended.ClusterGroup.Tags, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.ClusterGroup.Tags = tagsToMerge + } + + actualHash, _ := hashstructure.Hash(vw.Data(), hashstructure.FormatV2, nil) + intendedHash, _ := hashstructure.Hash(intended.Data(), hashstructure.FormatV2, nil) + + reconciliationRequired = actualHash != intendedHash + } else { + tagsToMerge := mergeTags(vw.ClusterGroup.Tags, nil, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.ClusterGroup.Tags = tagsToMerge + } + } + + for _, t := range vw.ClusterGroup.Tags { + if t.ID == 0 { + vw.objectsToReconcile = append(vw.objectsToReconcile, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + if reconciliationRequired { + vw.hasChanged = true + vw.objectsToReconcile = append(vw.objectsToReconcile, vw) + } + + return vw.objectsToReconcile, nil +} + +// SetDefaults sets the default values for the device type +func (vw *VirtualizationClusterGroupDataWrapper) SetDefaults() {} + +// VirtualizationClusterTypeDataWrapper represents a virtualization cluster type data wrapper +type VirtualizationClusterTypeDataWrapper struct { + BaseDataWrapper + ClusterType *VirtualizationClusterType +} + +func (*VirtualizationClusterTypeDataWrapper) comparableData() {} + +// Data returns the DeviceRole +func (vw *VirtualizationClusterTypeDataWrapper) Data() any { + return vw.ClusterType +} + +// IsValid returns true if the DeviceRole is not nil +func (vw *VirtualizationClusterTypeDataWrapper) IsValid() bool { + if vw.ClusterType != nil && !vw.hasParent && vw.ClusterType.Name == "" { + vw.ClusterType = nil + } + return vw.ClusterType != nil +} + +// Normalise normalises the data +func (vw *VirtualizationClusterTypeDataWrapper) Normalise() { + if vw.IsValid() && vw.ClusterType.Tags != nil && len(vw.ClusterType.Tags) == 0 { + vw.ClusterType.Tags = nil + } + vw.intended = true +} + +// NestedObjects returns all nested objects +func (vw *VirtualizationClusterTypeDataWrapper) NestedObjects() ([]ComparableData, error) { + if len(vw.nestedObjects) > 0 { + return vw.nestedObjects, nil + } + + if vw.ClusterType != nil && vw.hasParent && vw.ClusterType.Name == "" { + vw.ClusterType = nil + } + + objects := make([]ComparableData, 0) + + if vw.ClusterType == nil && vw.intended { + return objects, nil + } + + if vw.ClusterType == nil && vw.hasParent { + vw.ClusterType = NewVirtualizationClusterType() + vw.placeholder = true + } + + if vw.ClusterType.Slug == "" { + vw.ClusterType.Slug = slug.Make(vw.ClusterType.Name) + } + + if vw.ClusterType.Tags != nil { + for _, t := range vw.ClusterType.Tags { + if t.Slug == "" { + t.Slug = slug.Make(t.Name) + } + objects = append(objects, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + vw.nestedObjects = objects + + objects = append(objects, vw) + + return objects, nil +} + +// DataType returns the data type +func (vw *VirtualizationClusterTypeDataWrapper) DataType() string { + return VirtualizationClusterTypeObjectType +} + +// ObjectStateQueryParams returns the query parameters needed to retrieve its object state +func (vw *VirtualizationClusterTypeDataWrapper) ObjectStateQueryParams() map[string]string { + return map[string]string{ + "q": vw.ClusterType.Name, + } +} + +// ID returns the ID of the data +func (vw *VirtualizationClusterTypeDataWrapper) ID() int { + return vw.ClusterType.ID +} + +// Patch creates patches between the actual, intended and current data +func (vw *VirtualizationClusterTypeDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { + intended, ok := cmp.(*VirtualizationClusterTypeDataWrapper) + + if !ok && intended != nil { + return nil, errors.New("invalid data type") + } + + reconciliationRequired := true + + if intended != nil { + vw.ClusterType.ID = intended.ClusterType.ID + vw.ClusterType.Name = intended.ClusterType.Name + vw.ClusterType.Slug = intended.ClusterType.Slug + + if vw.ClusterType.Description == nil { + vw.ClusterType.Description = intended.ClusterType.Description + } + + tagsToMerge := mergeTags(vw.ClusterType.Tags, intended.ClusterType.Tags, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.ClusterType.Tags = tagsToMerge + } + + actualHash, _ := hashstructure.Hash(vw.Data(), hashstructure.FormatV2, nil) + intendedHash, _ := hashstructure.Hash(intended.Data(), hashstructure.FormatV2, nil) + + reconciliationRequired = actualHash != intendedHash + } else { + tagsToMerge := mergeTags(vw.ClusterType.Tags, nil, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.ClusterType.Tags = tagsToMerge + } + } + + for _, t := range vw.ClusterType.Tags { + if t.ID == 0 { + vw.objectsToReconcile = append(vw.objectsToReconcile, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + if reconciliationRequired { + vw.hasChanged = true + vw.objectsToReconcile = append(vw.objectsToReconcile, vw) + } + + return vw.objectsToReconcile, nil +} + +// SetDefaults sets the default values for the device type +func (vw *VirtualizationClusterTypeDataWrapper) SetDefaults() {} + +// VirtualizationClusterDataWrapper represents a virtualization cluster data wrapper +type VirtualizationClusterDataWrapper struct { + BaseDataWrapper + Cluster *VirtualizationCluster +} + +func (*VirtualizationClusterDataWrapper) comparableData() {} + +// Data returns the DeviceRole +func (vw *VirtualizationClusterDataWrapper) Data() any { + return vw.Cluster +} + +// IsValid returns true if the DeviceRole is not nil +func (vw *VirtualizationClusterDataWrapper) IsValid() bool { + if vw.Cluster != nil && !vw.hasParent && vw.Cluster.Name == "" { + vw.Cluster = nil + } + return vw.Cluster != nil +} + +// Normalise normalises the data +func (vw *VirtualizationClusterDataWrapper) Normalise() { + if vw.IsValid() && vw.Cluster.Tags != nil && len(vw.Cluster.Tags) == 0 { + vw.Cluster.Tags = nil + } + vw.intended = true +} + +// NestedObjects returns all nested objects +func (vw *VirtualizationClusterDataWrapper) NestedObjects() ([]ComparableData, error) { + if len(vw.nestedObjects) > 0 { + return vw.nestedObjects, nil + } + + if vw.Cluster != nil && vw.hasParent && vw.Cluster.Name == "" { + vw.Cluster = nil + } + + objects := make([]ComparableData, 0) + + if vw.Cluster == nil && vw.intended { + return objects, nil + } + + if vw.Cluster == nil && vw.hasParent { + vw.Cluster = NewVirtualizationCluster() + vw.placeholder = true + } + + clusterGroup := VirtualizationClusterGroupDataWrapper{ClusterGroup: vw.Cluster.Group, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} + + cgo, err := clusterGroup.NestedObjects() + if err != nil { + return nil, err + } + + objects = append(objects, cgo...) + + vw.Cluster.Group = clusterGroup.ClusterGroup + + clusterType := VirtualizationClusterTypeDataWrapper{ClusterType: vw.Cluster.Type, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} + + cto, err := clusterType.NestedObjects() + if err != nil { + return nil, err + } + + objects = append(objects, cto...) + + vw.Cluster.Type = clusterType.ClusterType + + site := DcimSiteDataWrapper{Site: vw.Cluster.Site, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} + + so, err := site.NestedObjects() + if err != nil { + return nil, err + } + + objects = append(objects, so...) + + vw.Cluster.Site = site.Site + + if vw.Cluster.Tags != nil { + for _, t := range vw.Cluster.Tags { + if t.Slug == "" { + t.Slug = slug.Make(t.Name) + } + objects = append(objects, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + vw.nestedObjects = objects + + objects = append(objects, vw) + + return objects, nil +} + +// DataType returns the data type +func (vw *VirtualizationClusterDataWrapper) DataType() string { + return VirtualizationClusterObjectType +} + +// ObjectStateQueryParams returns the query parameters needed to retrieve its object state +func (vw *VirtualizationClusterDataWrapper) ObjectStateQueryParams() map[string]string { + params := map[string]string{ + "q": vw.Cluster.Name, + } + if vw.Cluster.Site != nil { + params["site__name"] = vw.Cluster.Site.Name + } + return params +} + +// ID returns the ID of the data +func (vw *VirtualizationClusterDataWrapper) ID() int { + return vw.Cluster.ID +} + +// Patch creates patches between the actual, intended and current data +func (vw *VirtualizationClusterDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { + intended, ok := cmp.(*VirtualizationClusterDataWrapper) + if !ok && intended != nil { + return nil, errors.New("invalid data type") + } + + actualNestedObjectsMap := make(map[string]ComparableData) + for _, obj := range vw.nestedObjects { + actualNestedObjectsMap[fmt.Sprintf("%p", obj.Data())] = obj + } + + actualSite := extractFromObjectsMap(actualNestedObjectsMap, fmt.Sprintf("%p", vw.Cluster.Site)) + intendedSite := extractFromObjectsMap(intendedNestedObjects, fmt.Sprintf("%p", vw.Cluster.Site)) + + actualType := extractFromObjectsMap(actualNestedObjectsMap, fmt.Sprintf("%p", vw.Cluster.Type)) + intendedType := extractFromObjectsMap(intendedNestedObjects, fmt.Sprintf("%p", vw.Cluster.Type)) + + actualGroup := extractFromObjectsMap(actualNestedObjectsMap, fmt.Sprintf("%p", vw.Cluster.Group)) + intendedGroup := extractFromObjectsMap(intendedNestedObjects, fmt.Sprintf("%p", vw.Cluster.Group)) + + reconciliationRequired := true + + if intended != nil { + currentNestedObjectsMap := make(map[string]ComparableData) + currentNestedObjects, err := intended.NestedObjects() + if err != nil { + return nil, err + } + for _, obj := range currentNestedObjects { + currentNestedObjectsMap[fmt.Sprintf("%p", obj.Data())] = obj + } + + vw.Cluster.ID = intended.Cluster.ID + vw.Cluster.Name = intended.Cluster.Name + + if vw.Cluster.Description == nil { + vw.Cluster.Description = intended.Cluster.Description + } + + if actualSite.IsPlaceholder() && intended.Cluster.Site != nil { + intendedSite = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.Cluster.Site)) + } + + siteObjectsToReconcile, siteErr := actualSite.Patch(intendedSite, intendedNestedObjects) + if siteErr != nil { + return nil, siteErr + } + + site, err := copyData(actualSite.Data().(*DcimSite)) + if err != nil { + return nil, err + } + site.Tags = nil + + if !actualSite.HasChanged() { + site = &DcimSite{ + ID: actualSite.ID(), + } + + intendedSiteID := intendedSite.ID() + if intended.Cluster.Site != nil { + intendedSiteID = intended.Cluster.Site.ID + } + + intended.Cluster.Site = &DcimSite{ + ID: intendedSiteID, + } + } + + vw.Cluster.Site = site + + vw.objectsToReconcile = append(vw.objectsToReconcile, siteObjectsToReconcile...) + + if actualType.IsPlaceholder() && intended.Cluster.Type != nil { + intendedType = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.Cluster.Type)) + } + + typeObjectsToReconcile, typeErr := actualType.Patch(intendedType, intendedNestedObjects) + if typeErr != nil { + return nil, typeErr + } + + vType, err := copyData(actualType.Data().(*VirtualizationClusterType)) + if err != nil { + return nil, err + } + vType.Tags = nil + + if !actualType.HasChanged() { + vType = &VirtualizationClusterType{ + ID: actualType.ID(), + } + + intendedTypeID := intendedType.ID() + if intended.Cluster.Type != nil { + intendedTypeID = intended.Cluster.Type.ID + } + + intended.Cluster.Type = &VirtualizationClusterType{ + ID: intendedTypeID, + } + } + + vw.Cluster.Type = vType + + vw.objectsToReconcile = append(vw.objectsToReconcile, typeObjectsToReconcile...) + + if actualGroup.IsPlaceholder() && intended.Cluster.Group != nil { + intendedGroup = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.Cluster.Group)) + } + + groupObjectsToReconcile, groupErr := actualGroup.Patch(intendedGroup, intendedNestedObjects) + if groupErr != nil { + return nil, groupErr + } + + group, err := copyData(actualGroup.Data().(*VirtualizationClusterGroup)) + if err != nil { + return nil, err + } + group.Tags = nil + + if !actualGroup.HasChanged() { + group = &VirtualizationClusterGroup{ + ID: actualGroup.ID(), + } + + intendedGroupID := intendedGroup.ID() + if intended.Cluster.Group != nil { + intendedGroupID = intended.Cluster.Group.ID + } + + intended.Cluster.Group = &VirtualizationClusterGroup{ + ID: intendedGroupID, + } + } + + vw.Cluster.Group = group + + vw.objectsToReconcile = append(vw.objectsToReconcile, groupObjectsToReconcile...) + + tagsToMerge := mergeTags(vw.Cluster.Tags, intended.Cluster.Tags, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.Cluster.Tags = tagsToMerge + } + + for _, t := range vw.Cluster.Tags { + if t.ID == 0 { + vw.objectsToReconcile = append(vw.objectsToReconcile, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + actualHash, _ := hashstructure.Hash(vw.Data(), hashstructure.FormatV2, nil) + intendedHash, _ := hashstructure.Hash(intended.Data(), hashstructure.FormatV2, nil) + + reconciliationRequired = actualHash != intendedHash + } else { + siteObjectsToReconcile, siteErr := actualSite.Patch(intendedSite, intendedNestedObjects) + if siteErr != nil { + return nil, siteErr + } + + site, err := copyData(actualSite.Data().(*DcimSite)) + if err != nil { + return nil, err + } + site.Tags = nil + + if !actualSite.HasChanged() { + site = &DcimSite{ + ID: actualSite.ID(), + } + } + vw.Cluster.Site = site + + vw.objectsToReconcile = append(vw.objectsToReconcile, siteObjectsToReconcile...) + + typeObjectsToReconcile, typeErr := actualType.Patch(intendedType, intendedNestedObjects) + if typeErr != nil { + return nil, typeErr + } + + vType, err := copyData(actualType.Data().(*VirtualizationClusterType)) + if err != nil { + return nil, err + } + vType.Tags = nil + + if !actualType.HasChanged() { + vType = &VirtualizationClusterType{ + ID: actualType.ID(), + } + } + vw.Cluster.Type = vType + + vw.objectsToReconcile = append(vw.objectsToReconcile, typeObjectsToReconcile...) + + groupObjectsToReconcile, groupErr := actualGroup.Patch(intendedGroup, intendedNestedObjects) + if groupErr != nil { + return nil, groupErr + } + + group, err := copyData(actualGroup.Data().(*VirtualizationClusterGroup)) + if err != nil { + return nil, err + } + group.Tags = nil + + if !actualGroup.HasChanged() { + group = &VirtualizationClusterGroup{ + ID: actualGroup.ID(), + } + } + vw.Cluster.Group = group + + vw.objectsToReconcile = append(vw.objectsToReconcile, groupObjectsToReconcile...) + + tagsToMerge := mergeTags(vw.Cluster.Tags, nil, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.Cluster.Tags = tagsToMerge + } + + for _, t := range vw.Cluster.Tags { + if t.ID == 0 { + vw.objectsToReconcile = append(vw.objectsToReconcile, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + } + + if reconciliationRequired { + vw.hasChanged = true + vw.objectsToReconcile = append(vw.objectsToReconcile, vw) + } + + dedupObjectsToReconcile, err := dedupObjectsToReconcile(vw.objectsToReconcile) + if err != nil { + return nil, err + } + vw.objectsToReconcile = dedupObjectsToReconcile + + return vw.objectsToReconcile, nil +} + +// SetDefaults sets the default values for the device type +func (vw *VirtualizationClusterDataWrapper) SetDefaults() {} + +// VirtualizationVirtualMachineDataWrapper represents a virtualization virtual machine data wrapper +type VirtualizationVirtualMachineDataWrapper struct { + BaseDataWrapper + VirtualMachine *VirtualizationVirtualMachine +} + +func (*VirtualizationVirtualMachineDataWrapper) comparableData() {} + +// Data returns the DeviceRole +func (vw *VirtualizationVirtualMachineDataWrapper) Data() any { + return vw.VirtualMachine +} + +// IsValid returns true if the DeviceRole is not nil +func (vw *VirtualizationVirtualMachineDataWrapper) IsValid() bool { + if vw.VirtualMachine != nil && !vw.hasParent && vw.VirtualMachine.Name == "" { + vw.VirtualMachine = nil + } + return vw.VirtualMachine != nil +} + +// Normalise normalises the data +func (vw *VirtualizationVirtualMachineDataWrapper) Normalise() { + if vw.IsValid() && vw.VirtualMachine.Tags != nil && len(vw.VirtualMachine.Tags) == 0 { + vw.VirtualMachine.Tags = nil + } + vw.intended = true +} + +// NestedObjects returns all nested objects +func (vw *VirtualizationVirtualMachineDataWrapper) NestedObjects() ([]ComparableData, error) { + if len(vw.nestedObjects) > 0 { + return vw.nestedObjects, nil + } + + if vw.VirtualMachine != nil && vw.hasParent && vw.VirtualMachine.Name == "" { + vw.VirtualMachine = nil + } + + objects := make([]ComparableData, 0) + + if vw.VirtualMachine == nil && vw.intended { + return objects, nil + } + + if vw.VirtualMachine == nil && vw.hasParent { + vw.VirtualMachine = NewVirtualizationVirtualMachine() + vw.placeholder = true + } + + // Ignore primary IP addresses and device for time being + vw.VirtualMachine.PrimaryIPv4 = nil + vw.VirtualMachine.PrimaryIPv6 = nil + vw.VirtualMachine.Device = nil + + cluster := VirtualizationClusterDataWrapper{Cluster: vw.VirtualMachine.Cluster, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} + + co, err := cluster.NestedObjects() + if err != nil { + return nil, err + } + + objects = append(objects, co...) + + vw.VirtualMachine.Cluster = cluster.Cluster + + site := DcimSiteDataWrapper{Site: vw.VirtualMachine.Site, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} + + so, err := site.NestedObjects() + if err != nil { + return nil, err + } + + objects = append(objects, so...) + + vw.VirtualMachine.Site = site.Site + + if vw.VirtualMachine.Platform != nil { + platform := DcimPlatformDataWrapper{Platform: vw.VirtualMachine.Platform, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} + + po, err := platform.NestedObjects() + if err != nil { + return nil, err + } + + objects = append(objects, po...) + + vw.VirtualMachine.Platform = platform.Platform + } + + deviceRole := DcimDeviceRoleDataWrapper{DeviceRole: vw.VirtualMachine.Role, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} + + dro, err := deviceRole.NestedObjects() + if err != nil { + return nil, err + } + + objects = append(objects, dro...) + + vw.VirtualMachine.Role = deviceRole.DeviceRole + + if vw.VirtualMachine.Tags != nil { + for _, t := range vw.VirtualMachine.Tags { + if t.Slug == "" { + t.Slug = slug.Make(t.Name) + } + objects = append(objects, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + vw.nestedObjects = objects + + objects = append(objects, vw) + + return objects, nil +} + +// DataType returns the data type +func (vw *VirtualizationVirtualMachineDataWrapper) DataType() string { + return VirtualizationVirtualMachineObjectType +} + +// ObjectStateQueryParams returns the query parameters needed to retrieve its object state +func (vw *VirtualizationVirtualMachineDataWrapper) ObjectStateQueryParams() map[string]string { + params := map[string]string{ + "q": vw.VirtualMachine.Name, + } + if vw.VirtualMachine.Site != nil { + params["site__name"] = vw.VirtualMachine.Site.Name + } else if vw.VirtualMachine.Cluster != nil { + params["cluster__name"] = vw.VirtualMachine.Cluster.Name + + if vw.VirtualMachine.Cluster.Site != nil { + params["cluster__site__name"] = vw.VirtualMachine.Cluster.Site.Name + } + } + return params +} + +// ID returns the ID of the data +func (vw *VirtualizationVirtualMachineDataWrapper) ID() int { + return vw.VirtualMachine.ID +} + +// Patch creates patches between the actual, intended and current data +func (vw *VirtualizationVirtualMachineDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { + intended, ok := cmp.(*VirtualizationVirtualMachineDataWrapper) + + if !ok && intended != nil { + return nil, errors.New("invalid data type") + } + + actualNestedObjectsMap := make(map[string]ComparableData) + for _, obj := range vw.nestedObjects { + actualNestedObjectsMap[fmt.Sprintf("%p", obj.Data())] = obj + } + + actualSite := extractFromObjectsMap(actualNestedObjectsMap, fmt.Sprintf("%p", vw.VirtualMachine.Site)) + intendedSite := extractFromObjectsMap(intendedNestedObjects, fmt.Sprintf("%p", vw.VirtualMachine.Site)) + + actualCluster := extractFromObjectsMap(actualNestedObjectsMap, fmt.Sprintf("%p", vw.VirtualMachine.Cluster)) + intendedCluster := extractFromObjectsMap(intendedNestedObjects, fmt.Sprintf("%p", vw.VirtualMachine.Cluster)) + + actualRole := extractFromObjectsMap(actualNestedObjectsMap, fmt.Sprintf("%p", vw.VirtualMachine.Role)) + intendedRole := extractFromObjectsMap(intendedNestedObjects, fmt.Sprintf("%p", vw.VirtualMachine.Role)) + + actualDevice := extractFromObjectsMap(actualNestedObjectsMap, fmt.Sprintf("%p", vw.VirtualMachine.Device)) + intendedDevice := extractFromObjectsMap(intendedNestedObjects, fmt.Sprintf("%p", vw.VirtualMachine.Device)) + + actualPlatform := extractFromObjectsMap(actualNestedObjectsMap, fmt.Sprintf("%p", vw.VirtualMachine.Platform)) + intendedPlatform := extractFromObjectsMap(intendedNestedObjects, fmt.Sprintf("%p", vw.VirtualMachine.Platform)) + + reconciliationRequired := true + + if intended != nil { + currentNestedObjectsMap := make(map[string]ComparableData) + currentNestedObjects, err := intended.NestedObjects() + if err != nil { + return nil, err + } + for _, obj := range currentNestedObjects { + currentNestedObjectsMap[fmt.Sprintf("%p", obj.Data())] = obj + } + + vw.VirtualMachine.ID = intended.VirtualMachine.ID + vw.VirtualMachine.Name = intended.VirtualMachine.Name + + if vw.VirtualMachine.Status == nil { + vw.VirtualMachine.Status = intended.VirtualMachine.Status + } + + if actualSite.IsPlaceholder() && intended.VirtualMachine.Site != nil { + intendedSite = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.VirtualMachine.Site)) + } + + siteObjectsToReconcile, siteErr := actualSite.Patch(intendedSite, intendedNestedObjects) + if siteErr != nil { + return nil, siteErr + } + + site, err := copyData(actualSite.Data().(*DcimSite)) + if err != nil { + return nil, err + } + site.Tags = nil + + if !actualSite.HasChanged() { + site = &DcimSite{ + ID: actualSite.ID(), + } + + intendedSiteID := intendedSite.ID() + if intended.VirtualMachine.Site != nil { + intendedSiteID = intended.VirtualMachine.Site.ID + } + + intended.VirtualMachine.Site = &DcimSite{ + ID: intendedSiteID, + } + } + + vw.VirtualMachine.Site = site + + vw.objectsToReconcile = append(vw.objectsToReconcile, siteObjectsToReconcile...) + + if actualCluster.IsPlaceholder() && intended.VirtualMachine.Cluster != nil { + intendedCluster = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.VirtualMachine.Cluster)) + } + + clusterObjectsToReconcile, clusterErr := actualCluster.Patch(intendedCluster, intendedNestedObjects) + if clusterErr != nil { + return nil, clusterErr + } + + cluster, err := copyData(actualCluster.Data().(*VirtualizationCluster)) + if err != nil { + return nil, err + } + cluster.Tags = nil + + if !actualCluster.HasChanged() { + cluster = &VirtualizationCluster{ + ID: actualCluster.ID(), + } + + intendedClusterID := intendedCluster.ID() + if intended.VirtualMachine.Cluster != nil { + intendedClusterID = intended.VirtualMachine.Cluster.ID + } + + intended.VirtualMachine.Cluster = &VirtualizationCluster{ + ID: intendedClusterID, + } + } + + vw.VirtualMachine.Cluster = cluster + + vw.objectsToReconcile = append(vw.objectsToReconcile, clusterObjectsToReconcile...) + + if actualRole.IsPlaceholder() && intended.VirtualMachine.Role != nil { + intendedRole = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.VirtualMachine.Role)) + } + + roleObjectsToReconcile, roleErr := actualRole.Patch(intendedRole, intendedNestedObjects) + if roleErr != nil { + return nil, roleErr + } + + role, err := copyData(actualRole.Data().(*DcimDeviceRole)) + if err != nil { + return nil, err + } + role.Tags = nil + + if !actualRole.HasChanged() { + role = &DcimDeviceRole{ + ID: actualRole.ID(), + } + + intendedRoleID := intendedRole.ID() + if intended.VirtualMachine.Role != nil { + intendedRoleID = intended.VirtualMachine.Role.ID + } + + intended.VirtualMachine.Role = &DcimDeviceRole{ + ID: intendedRoleID, + } + } + + vw.VirtualMachine.Role = role + + vw.objectsToReconcile = append(vw.objectsToReconcile, roleObjectsToReconcile...) + + if actualDevice != nil { + if actualDevice.IsPlaceholder() && intended.VirtualMachine.Device != nil { + intendedDevice = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.VirtualMachine.Device)) + } + + deviceObjectsToReconcile, deviceErr := actualDevice.Patch(intendedDevice, intendedNestedObjects) + if deviceErr != nil { + return nil, deviceErr + } + + device, err := copyData(actualDevice.Data().(*DcimDevice)) + if err != nil { + return nil, err + } + device.Tags = nil + + if !actualDevice.HasChanged() { + device = &DcimDevice{ + ID: actualDevice.ID(), + } + + intendedDeviceID := intendedDevice.ID() + if intended.VirtualMachine.Device != nil { + intendedDeviceID = intended.VirtualMachine.Device.ID + } + + intended.VirtualMachine.Device = &DcimDevice{ + ID: intendedDeviceID, + } + } + + vw.VirtualMachine.Device = device + + vw.objectsToReconcile = append(vw.objectsToReconcile, deviceObjectsToReconcile...) + } + + if actualPlatform != nil { + if actualPlatform.IsPlaceholder() && intended.VirtualMachine.Platform != nil { + intendedPlatform = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.VirtualMachine.Platform)) + } + + platformObjectsToReconcile, platformErr := actualPlatform.Patch(intendedPlatform, intendedNestedObjects) + if platformErr != nil { + return nil, platformErr + } + + platform, err := copyData(actualPlatform.Data().(*DcimPlatform)) + if err != nil { + return nil, err + } + platform.Tags = nil + + if !actualPlatform.HasChanged() { + platform = &DcimPlatform{ + ID: actualPlatform.ID(), + } + + intendedPlatformID := intendedPlatform.ID() + if intended.VirtualMachine.Platform != nil { + intendedPlatformID = intended.VirtualMachine.Platform.ID + } + + intended.VirtualMachine.Platform = &DcimPlatform{ + ID: intendedPlatformID, + } + } + + vw.VirtualMachine.Platform = platform + + vw.objectsToReconcile = append(vw.objectsToReconcile, platformObjectsToReconcile...) + } else { + if intended.VirtualMachine.Platform != nil { + platformID := intended.VirtualMachine.Platform.ID + vw.VirtualMachine.Platform = &DcimPlatform{ + ID: platformID, + } + intended.VirtualMachine.Platform = &DcimPlatform{ + ID: platformID, + } + } + } + + if vw.VirtualMachine.Vcpus == nil { + vw.VirtualMachine.Vcpus = intended.VirtualMachine.Vcpus + } + if vw.VirtualMachine.Memory == nil { + vw.VirtualMachine.Memory = intended.VirtualMachine.Memory + } + if vw.VirtualMachine.Disk == nil { + vw.VirtualMachine.Disk = intended.VirtualMachine.Disk + } + if vw.VirtualMachine.Comments == nil { + vw.VirtualMachine.Comments = intended.VirtualMachine.Comments + } + + if vw.VirtualMachine.Description == nil { + vw.VirtualMachine.Description = intended.VirtualMachine.Description + } + + tagsToMerge := mergeTags(vw.VirtualMachine.Tags, intended.VirtualMachine.Tags, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.VirtualMachine.Tags = tagsToMerge + } + + for _, t := range vw.VirtualMachine.Tags { + if t.ID == 0 { + vw.objectsToReconcile = append(vw.objectsToReconcile, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + actualHash, _ := hashstructure.Hash(vw.Data(), hashstructure.FormatV2, nil) + intendedHash, _ := hashstructure.Hash(intended.Data(), hashstructure.FormatV2, nil) + + reconciliationRequired = actualHash != intendedHash + } else { + siteObjectsToReconcile, siteErr := actualSite.Patch(intendedSite, intendedNestedObjects) + if siteErr != nil { + return nil, siteErr + } + + site, err := copyData(actualSite.Data().(*DcimSite)) + if err != nil { + return nil, err + } + site.Tags = nil + + if !actualSite.HasChanged() { + site = &DcimSite{ + ID: actualSite.ID(), + } + } + vw.VirtualMachine.Site = site + + vw.objectsToReconcile = append(vw.objectsToReconcile, siteObjectsToReconcile...) + + clusterObjectsToReconcile, clusterErr := actualCluster.Patch(intendedCluster, intendedNestedObjects) + if clusterErr != nil { + return nil, clusterErr + } + + cluster, err := copyData(actualCluster.Data().(*VirtualizationCluster)) + if err != nil { + return nil, err + } + cluster.Tags = nil + + if !actualCluster.HasChanged() { + cluster = &VirtualizationCluster{ + ID: actualCluster.ID(), + } + } + vw.VirtualMachine.Cluster = cluster + + vw.objectsToReconcile = append(vw.objectsToReconcile, clusterObjectsToReconcile...) + + roleObjectsToReconcile, roleErr := actualRole.Patch(intendedRole, intendedNestedObjects) + if roleErr != nil { + return nil, roleErr + } + + role, err := copyData(actualRole.Data().(*DcimDeviceRole)) + if err != nil { + return nil, err + } + role.Tags = nil + + if !actualRole.HasChanged() { + role = &DcimDeviceRole{ + ID: actualRole.ID(), + } + } + vw.VirtualMachine.Role = role + + vw.objectsToReconcile = append(vw.objectsToReconcile, roleObjectsToReconcile...) + + if actualPlatform != nil { + platformObjectsToReconcile, platformErr := actualPlatform.Patch(intendedPlatform, intendedNestedObjects) + if platformErr != nil { + return nil, platformErr + } + + platform, err := copyData(actualPlatform.Data().(*DcimPlatform)) + if err != nil { + return nil, err + } + platform.Tags = nil + + if !actualPlatform.HasChanged() { + platform = &DcimPlatform{ + ID: actualPlatform.ID(), + } + } + vw.VirtualMachine.Platform = platform + + vw.objectsToReconcile = append(vw.objectsToReconcile, platformObjectsToReconcile...) + } + + if actualDevice != nil { + deviceObjectsToReconcile, deviceErr := actualDevice.Patch(intendedDevice, intendedNestedObjects) + if deviceErr != nil { + return nil, deviceErr + } + + device, err := copyData(actualDevice.Data().(*DcimDevice)) + if err != nil { + return nil, err + } + device.Tags = nil + + if !actualDevice.HasChanged() { + device = &DcimDevice{ + ID: actualDevice.ID(), + } + } + vw.VirtualMachine.Device = device + + vw.objectsToReconcile = append(vw.objectsToReconcile, deviceObjectsToReconcile...) + } + + tagsToMerge := mergeTags(vw.VirtualMachine.Tags, nil, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.VirtualMachine.Tags = tagsToMerge + } + + for _, t := range vw.VirtualMachine.Tags { + if t.ID == 0 { + vw.objectsToReconcile = append(vw.objectsToReconcile, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + } + + if reconciliationRequired { + vw.hasChanged = true + vw.objectsToReconcile = append(vw.objectsToReconcile, vw) + } + + dedupObjectsToReconcile, err := dedupObjectsToReconcile(vw.objectsToReconcile) + if err != nil { + return nil, err + } + vw.objectsToReconcile = dedupObjectsToReconcile + + return vw.objectsToReconcile, nil +} + +// SetDefaults sets the default values for the device type +func (vw *VirtualizationVirtualMachineDataWrapper) SetDefaults() {} + +// VirtualizationVMInterfaceDataWrapper represents a virtualization VM interface data wrapper +type VirtualizationVMInterfaceDataWrapper struct { + BaseDataWrapper + VMInterface *VirtualizationVMInterface +} + +func (*VirtualizationVMInterfaceDataWrapper) comparableData() {} + +// Data returns the DeviceRole +func (vw *VirtualizationVMInterfaceDataWrapper) Data() any { + return vw.VMInterface +} + +// IsValid returns true if the DeviceRole is not nil +func (vw *VirtualizationVMInterfaceDataWrapper) IsValid() bool { + if vw.VMInterface != nil && !vw.hasParent && vw.VMInterface.Name == "" { + vw.VMInterface = nil + } + return vw.VMInterface != nil +} + +// Normalise normalises the data +func (vw *VirtualizationVMInterfaceDataWrapper) Normalise() { + if vw.IsValid() && vw.VMInterface.Tags != nil && len(vw.VMInterface.Tags) == 0 { + vw.VMInterface.Tags = nil + } + vw.intended = true +} + +// NestedObjects returns all nested objects +func (vw *VirtualizationVMInterfaceDataWrapper) NestedObjects() ([]ComparableData, error) { + if len(vw.nestedObjects) > 0 { + return vw.nestedObjects, nil + } + + if vw.VMInterface != nil && vw.hasParent && vw.VMInterface.Name == "" { + vw.VMInterface = nil + } + + objects := make([]ComparableData, 0) + + if vw.VMInterface == nil && vw.intended { + return objects, nil + } + + if vw.VMInterface == nil && vw.hasParent { + vw.VMInterface = NewVirtualizationVMInterface() + vw.placeholder = true + } + + virtualMachine := VirtualizationVirtualMachineDataWrapper{VirtualMachine: vw.VMInterface.VirtualMachine, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} + + vmo, err := virtualMachine.NestedObjects() + if err != nil { + return nil, err + } + + objects = append(objects, vmo...) + + vw.VMInterface.VirtualMachine = virtualMachine.VirtualMachine + + if vw.VMInterface.Tags != nil { + for _, t := range vw.VMInterface.Tags { + if t.Slug == "" { + t.Slug = slug.Make(t.Name) + } + objects = append(objects, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + vw.nestedObjects = objects + + objects = append(objects, vw) + + return objects, nil +} + +// DataType returns the data type +func (vw *VirtualizationVMInterfaceDataWrapper) DataType() string { + return VirtualizationVMInterfaceObjectType +} + +// ObjectStateQueryParams returns the query parameters needed to retrieve its object state +func (vw *VirtualizationVMInterfaceDataWrapper) ObjectStateQueryParams() map[string]string { + params := map[string]string{ + "q": vw.VMInterface.Name, + } + if vw.VMInterface.VirtualMachine != nil { + params["virtual_machine__name"] = vw.VMInterface.VirtualMachine.Name + + if vw.VMInterface.VirtualMachine.Site != nil { + params["virtual_machine__site__name"] = vw.VMInterface.VirtualMachine.Site.Name + } + } + return params +} + +// ID returns the ID of the data +func (vw *VirtualizationVMInterfaceDataWrapper) ID() int { + return vw.VMInterface.ID +} + +// Patch creates patches between the actual, intended and current data +func (vw *VirtualizationVMInterfaceDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { + intended, ok := cmp.(*VirtualizationVMInterfaceDataWrapper) + + if !ok && intended != nil { + return nil, errors.New("invalid data type") + } + + actualNestedObjectsMap := make(map[string]ComparableData) + for _, obj := range vw.nestedObjects { + actualNestedObjectsMap[fmt.Sprintf("%p", obj.Data())] = obj + } + + actualVirtualMachine := extractFromObjectsMap(actualNestedObjectsMap, fmt.Sprintf("%p", vw.VMInterface.VirtualMachine)) + intendedVirtualMachine := extractFromObjectsMap(intendedNestedObjects, fmt.Sprintf("%p", vw.VMInterface.VirtualMachine)) + + reconciliationRequired := true + + if intended != nil { + currentNestedObjectsMap := make(map[string]ComparableData) + currentNestedObjects, err := intended.NestedObjects() + if err != nil { + return nil, err + } + for _, obj := range currentNestedObjects { + currentNestedObjectsMap[fmt.Sprintf("%p", obj.Data())] = obj + } + + vw.VMInterface.ID = intended.VMInterface.ID + vw.VMInterface.Name = intended.VMInterface.Name + + if actualVirtualMachine.IsPlaceholder() && intended.VMInterface.VirtualMachine != nil { + intendedVirtualMachine = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.VMInterface.VirtualMachine)) + } + + virtualMachineObjectsToReconcile, virtualMachineErr := actualVirtualMachine.Patch(intendedVirtualMachine, intendedNestedObjects) + if virtualMachineErr != nil { + return nil, virtualMachineErr + } + + virtualMachine, err := copyData(actualVirtualMachine.Data().(*VirtualizationVirtualMachine)) + if err != nil { + return nil, err + } + virtualMachine.Tags = nil + + if !actualVirtualMachine.HasChanged() { + virtualMachine = &VirtualizationVirtualMachine{ + ID: actualVirtualMachine.ID(), + } + + intendedVirtualMachineID := intendedVirtualMachine.ID() + if intended.VMInterface.VirtualMachine != nil { + intendedVirtualMachineID = intended.VMInterface.VirtualMachine.ID + } + + intended.VMInterface.VirtualMachine = &VirtualizationVirtualMachine{ + ID: intendedVirtualMachineID, + } + } + + vw.VMInterface.VirtualMachine = virtualMachine + + vw.objectsToReconcile = append(vw.objectsToReconcile, virtualMachineObjectsToReconcile...) + + if vw.VMInterface.Enabled == nil { + vw.VMInterface.Enabled = intended.VMInterface.Enabled + } + + if vw.VMInterface.MTU == nil { + vw.VMInterface.MTU = intended.VMInterface.MTU + } + + if vw.VMInterface.MACAddress == nil { + vw.VMInterface.MACAddress = intended.VMInterface.MACAddress + } + + if vw.VMInterface.Description == nil { + vw.VMInterface.Description = intended.VMInterface.Description + } + + tagsToMerge := mergeTags(vw.VMInterface.Tags, intended.VMInterface.Tags, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.VMInterface.Tags = tagsToMerge + } + + for _, t := range vw.VMInterface.Tags { + if t.ID == 0 { + vw.objectsToReconcile = append(vw.objectsToReconcile, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + actualHash, _ := hashstructure.Hash(vw.Data(), hashstructure.FormatV2, nil) + intendedHash, _ := hashstructure.Hash(intended.Data(), hashstructure.FormatV2, nil) + + reconciliationRequired = actualHash != intendedHash + } else { + virtualMachineObjectsToReconcile, virtualMachineErr := actualVirtualMachine.Patch(intendedVirtualMachine, intendedNestedObjects) + if virtualMachineErr != nil { + return nil, virtualMachineErr + } + + virtualMachine, err := copyData(actualVirtualMachine.Data().(*VirtualizationVirtualMachine)) + if err != nil { + return nil, err + } + virtualMachine.Tags = nil + + if !actualVirtualMachine.HasChanged() { + virtualMachine = &VirtualizationVirtualMachine{ + ID: actualVirtualMachine.ID(), + } + } + vw.VMInterface.VirtualMachine = virtualMachine + + vw.objectsToReconcile = append(vw.objectsToReconcile, virtualMachineObjectsToReconcile...) + + tagsToMerge := mergeTags(vw.VMInterface.Tags, nil, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.VMInterface.Tags = tagsToMerge + } + + for _, t := range vw.VMInterface.Tags { + if t.ID == 0 { + vw.objectsToReconcile = append(vw.objectsToReconcile, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + } + + if reconciliationRequired { + vw.hasChanged = true + vw.objectsToReconcile = append(vw.objectsToReconcile, vw) + } + + return vw.objectsToReconcile, nil +} + +// SetDefaults sets the default values for the device type +func (vw *VirtualizationVMInterfaceDataWrapper) SetDefaults() {} + +// VirtualizationVirtualDiskDataWrapper represents a virtualization disk data wrapper +type VirtualizationVirtualDiskDataWrapper struct { + BaseDataWrapper + VirtualDisk *VirtualizationVirtualDisk +} + +func (*VirtualizationVirtualDiskDataWrapper) comparableData() {} + +// Data returns the DeviceRole +func (vw *VirtualizationVirtualDiskDataWrapper) Data() any { + return vw.VirtualDisk +} + +// IsValid returns true if the DeviceRole is not nil +func (vw *VirtualizationVirtualDiskDataWrapper) IsValid() bool { + if vw.VirtualDisk != nil && !vw.hasParent && vw.VirtualDisk.Name == "" { + vw.VirtualDisk = nil + } + return vw.VirtualDisk != nil +} + +// Normalise normalises the data +func (vw *VirtualizationVirtualDiskDataWrapper) Normalise() { + if vw.IsValid() && vw.VirtualDisk.Tags != nil && len(vw.VirtualDisk.Tags) == 0 { + vw.VirtualDisk.Tags = nil + } + vw.intended = true +} + +// NestedObjects returns all nested objects +func (vw *VirtualizationVirtualDiskDataWrapper) NestedObjects() ([]ComparableData, error) { + if len(vw.nestedObjects) > 0 { + return vw.nestedObjects, nil + } + + if vw.VirtualDisk != nil && vw.hasParent && vw.VirtualDisk.Name == "" { + vw.VirtualDisk = nil + } + + objects := make([]ComparableData, 0) + + if vw.VirtualDisk == nil && vw.intended { + return objects, nil + } + + if vw.VirtualDisk == nil && vw.hasParent { + vw.VirtualDisk = NewVirtualizationVirtualDisk() + vw.placeholder = true + } + + virtualMachine := VirtualizationVirtualMachineDataWrapper{VirtualMachine: vw.VirtualDisk.VirtualMachine, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} + + vmo, err := virtualMachine.NestedObjects() + if err != nil { + return nil, err + } + + objects = append(objects, vmo...) + + vw.VirtualDisk.VirtualMachine = virtualMachine.VirtualMachine + + if vw.VirtualDisk.Tags != nil { + for _, t := range vw.VirtualDisk.Tags { + if t.Slug == "" { + t.Slug = slug.Make(t.Name) + } + objects = append(objects, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + vw.nestedObjects = objects + + objects = append(objects, vw) + + return objects, nil +} + +// DataType returns the data type +func (vw *VirtualizationVirtualDiskDataWrapper) DataType() string { + return VirtualizationVirtualDiskObjectType +} + +// ObjectStateQueryParams returns the query parameters needed to retrieve its object state +func (vw *VirtualizationVirtualDiskDataWrapper) ObjectStateQueryParams() map[string]string { + params := map[string]string{ + "q": vw.VirtualDisk.Name, + } + if vw.VirtualDisk.VirtualMachine != nil { + params["virtual_machine__name"] = vw.VirtualDisk.VirtualMachine.Name + + if vw.VirtualDisk.VirtualMachine.Site != nil { + params["virtual_machine__site__name"] = vw.VirtualDisk.VirtualMachine.Site.Name + } + } + return params +} + +// ID returns the ID of the data +func (vw *VirtualizationVirtualDiskDataWrapper) ID() int { + return vw.VirtualDisk.ID +} + +// Patch creates patches between the actual, intended and current data +func (vw *VirtualizationVirtualDiskDataWrapper) Patch(cmp ComparableData, intendedNestedObjects map[string]ComparableData) ([]ComparableData, error) { + intended, ok := cmp.(*VirtualizationVirtualDiskDataWrapper) + + if !ok && intended != nil { + return nil, errors.New("invalid data type") + } + + actualNestedObjectsMap := make(map[string]ComparableData) + for _, obj := range vw.nestedObjects { + actualNestedObjectsMap[fmt.Sprintf("%p", obj.Data())] = obj + } + + actualVirtualMachine := extractFromObjectsMap(actualNestedObjectsMap, fmt.Sprintf("%p", vw.VirtualDisk.VirtualMachine)) + intendedVirtualMachine := extractFromObjectsMap(intendedNestedObjects, fmt.Sprintf("%p", vw.VirtualDisk.VirtualMachine)) + + reconciliationRequired := true + + if intended != nil { + currentNestedObjectsMap := make(map[string]ComparableData) + currentNestedObjects, err := intended.NestedObjects() + if err != nil { + return nil, err + } + for _, obj := range currentNestedObjects { + currentNestedObjectsMap[fmt.Sprintf("%p", obj.Data())] = obj + } + + vw.VirtualDisk.ID = intended.VirtualDisk.ID + vw.VirtualDisk.Name = intended.VirtualDisk.Name + + if actualVirtualMachine.IsPlaceholder() && intended.VirtualDisk.VirtualMachine != nil { + intendedVirtualMachine = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.VirtualDisk.VirtualMachine)) + } + + virtualMachineObjectsToReconcile, virtualMachineErr := actualVirtualMachine.Patch(intendedVirtualMachine, intendedNestedObjects) + if virtualMachineErr != nil { + return nil, virtualMachineErr + } + + virtualMachine, err := copyData(actualVirtualMachine.Data().(*VirtualizationVirtualMachine)) + if err != nil { + return nil, err + } + virtualMachine.Tags = nil + + if !actualVirtualMachine.HasChanged() { + virtualMachine = &VirtualizationVirtualMachine{ + ID: actualVirtualMachine.ID(), + } + + intendedVirtualMachineID := intendedVirtualMachine.ID() + if intended.VirtualDisk.VirtualMachine != nil { + intendedVirtualMachineID = intended.VirtualDisk.VirtualMachine.ID + } + + intended.VirtualDisk.VirtualMachine = &VirtualizationVirtualMachine{ + ID: intendedVirtualMachineID, + } + } + + vw.VirtualDisk.VirtualMachine = virtualMachine + + vw.objectsToReconcile = append(vw.objectsToReconcile, virtualMachineObjectsToReconcile...) + + if vw.VirtualDisk.Description == nil { + vw.VirtualDisk.Description = intended.VirtualDisk.Description + } + + tagsToMerge := mergeTags(vw.VirtualDisk.Tags, intended.VirtualDisk.Tags, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.VirtualDisk.Tags = tagsToMerge + } + + for _, t := range vw.VirtualDisk.Tags { + if t.ID == 0 { + vw.objectsToReconcile = append(vw.objectsToReconcile, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + + actualHash, _ := hashstructure.Hash(vw.Data(), hashstructure.FormatV2, nil) + intendedHash, _ := hashstructure.Hash(intended.Data(), hashstructure.FormatV2, nil) + + reconciliationRequired = actualHash != intendedHash + } else { + virtualMachineObjectsToReconcile, virtualMachineErr := actualVirtualMachine.Patch(intendedVirtualMachine, intendedNestedObjects) + if virtualMachineErr != nil { + return nil, virtualMachineErr + } + + virtualMachine, err := copyData(actualVirtualMachine.Data().(*VirtualizationVirtualMachine)) + if err != nil { + return nil, err + } + virtualMachine.Tags = nil + + if !actualVirtualMachine.HasChanged() { + virtualMachine = &VirtualizationVirtualMachine{ + ID: actualVirtualMachine.ID(), + } + } + vw.VirtualDisk.VirtualMachine = virtualMachine + + vw.objectsToReconcile = append(vw.objectsToReconcile, virtualMachineObjectsToReconcile...) + + tagsToMerge := mergeTags(vw.VirtualDisk.Tags, nil, intendedNestedObjects) + + if len(tagsToMerge) > 0 { + vw.VirtualDisk.Tags = tagsToMerge + } + + for _, t := range vw.VirtualDisk.Tags { + if t.ID == 0 { + vw.objectsToReconcile = append(vw.objectsToReconcile, &TagDataWrapper{Tag: t, hasParent: true}) + } + } + } + + if reconciliationRequired { + vw.hasChanged = true + vw.objectsToReconcile = append(vw.objectsToReconcile, vw) + } + + return vw.objectsToReconcile, nil +} + +// SetDefaults sets the default values for the device type +func (vw *VirtualizationVirtualDiskDataWrapper) SetDefaults() {} diff --git a/diode-server/netbox/wrappers.go b/diode-server/netbox/wrappers.go index e71024d2..422fd9ed 100644 --- a/diode-server/netbox/wrappers.go +++ b/diode-server/netbox/wrappers.go @@ -1949,6 +1949,18 @@ func NewDataWrapper(dataType string) (ComparableData, error) { return &IpamIPAddressDataWrapper{}, nil case IpamPrefixObjectType: return &IpamPrefixDataWrapper{}, nil + case VirtualizationClusterGroupObjectType: + return &VirtualizationClusterGroupDataWrapper{}, nil + case VirtualizationClusterTypeObjectType: + return &VirtualizationClusterTypeDataWrapper{}, nil + case VirtualizationClusterObjectType: + return &VirtualizationClusterDataWrapper{}, nil + case VirtualizationVirtualMachineObjectType: + return &VirtualizationVirtualMachineDataWrapper{}, nil + case VirtualizationVMInterfaceObjectType: + return &VirtualizationVMInterfaceDataWrapper{}, nil + case VirtualizationVirtualDiskObjectType: + return &VirtualizationVirtualDiskDataWrapper{}, nil default: return nil, fmt.Errorf("unsupported data type %s", dataType) } diff --git a/diode-server/netboxdiodeplugin/client.go b/diode-server/netboxdiodeplugin/client.go index 1f136832..c771de84 100644 --- a/diode-server/netboxdiodeplugin/client.go +++ b/diode-server/netboxdiodeplugin/client.go @@ -489,6 +489,42 @@ func wrapObjectState(dataType string, object any) (any, error) { }{ Prefix: object, }, nil + case netbox.VirtualizationClusterGroupObjectType: + return struct { + ClusterGroup any + }{ + ClusterGroup: object, + }, nil + case netbox.VirtualizationClusterTypeObjectType: + return struct { + ClusterType any + }{ + ClusterType: object, + }, nil + case netbox.VirtualizationClusterObjectType: + return struct { + Cluster any + }{ + Cluster: object, + }, nil + case netbox.VirtualizationVirtualMachineObjectType: + return struct { + VirtualMachine any + }{ + VirtualMachine: object, + }, nil + case netbox.VirtualizationVMInterfaceObjectType: + return struct { + VMInterface any + }{ + VMInterface: object, + }, nil + case netbox.VirtualizationVirtualDiskObjectType: + return struct { + VirtualDisk any + }{ + VirtualDisk: object, + }, nil default: return nil, fmt.Errorf("unsupported data type %s", dataType) } diff --git a/diode-server/netboxdiodeplugin/client_test.go b/diode-server/netboxdiodeplugin/client_test.go index 97c645d3..37ac2e5e 100644 --- a/diode-server/netboxdiodeplugin/client_test.go +++ b/diode-server/netboxdiodeplugin/client_test.go @@ -346,6 +346,114 @@ func TestRetrieveObjectState(t *testing.T) { tlsSkipVerify: true, shouldError: false, }, + { + name: "valid response for Virtualization Cluster Group", + params: netboxdiodeplugin.RetrieveObjectStateQueryParams{ObjectType: netbox.VirtualizationClusterGroupObjectType, ObjectID: 1}, + mockServerResponse: `{"object_type":"virtualization.clustergroup","object_change_id":1,"object":{"id":1,"name":"test"}}`, + apiKey: "foobar", + response: &netboxdiodeplugin.ObjectState{ + ObjectType: netbox.VirtualizationClusterGroupObjectType, + ObjectChangeID: 1, + Object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "test", + }, + }, + }, + tlsSkipVerify: true, + shouldError: false, + }, + { + name: "valid response for Virtualization Cluster Type", + params: netboxdiodeplugin.RetrieveObjectStateQueryParams{ObjectType: netbox.VirtualizationClusterTypeObjectType, ObjectID: 1}, + mockServerResponse: `{"object_type":"virtualization.clustertype","object_change_id":1,"object":{"id":1,"name":"test"}}`, + apiKey: "foobar", + response: &netboxdiodeplugin.ObjectState{ + ObjectType: netbox.VirtualizationClusterTypeObjectType, + ObjectChangeID: 1, + Object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "test", + }, + }, + }, + tlsSkipVerify: true, + shouldError: false, + }, + { + name: "valid response for Virtualization Cluster", + params: netboxdiodeplugin.RetrieveObjectStateQueryParams{ObjectType: netbox.VirtualizationClusterObjectType, ObjectID: 1}, + mockServerResponse: `{"object_type":"virtualization.cluster","object_change_id":1,"object":{"id":1,"name":"test"}}`, + apiKey: "foobar", + response: &netboxdiodeplugin.ObjectState{ + ObjectType: netbox.VirtualizationClusterObjectType, + ObjectChangeID: 1, + Object: &netbox.VirtualizationClusterDataWrapper{ + Cluster: &netbox.VirtualizationCluster{ + ID: 1, + Name: "test", + }, + }, + }, + tlsSkipVerify: true, + shouldError: false, + }, + { + name: "valid response for Virtualization Virtual Machine", + params: netboxdiodeplugin.RetrieveObjectStateQueryParams{ObjectType: netbox.VirtualizationVirtualMachineObjectType, ObjectID: 1}, + mockServerResponse: `{"object_type":"virtualization.virtualmachine","object_change_id":1,"object":{"id":1,"name":"test"}}`, + apiKey: "foobar", + response: &netboxdiodeplugin.ObjectState{ + ObjectType: netbox.VirtualizationVirtualMachineObjectType, + ObjectChangeID: 1, + Object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + ID: 1, + Name: "test", + }, + }, + }, + tlsSkipVerify: true, + shouldError: false, + }, + { + name: "valid response for Virtualization Interface", + params: netboxdiodeplugin.RetrieveObjectStateQueryParams{ObjectType: netbox.VirtualizationVMInterfaceObjectType, ObjectID: 1}, + mockServerResponse: `{"object_type":"virtualization.vminterface","object_change_id":1,"object":{"id":1,"name":"test"}}`, + apiKey: "foobar", + response: &netboxdiodeplugin.ObjectState{ + ObjectType: netbox.VirtualizationVMInterfaceObjectType, + ObjectChangeID: 1, + Object: &netbox.VirtualizationVMInterfaceDataWrapper{ + VMInterface: &netbox.VirtualizationVMInterface{ + ID: 1, + Name: "test", + }, + }, + }, + tlsSkipVerify: true, + shouldError: false, + }, + { + name: "valid response for Virtualization Virtual Disk", + params: netboxdiodeplugin.RetrieveObjectStateQueryParams{ObjectType: netbox.VirtualizationVirtualDiskObjectType, ObjectID: 1}, + mockServerResponse: `{"object_type":"virtualization.virtualdisk","object_change_id":1,"object":{"id":1,"name":"test"}}`, + apiKey: "foobar", + response: &netboxdiodeplugin.ObjectState{ + ObjectType: netbox.VirtualizationVirtualDiskObjectType, + ObjectChangeID: 1, + Object: &netbox.VirtualizationVirtualDiskDataWrapper{ + VirtualDisk: &netbox.VirtualizationVirtualDisk{ + ID: 1, + Name: "test", + }, + }, + }, + tlsSkipVerify: true, + shouldError: false, + }, { name: "valid response for DCIM device with query and additional attributes", params: netboxdiodeplugin.RetrieveObjectStateQueryParams{ diff --git a/diode-server/reconciler/changeset/changeset_test.go b/diode-server/reconciler/changeset/changeset_dcim_test.go similarity index 69% rename from diode-server/reconciler/changeset/changeset_test.go rename to diode-server/reconciler/changeset/changeset_dcim_test.go index 9ef7d7d1..0992feaf 100644 --- a/diode-server/reconciler/changeset/changeset_test.go +++ b/diode-server/reconciler/changeset/changeset_dcim_test.go @@ -14,7 +14,7 @@ import ( "github.com/netboxlabs/diode/diode-server/reconciler/changeset" ) -func TestPrepare(t *testing.T) { +func TestDcimPrepare(t *testing.T) { type mockRetrieveObjectState struct { objectType string objectID int @@ -3981,1844 +3981,7 @@ func TestPrepare(t *testing.T) { wantErr: true, }, { - name: "[P14] ingest ipam.ipaddress with address only - existing object not found - create", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.ipaddress", - "entity": { - "IpAddress": { - "address": "192.168.0.1/22" - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "ipam.ipaddress", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.1/22"}, - objectChangeID: 0, - object: &netbox.IpamIPAddressDataWrapper{ - IPAddress: nil, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{ - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "ipam.ipaddress", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.IpamIPAddress{ - Address: "192.168.0.1/22", - Status: &netbox.DefaultIPAddressStatus, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "[P14] ingest ipam.ipaddress with address and interface - existing object not found - create", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.ipaddress", - "entity": { - "IpAddress": { - "address": "192.168.0.1/22", - "AssignedObject": { - "Interface": { - "name": "GigabitEthernet0/0/0" - } - } - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "dcim.site", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimSiteDataWrapper{ - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - }, - }, - { - objectType: "dcim.manufacturer", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimManufacturerDataWrapper{ - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - { - objectType: "dcim.platform", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimPlatformDataWrapper{ - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicetype", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceTypeDataWrapper{ - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicerole", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceRoleDataWrapper{ - DeviceRole: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - }, - }, - { - objectType: "dcim.device", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceDataWrapper{ - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - { - objectType: "dcim.interface", - objectID: 0, - queryParams: map[string]string{"q": "GigabitEthernet0/0/0", "device__name": "undefined", "device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimInterfaceDataWrapper{ - Interface: &netbox.DcimInterface{ - ID: 1, - Name: "GigabitEthernet0/0/0", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - }, - { - objectType: "ipam.ipaddress", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet0/0/0", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.IpamIPAddressDataWrapper{ - IPAddress: nil, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{ - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "ipam.ipaddress", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.IpamIPAddress{ - Address: "192.168.0.1/22", - Status: &netbox.DefaultIPAddressStatus, - AssignedObject: &netbox.IPAddressInterface{ - Interface: &netbox.DcimInterface{ - ID: 1, - Device: &netbox.DcimDevice{ - ID: 1, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "[P14] ingest ipam.ipaddress with address and a new interface - existing IP address and interface not found - create an interface and IP address", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.ipaddress", - "entity": { - "IpAddress": { - "address": "192.168.0.1/22", - "AssignedObject": { - "Interface": { - "name": "GigabitEthernet0/0/0" - } - } - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "dcim.site", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimSiteDataWrapper{ - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - }, - }, - { - objectType: "dcim.manufacturer", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimManufacturerDataWrapper{ - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - { - objectType: "dcim.platform", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimPlatformDataWrapper{ - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicetype", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceTypeDataWrapper{ - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicerole", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceRoleDataWrapper{ - DeviceRole: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - }, - }, - { - objectType: "dcim.device", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceDataWrapper{ - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - { - objectType: "dcim.interface", - objectID: 0, - queryParams: map[string]string{"q": "GigabitEthernet0/0/0", "device__name": "undefined", "device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimInterfaceDataWrapper{ - Interface: nil, - }, - }, - { - objectType: "ipam.ipaddress", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet0/0/0", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.IpamIPAddressDataWrapper{ - IPAddress: nil, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{ - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "dcim.interface", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.DcimInterface{ - Name: "GigabitEthernet0/0/0", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - }, - }, - }, - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "ipam.ipaddress", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.IpamIPAddress{ - Address: "192.168.0.1/22", - Status: &netbox.DefaultIPAddressStatus, - AssignedObject: &netbox.IPAddressInterface{ - Interface: &netbox.DcimInterface{ - Name: "GigabitEthernet0/0/0", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "[P14] ingest ipam.ipaddress with address and a new interface - IP address found assigned to a different interface - create the interface and the IP address", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.ipaddress", - "entity": { - "IpAddress": { - "address": "192.168.0.1/22", - "AssignedObject": { - "Interface": { - "name": "GigabitEthernet1/0/1" - } - } - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "dcim.site", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimSiteDataWrapper{ - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - }, - }, - { - objectType: "dcim.manufacturer", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimManufacturerDataWrapper{ - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - { - objectType: "dcim.platform", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimPlatformDataWrapper{ - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicetype", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceTypeDataWrapper{ - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicerole", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceRoleDataWrapper{ - DeviceRole: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - }, - }, - { - objectType: "dcim.device", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceDataWrapper{ - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - { - objectType: "dcim.interface", - objectID: 0, - queryParams: map[string]string{"q": "GigabitEthernet1/0/1", "device__name": "undefined", "device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimInterfaceDataWrapper{ - Interface: nil, - }, - }, - { - objectType: "ipam.ipaddress", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet1/0/1", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.IpamIPAddressDataWrapper{ - IPAddress: &netbox.IpamIPAddress{ - ID: 1, - Address: "192.168.0.1/22", - Status: &netbox.DefaultIPAddressStatus, - AssignedObject: &netbox.IPAddressInterface{ - Interface: &netbox.DcimInterface{ - ID: 1, - Name: "GigabitEthernet0/0/0", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - }, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{ - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "dcim.interface", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.DcimInterface{ - Name: "GigabitEthernet1/0/1", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - }, - }, - }, - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "ipam.ipaddress", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.IpamIPAddress{ - Address: "192.168.0.1/22", - Status: &netbox.DefaultIPAddressStatus, - AssignedObject: &netbox.IPAddressInterface{ - Interface: &netbox.DcimInterface{ - Name: "GigabitEthernet1/0/1", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "[P14] ingest ipam.ipaddress with assigned interface - existing IP address found assigned a different device - create IP address with a new assigned object (interface)", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.ipaddress", - "entity": { - "IpAddress": { - "address": "192.168.0.1/22", - "AssignedObject": { - "Interface": { - "name": "GigabitEthernet1/0/1" - } - } - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "dcim.site", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimSiteDataWrapper{ - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - }, - }, - { - objectType: "dcim.manufacturer", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimManufacturerDataWrapper{ - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - { - objectType: "dcim.platform", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimPlatformDataWrapper{ - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicetype", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceTypeDataWrapper{ - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicerole", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceRoleDataWrapper{ - DeviceRole: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - }, - }, - { - objectType: "dcim.device", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceDataWrapper{ - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - { - objectType: "dcim.interface", - objectID: 0, - queryParams: map[string]string{"q": "GigabitEthernet1/0/1", "device__name": "undefined", "device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimInterfaceDataWrapper{ - Interface: &netbox.DcimInterface{ - ID: 2, - Name: "GigabitEthernet1/0/1", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - }, - { - objectType: "ipam.ipaddress", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet1/0/1", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.IpamIPAddressDataWrapper{ - IPAddress: &netbox.IpamIPAddress{ - ID: 1, - Address: "192.168.0.1/22", - Status: &netbox.DefaultIPAddressStatus, - AssignedObject: &netbox.IPAddressInterface{ - Interface: &netbox.DcimInterface{ - ID: 1, - Name: "GigabitEthernet0/0/0", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - }, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{ - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "ipam.ipaddress", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.IpamIPAddress{ - Address: "192.168.0.1/22", - Status: &netbox.DefaultIPAddressStatus, - AssignedObject: &netbox.IPAddressInterface{ - Interface: &netbox.DcimInterface{ - ID: 2, - Device: &netbox.DcimDevice{ - ID: 1, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "[P14] ingest ipam.ipaddress with address and interface - existing IP address found with same interface assigned - no update needed", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.ipaddress", - "entity": { - "IpAddress": { - "address": "192.168.0.1/22", - "AssignedObject": { - "Interface": { - "name": "GigabitEthernet0/0/0" - } - } - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "dcim.site", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimSiteDataWrapper{ - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - }, - }, - { - objectType: "dcim.manufacturer", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimManufacturerDataWrapper{ - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - { - objectType: "dcim.platform", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimPlatformDataWrapper{ - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicetype", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceTypeDataWrapper{ - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicerole", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceRoleDataWrapper{ - DeviceRole: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - }, - }, - { - objectType: "dcim.device", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceDataWrapper{ - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - { - objectType: "dcim.interface", - objectID: 0, - queryParams: map[string]string{"q": "GigabitEthernet0/0/0", "device__name": "undefined", "device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimInterfaceDataWrapper{ - Interface: &netbox.DcimInterface{ - ID: 1, - Name: "GigabitEthernet0/0/0", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - }, - { - objectType: "ipam.ipaddress", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet0/0/0", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.IpamIPAddressDataWrapper{ - IPAddress: &netbox.IpamIPAddress{ - ID: 1, - Address: "192.168.0.1/22", - Status: &netbox.DefaultIPAddressStatus, - AssignedObject: &netbox.IPAddressInterface{ - Interface: &netbox.DcimInterface{ - ID: 1, - Name: "GigabitEthernet0/0/0", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - }, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{}, - }, - wantErr: false, - }, - { - name: "[P14] ingest ipam.ipaddress with address only - existing IP address found without interface assigned - no update needed", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.ipaddress", - "entity": { - "IpAddress": { - "address": "192.168.0.1/22" - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "ipam.ipaddress", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.1/22"}, - objectChangeID: 0, - object: &netbox.IpamIPAddressDataWrapper{ - IPAddress: &netbox.IpamIPAddress{ - ID: 1, - Address: "192.168.0.1/22", - Status: &netbox.DefaultIPAddressStatus, - }, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{}, - }, - wantErr: false, - }, - { - name: "[P14] ingest ipam.ipaddress with address and new description - existing IP address found - update IP address with new description", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.ipaddress", - "entity": { - "IpAddress": { - "address": "192.168.0.1/22", - "description": "new description", - "AssignedObject": { - "Interface": { - "name": "GigabitEthernet0/0/0" - } - } - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "dcim.site", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimSiteDataWrapper{ - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - }, - }, - { - objectType: "dcim.manufacturer", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimManufacturerDataWrapper{ - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - { - objectType: "dcim.platform", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimPlatformDataWrapper{ - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicetype", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceTypeDataWrapper{ - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - }, - { - objectType: "dcim.devicerole", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceRoleDataWrapper{ - DeviceRole: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - }, - }, - { - objectType: "dcim.device", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceDataWrapper{ - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - { - objectType: "dcim.interface", - objectID: 0, - queryParams: map[string]string{"q": "GigabitEthernet0/0/0", "device__name": "undefined", "device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimInterfaceDataWrapper{ - Interface: &netbox.DcimInterface{ - ID: 1, - Name: "GigabitEthernet0/0/0", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - }, - { - objectType: "ipam.ipaddress", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet0/0/0", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.IpamIPAddressDataWrapper{ - IPAddress: &netbox.IpamIPAddress{ - ID: 1, - Address: "192.168.0.1/22", - Status: &netbox.DefaultIPAddressStatus, - AssignedObject: &netbox.IPAddressInterface{ - Interface: &netbox.DcimInterface{ - ID: 1, - Name: "GigabitEthernet0/0/0", - Type: strPtr(netbox.DefaultInterfaceType), - Device: &netbox.DcimDevice{ - ID: 1, - Name: "undefined", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - DeviceType: &netbox.DcimDeviceType{ - ID: 1, - Model: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - Platform: &netbox.DcimPlatform{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Manufacturer: &netbox.DcimManufacturer{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), - }, - }, - }, - }, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{ - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeUpdate, - ObjectType: "ipam.ipaddress", - ObjectID: intPtr(1), - ObjectVersion: nil, - Data: &netbox.IpamIPAddress{ - ID: 1, - Address: "192.168.0.1/22", - Status: &netbox.DefaultIPAddressStatus, - Description: strPtr("new description"), - AssignedObject: &netbox.IPAddressInterface{ - Interface: &netbox.DcimInterface{ - ID: 1, - Device: &netbox.DcimDevice{ - ID: 1, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "[P14] ingest empty ipam.ipaddress - error", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.ipaddress", - "entity": { - "IPAddress": {} - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{}, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{}, - }, - wantErr: true, - }, - { - name: "[P15] ingest ipam.prefix with prefix only - existing object not found - create prefix and site (placeholder)", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.prefix", - "entity": { - "Prefix": { - "prefix": "192.168.0.0/32" - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "dcim.site", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimSiteDataWrapper{ - Site: nil, - }, - }, - { - objectType: "ipam.prefix", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.0/32"}, - objectChangeID: 0, - object: &netbox.IpamPrefixDataWrapper{ - Prefix: nil, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{ - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "dcim.site", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.DcimSite{ - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - }, - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "ipam.prefix", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.IpamPrefix{ - Prefix: "192.168.0.0/32", - Site: &netbox.DcimSite{ - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - Status: &netbox.DefaultPrefixStatus, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "[P15] ingest ipam.prefix with prefix only - existing object and its related objects found - do nothing", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.prefix", - "entity": { - "Prefix": { - "prefix": "192.168.0.0/32", - "site": { - "name": "undefined" - } - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "dcim.site", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimSiteDataWrapper{ - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - }, - }, - { - objectType: "ipam.prefix", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.0/32"}, - objectChangeID: 0, - object: &netbox.IpamPrefixDataWrapper{ - Prefix: &netbox.IpamPrefix{ - ID: 1, - Prefix: "192.168.0.0/32", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - Status: &netbox.DefaultPrefixStatus, - }, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{}, - }, - wantErr: false, - }, - { - name: "[P15] ingest ipam.prefix with empty site", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.prefix", - "entity": { - "Prefix": { - "prefix": "192.168.0.0/32", - "site": {} - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "dcim.site", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimSiteDataWrapper{ - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - }, - }, - { - objectType: "ipam.prefix", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.0/32"}, - objectChangeID: 0, - object: &netbox.IpamPrefixDataWrapper{ - Prefix: &netbox.IpamPrefix{ - ID: 1, - Prefix: "192.168.0.0/32", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - Status: &netbox.DefaultPrefixStatus, - }, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{}, - }, - wantErr: false, - }, - { - name: "[P15] ingest ipam.prefix with prefix and a tag - existing object found - create tag and update prefix", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "ipam.prefix", - "entity": { - "Prefix": { - "prefix": "192.168.0.0/32", - "tags": [ - { - "name": "tag 100" - } - ] - } - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "dcim.site", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimSiteDataWrapper{ - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - }, - }, - { - objectType: "extras.tag", - objectID: 0, - queryParams: map[string]string{"q": "tag 100"}, - objectChangeID: 0, - object: &netbox.TagDataWrapper{ - Tag: nil, - }, - }, - { - objectType: "ipam.prefix", - objectID: 0, - queryParams: map[string]string{"q": "192.168.0.0/32"}, - objectChangeID: 0, - object: &netbox.IpamPrefixDataWrapper{ - Prefix: &netbox.IpamPrefix{ - ID: 1, - Prefix: "192.168.0.0/32", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - Status: &netbox.DefaultPrefixStatus, - }, - }, - }, - }, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{ - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b6", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "extras.tag", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.Tag{ - Name: "tag 100", - Slug: "tag-100", - }, - }, - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeUpdate, - ObjectType: "ipam.prefix", - ObjectID: intPtr(1), - ObjectVersion: nil, - Data: &netbox.IpamPrefix{ - ID: 1, - Prefix: "192.168.0.0/32", - Site: &netbox.DcimSite{ - ID: 1, - }, - Status: &netbox.DefaultPrefixStatus, - Tags: []*netbox.Tag{ - { - Name: "tag 100", - Slug: "tag-100", - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "[P16] ingest dcim.device with device type and manufacturer - device type and manufacturer objects found - create device with existing device type and manufacturer", + name: "[P14] ingest dcim.device with device type and manufacturer - device type and manufacturer objects found - create device with existing device type and manufacturer", rawIngestEntity: []byte(`{ "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", "data_type": "dcim.device", @@ -5980,7 +4143,7 @@ func TestPrepare(t *testing.T) { wantErr: false, }, { - name: "[P17] ingest dcim.interface with name, mtu, device with site - device exists for platform Arista - create interface with existing device and platform", + name: "[P15] ingest dcim.interface with name, mtu, device with site - device exists for platform Arista - create interface with existing device and platform", rawIngestEntity: []byte(`{ "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", "data_type": "dcim.interface", diff --git a/diode-server/reconciler/changeset/changeset_ipam_test.go b/diode-server/reconciler/changeset/changeset_ipam_test.go new file mode 100644 index 00000000..a83db503 --- /dev/null +++ b/diode-server/reconciler/changeset/changeset_ipam_test.go @@ -0,0 +1,1868 @@ +package changeset_test + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/netboxlabs/diode/diode-server/netbox" + "github.com/netboxlabs/diode/diode-server/netboxdiodeplugin" + "github.com/netboxlabs/diode/diode-server/netboxdiodeplugin/mocks" + "github.com/netboxlabs/diode/diode-server/reconciler/changeset" +) + +func TestIpamPrepare(t *testing.T) { + type mockRetrieveObjectState struct { + objectType string + objectID int + queryParams map[string]string + objectChangeID int + object netbox.ComparableData + } + tests := []struct { + name string + rawIngestEntity []byte + retrieveObjectStates []mockRetrieveObjectState + wantChangeSet changeset.ChangeSet + wantErr bool + }{ + { + name: "[P1] ingest ipam.ipaddress with address and interface - existing object not found - create", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.ipaddress", + "entity": { + "IpAddress": { + "address": "192.168.0.1/22", + "AssignedObject": { + "Interface": { + "name": "GigabitEthernet0/0/0" + } + } + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.manufacturer", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimManufacturerDataWrapper{ + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.platform", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimPlatformDataWrapper{ + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicetype", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceTypeDataWrapper{ + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + { + objectType: "dcim.device", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceDataWrapper{ + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + { + objectType: "dcim.interface", + objectID: 0, + queryParams: map[string]string{"q": "GigabitEthernet0/0/0", "device__name": "undefined", "device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimInterfaceDataWrapper{ + Interface: &netbox.DcimInterface{ + ID: 1, + Name: "GigabitEthernet0/0/0", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + }, + { + objectType: "ipam.ipaddress", + objectID: 0, + queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet0/0/0", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.IpamIPAddressDataWrapper{ + IPAddress: nil, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "ipam.ipaddress", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.IpamIPAddress{ + Address: "192.168.0.1/22", + Status: &netbox.DefaultIPAddressStatus, + AssignedObject: &netbox.IPAddressInterface{ + Interface: &netbox.DcimInterface{ + ID: 1, + Device: &netbox.DcimDevice{ + ID: 1, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P1] ingest ipam.ipaddress with address and a new interface - existing IP address and interface not found - create an interface and IP address", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.ipaddress", + "entity": { + "IpAddress": { + "address": "192.168.0.1/22", + "AssignedObject": { + "Interface": { + "name": "GigabitEthernet0/0/0" + } + } + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.manufacturer", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimManufacturerDataWrapper{ + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.platform", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimPlatformDataWrapper{ + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicetype", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceTypeDataWrapper{ + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + { + objectType: "dcim.device", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceDataWrapper{ + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + { + objectType: "dcim.interface", + objectID: 0, + queryParams: map[string]string{"q": "GigabitEthernet0/0/0", "device__name": "undefined", "device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimInterfaceDataWrapper{ + Interface: nil, + }, + }, + { + objectType: "ipam.ipaddress", + objectID: 0, + queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet0/0/0", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.IpamIPAddressDataWrapper{ + IPAddress: nil, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "dcim.interface", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.DcimInterface{ + Name: "GigabitEthernet0/0/0", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + }, + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "ipam.ipaddress", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.IpamIPAddress{ + Address: "192.168.0.1/22", + Status: &netbox.DefaultIPAddressStatus, + AssignedObject: &netbox.IPAddressInterface{ + Interface: &netbox.DcimInterface{ + Name: "GigabitEthernet0/0/0", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P1] ingest ipam.ipaddress with address and a new interface - IP address found assigned to a different interface - create the interface and the IP address", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.ipaddress", + "entity": { + "IpAddress": { + "address": "192.168.0.1/22", + "AssignedObject": { + "Interface": { + "name": "GigabitEthernet1/0/1" + } + } + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.manufacturer", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimManufacturerDataWrapper{ + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.platform", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimPlatformDataWrapper{ + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicetype", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceTypeDataWrapper{ + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + { + objectType: "dcim.device", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceDataWrapper{ + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + { + objectType: "dcim.interface", + objectID: 0, + queryParams: map[string]string{"q": "GigabitEthernet1/0/1", "device__name": "undefined", "device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimInterfaceDataWrapper{ + Interface: nil, + }, + }, + { + objectType: "ipam.ipaddress", + objectID: 0, + queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet1/0/1", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.IpamIPAddressDataWrapper{ + IPAddress: &netbox.IpamIPAddress{ + ID: 1, + Address: "192.168.0.1/22", + Status: &netbox.DefaultIPAddressStatus, + AssignedObject: &netbox.IPAddressInterface{ + Interface: &netbox.DcimInterface{ + ID: 1, + Name: "GigabitEthernet0/0/0", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "dcim.interface", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.DcimInterface{ + Name: "GigabitEthernet1/0/1", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + }, + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "ipam.ipaddress", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.IpamIPAddress{ + Address: "192.168.0.1/22", + Status: &netbox.DefaultIPAddressStatus, + AssignedObject: &netbox.IPAddressInterface{ + Interface: &netbox.DcimInterface{ + Name: "GigabitEthernet1/0/1", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P1] ingest ipam.ipaddress with assigned interface - existing IP address found assigned a different device - create IP address with a new assigned object (interface)", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.ipaddress", + "entity": { + "IpAddress": { + "address": "192.168.0.1/22", + "AssignedObject": { + "Interface": { + "name": "GigabitEthernet1/0/1" + } + } + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.manufacturer", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimManufacturerDataWrapper{ + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.platform", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimPlatformDataWrapper{ + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicetype", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceTypeDataWrapper{ + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + { + objectType: "dcim.device", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceDataWrapper{ + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + { + objectType: "dcim.interface", + objectID: 0, + queryParams: map[string]string{"q": "GigabitEthernet1/0/1", "device__name": "undefined", "device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimInterfaceDataWrapper{ + Interface: &netbox.DcimInterface{ + ID: 2, + Name: "GigabitEthernet1/0/1", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + }, + { + objectType: "ipam.ipaddress", + objectID: 0, + queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet1/0/1", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.IpamIPAddressDataWrapper{ + IPAddress: &netbox.IpamIPAddress{ + ID: 1, + Address: "192.168.0.1/22", + Status: &netbox.DefaultIPAddressStatus, + AssignedObject: &netbox.IPAddressInterface{ + Interface: &netbox.DcimInterface{ + ID: 1, + Name: "GigabitEthernet0/0/0", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "ipam.ipaddress", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.IpamIPAddress{ + Address: "192.168.0.1/22", + Status: &netbox.DefaultIPAddressStatus, + AssignedObject: &netbox.IPAddressInterface{ + Interface: &netbox.DcimInterface{ + ID: 2, + Device: &netbox.DcimDevice{ + ID: 1, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P1] ingest ipam.ipaddress with address and interface - existing IP address found with same interface assigned - no update needed", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.ipaddress", + "entity": { + "IpAddress": { + "address": "192.168.0.1/22", + "AssignedObject": { + "Interface": { + "name": "GigabitEthernet0/0/0" + } + } + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.manufacturer", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimManufacturerDataWrapper{ + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.platform", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimPlatformDataWrapper{ + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicetype", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceTypeDataWrapper{ + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + { + objectType: "dcim.device", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceDataWrapper{ + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + { + objectType: "dcim.interface", + objectID: 0, + queryParams: map[string]string{"q": "GigabitEthernet0/0/0", "device__name": "undefined", "device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimInterfaceDataWrapper{ + Interface: &netbox.DcimInterface{ + ID: 1, + Name: "GigabitEthernet0/0/0", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + }, + { + objectType: "ipam.ipaddress", + objectID: 0, + queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet0/0/0", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.IpamIPAddressDataWrapper{ + IPAddress: &netbox.IpamIPAddress{ + ID: 1, + Address: "192.168.0.1/22", + Status: &netbox.DefaultIPAddressStatus, + AssignedObject: &netbox.IPAddressInterface{ + Interface: &netbox.DcimInterface{ + ID: 1, + Name: "GigabitEthernet0/0/0", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: false, + }, + { + name: "[P1] ingest ipam.ipaddress with address only - existing IP address found without interface assigned - no update needed", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.ipaddress", + "entity": { + "IpAddress": { + "address": "192.168.0.1/22" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "ipam.ipaddress", + objectID: 0, + queryParams: map[string]string{"q": "192.168.0.1/22"}, + objectChangeID: 0, + object: &netbox.IpamIPAddressDataWrapper{ + IPAddress: &netbox.IpamIPAddress{ + ID: 1, + Address: "192.168.0.1/22", + Status: &netbox.DefaultIPAddressStatus, + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: false, + }, + { + name: "[P1] ingest ipam.ipaddress with address and new description - existing IP address found - update IP address with new description", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.ipaddress", + "entity": { + "IpAddress": { + "address": "192.168.0.1/22", + "description": "new description", + "AssignedObject": { + "Interface": { + "name": "GigabitEthernet0/0/0" + } + } + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.manufacturer", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimManufacturerDataWrapper{ + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.platform", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimPlatformDataWrapper{ + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicetype", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "manufacturer__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceTypeDataWrapper{ + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + { + objectType: "dcim.device", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceDataWrapper{ + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + { + objectType: "dcim.interface", + objectID: 0, + queryParams: map[string]string{"q": "GigabitEthernet0/0/0", "device__name": "undefined", "device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimInterfaceDataWrapper{ + Interface: &netbox.DcimInterface{ + ID: 1, + Name: "GigabitEthernet0/0/0", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + }, + { + objectType: "ipam.ipaddress", + objectID: 0, + queryParams: map[string]string{"q": "192.168.0.1/22", "interface__name": "GigabitEthernet0/0/0", "interface__device__name": "undefined", "interface__device__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.IpamIPAddressDataWrapper{ + IPAddress: &netbox.IpamIPAddress{ + ID: 1, + Address: "192.168.0.1/22", + Status: &netbox.DefaultIPAddressStatus, + AssignedObject: &netbox.IPAddressInterface{ + Interface: &netbox.DcimInterface{ + ID: 1, + Name: "GigabitEthernet0/0/0", + Type: strPtr(netbox.DefaultInterfaceType), + Device: &netbox.DcimDevice{ + ID: 1, + Name: "undefined", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + DeviceType: &netbox.DcimDeviceType{ + ID: 1, + Model: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + Platform: &netbox.DcimPlatform{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Manufacturer: &netbox.DcimManufacturer{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: (*netbox.DcimDeviceStatus)(strPtr(string(netbox.DcimDeviceStatusActive))), + }, + }, + }, + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeUpdate, + ObjectType: "ipam.ipaddress", + ObjectID: intPtr(1), + ObjectVersion: nil, + Data: &netbox.IpamIPAddress{ + ID: 1, + Address: "192.168.0.1/22", + Status: &netbox.DefaultIPAddressStatus, + Description: strPtr("new description"), + AssignedObject: &netbox.IPAddressInterface{ + Interface: &netbox.DcimInterface{ + ID: 1, + Device: &netbox.DcimDevice{ + ID: 1, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P1] ingest empty ipam.ipaddress - error", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.ipaddress", + "entity": { + "IPAddress": {} + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{}, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: true, + }, + { + name: "[P2] ingest ipam.prefix with prefix only - existing object not found - create prefix and site (placeholder)", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.prefix", + "entity": { + "Prefix": { + "prefix": "192.168.0.0/32" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: nil, + }, + }, + { + objectType: "ipam.prefix", + objectID: 0, + queryParams: map[string]string{"q": "192.168.0.0/32"}, + objectChangeID: 0, + object: &netbox.IpamPrefixDataWrapper{ + Prefix: nil, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "dcim.site", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.DcimSite{ + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "ipam.prefix", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.IpamPrefix{ + Prefix: "192.168.0.0/32", + Site: &netbox.DcimSite{ + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + Status: &netbox.DefaultPrefixStatus, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P2] ingest ipam.prefix with prefix only - existing object and its related objects found - do nothing", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.prefix", + "entity": { + "Prefix": { + "prefix": "192.168.0.0/32", + "site": { + "name": "undefined" + } + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "ipam.prefix", + objectID: 0, + queryParams: map[string]string{"q": "192.168.0.0/32"}, + objectChangeID: 0, + object: &netbox.IpamPrefixDataWrapper{ + Prefix: &netbox.IpamPrefix{ + ID: 1, + Prefix: "192.168.0.0/32", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + Status: &netbox.DefaultPrefixStatus, + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: false, + }, + { + name: "[P2] ingest ipam.prefix with empty site", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.prefix", + "entity": { + "Prefix": { + "prefix": "192.168.0.0/32", + "site": {} + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "ipam.prefix", + objectID: 0, + queryParams: map[string]string{"q": "192.168.0.0/32"}, + objectChangeID: 0, + object: &netbox.IpamPrefixDataWrapper{ + Prefix: &netbox.IpamPrefix{ + ID: 1, + Prefix: "192.168.0.0/32", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + Status: &netbox.DefaultPrefixStatus, + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: false, + }, + { + name: "[P2] ingest ipam.prefix with prefix and a tag - existing object found - create tag and update prefix", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "ipam.prefix", + "entity": { + "Prefix": { + "prefix": "192.168.0.0/32", + "tags": [ + { + "name": "tag 100" + } + ] + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "extras.tag", + objectID: 0, + queryParams: map[string]string{"q": "tag 100"}, + objectChangeID: 0, + object: &netbox.TagDataWrapper{ + Tag: nil, + }, + }, + { + objectType: "ipam.prefix", + objectID: 0, + queryParams: map[string]string{"q": "192.168.0.0/32"}, + objectChangeID: 0, + object: &netbox.IpamPrefixDataWrapper{ + Prefix: &netbox.IpamPrefix{ + ID: 1, + Prefix: "192.168.0.0/32", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + Status: &netbox.DefaultPrefixStatus, + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b6", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "extras.tag", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.Tag{ + Name: "tag 100", + Slug: "tag-100", + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeUpdate, + ObjectType: "ipam.prefix", + ObjectID: intPtr(1), + ObjectVersion: nil, + Data: &netbox.IpamPrefix{ + ID: 1, + Prefix: "192.168.0.0/32", + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: &netbox.DefaultPrefixStatus, + Tags: []*netbox.Tag{ + { + Name: "tag 100", + Slug: "tag-100", + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ingestEntity changeset.IngestEntity + err := json.Unmarshal(tt.rawIngestEntity, &ingestEntity) + require.NoError(t, err) + + mockClient := mocks.NewNetBoxAPI(t) + + for _, m := range tt.retrieveObjectStates { + mockClient.EXPECT().RetrieveObjectState(context.Background(), netboxdiodeplugin.RetrieveObjectStateQueryParams{ + ObjectType: m.objectType, + ObjectID: m.objectID, + Params: m.queryParams, + }).Return(&netboxdiodeplugin.ObjectState{ + ObjectID: m.objectID, + ObjectType: m.objectType, + ObjectChangeID: m.objectChangeID, + Object: m.object, + }, nil) + } + + cs, err := changeset.Prepare(ingestEntity, mockClient) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + + require.Equal(t, len(tt.wantChangeSet.ChangeSet), len(cs.ChangeSet)) + + for i := range tt.wantChangeSet.ChangeSet { + assert.Equal(t, tt.wantChangeSet.ChangeSet[i].ChangeType, cs.ChangeSet[i].ChangeType) + assert.Equal(t, tt.wantChangeSet.ChangeSet[i].ObjectType, cs.ChangeSet[i].ObjectType) + assert.Equal(t, tt.wantChangeSet.ChangeSet[i].Data, cs.ChangeSet[i].Data) + } + }) + } +} diff --git a/diode-server/reconciler/changeset/changeset_virt_test.go b/diode-server/reconciler/changeset/changeset_virt_test.go new file mode 100644 index 00000000..b3b5af57 --- /dev/null +++ b/diode-server/reconciler/changeset/changeset_virt_test.go @@ -0,0 +1,1683 @@ +package changeset_test + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/netboxlabs/diode/diode-server/netbox" + "github.com/netboxlabs/diode/diode-server/netboxdiodeplugin" + "github.com/netboxlabs/diode/diode-server/netboxdiodeplugin/mocks" + "github.com/netboxlabs/diode/diode-server/reconciler/changeset" +) + +func TestVirtualizationPrepare(t *testing.T) { + type mockRetrieveObjectState struct { + objectType string + objectID int + queryParams map[string]string + objectChangeID int + object netbox.ComparableData + } + tests := []struct { + name string + rawIngestEntity []byte + retrieveObjectStates []mockRetrieveObjectState + wantChangeSet changeset.ChangeSet + wantErr bool + }{ + { + name: "[P1] ingest virtualization.clustergroup with name only - existing object not found - create", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.clustergroup", + "entity": { + "ClusterGroup": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.clustergroup", + objectID: 0, + queryParams: map[string]string{"q": "Test"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: nil, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.clustergroup", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationClusterGroup{ + Name: "Test", + Slug: "test", + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P1] ingest virtualization.clustergroup with name only - existing object found - do nothing", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.clustergroup", + "entity": { + "ClusterGroup": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.clustergroup", + objectID: 0, + queryParams: map[string]string{"q": "Test"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "Test", + Slug: "test", + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: false, + }, + { + name: "[P1] ingest empty virtualization.clustergroup - error", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.clustergroup", + "entity": { + "ClusterGroup": {} + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{}, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: true, + }, + { + name: "[P2] ingest virtualization.clustertype with name only - existing object not found - create", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.clustertype", + "entity": { + "ClusterType": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.clustertype", + objectID: 0, + queryParams: map[string]string{"q": "Test"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: nil, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.clustertype", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationClusterType{ + Name: "Test", + Slug: "test", + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P2] ingest virtualization.clustertype with name only - existing object found - do nothing", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.clustertype", + "entity": { + "ClusterType": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.clustertype", + objectID: 0, + queryParams: map[string]string{"q": "Test"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "Test", + Slug: "test", + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: false, + }, + { + name: "[P2] ingest empty virtualization.clustertype - error", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.clustertype", + "entity": { + "ClusterType": {} + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{}, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: true, + }, + { + name: "[P3] ingest virtualization.cluster with name only - existing object not found - create", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.cluster", + "entity": { + "Cluster": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.cluster", + objectID: 0, + queryParams: map[string]string{"q": "Test", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterDataWrapper{ + Cluster: nil, + }, + }, + { + objectType: "virtualization.clustergroup", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "virtualization.clustertype", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.cluster", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationCluster{ + Name: "Test", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P3] ingest virtualization.cluster with name only - existing object found - do nothing", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.cluster", + "entity": { + "Cluster": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.cluster", + objectID: 0, + queryParams: map[string]string{"q": "Test", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterDataWrapper{ + Cluster: &netbox.VirtualizationCluster{ + ID: 1, + Name: "Test", + }, + }, + }, + { + objectType: "virtualization.clustergroup", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "virtualization.clustertype", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: false, + }, + { + name: "[P3] ingest empty virtualization.cluster - error", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.cluster", + "entity": { + "Cluster": {} + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{}, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: true, + }, + { + name: "[P4] ingest virtualization.virtualmachine with name only - existing object not found - create", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.virtualmachine", + "entity": { + "VirtualMachine": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.virtualmachine", + objectID: 0, + queryParams: map[string]string{"q": "Test", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: nil, + }, + }, + { + objectType: "virtualization.cluster", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterDataWrapper{ + Cluster: nil, + }, + }, + { + objectType: "virtualization.clustergroup", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "virtualization.clustertype", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.cluster", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationCluster{ + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.virtualmachine", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationVirtualMachine{ + Name: "Test", + Cluster: &netbox.VirtualizationCluster{ + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P4] ingest virtualization.virtualmachine with name only - existing object found - do nothing", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.virtualmachine", + "entity": { + "VirtualMachine": { + "name": "Test", + "cluster": { + "name": "cluster1" + } + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.virtualmachine", + objectID: 0, + queryParams: map[string]string{"q": "Test", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + ID: 1, + Name: "Test", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + Cluster: &netbox.VirtualizationCluster{ + ID: 1, + Name: "cluster1", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + }, + { + objectType: "virtualization.cluster", + objectID: 0, + queryParams: map[string]string{"q": "cluster1", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterDataWrapper{ + Cluster: &netbox.VirtualizationCluster{ + ID: 1, + Name: "cluster1", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "virtualization.clustergroup", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "virtualization.clustertype", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: false, + }, + { + name: "[P4] ingest empty virtualization.virtualmachine - error", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.virtualmachine", + "entity": { + "VirtualMachine": {} + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{}, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: true, + }, + { + name: "[P5] ingest virtualization.vminterface with name only - existing object not found - create", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.vminterface", + "entity": { + "VMInterface": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.vminterface", + objectID: 0, + queryParams: map[string]string{"q": "Test", "virtual_machine__name": "undefined", "virtual_machine__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVMInterfaceDataWrapper{ + VMInterface: nil, + }, + }, + { + objectType: "virtualization.virtualmachine", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: nil, + }, + }, + { + objectType: "virtualization.cluster", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterDataWrapper{ + Cluster: nil, + }, + }, + { + objectType: "virtualization.clustergroup", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "virtualization.clustertype", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.cluster", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationCluster{ + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.virtualmachine", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationVirtualMachine{ + Name: "undefined", + Cluster: &netbox.VirtualizationCluster{ + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.vminterface", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationVMInterface{ + Name: "Test", + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + Name: "undefined", + Cluster: &netbox.VirtualizationCluster{ + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P5] ingest virtualization.vminterface with name only - existing object found - do nothing", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.vminterface", + "entity": { + "VMInterface": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.vminterface", + objectID: 0, + queryParams: map[string]string{"q": "Test", "virtual_machine__name": "undefined", "virtual_machine__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVMInterfaceDataWrapper{ + VMInterface: &netbox.VirtualizationVMInterface{ + ID: 1, + Name: "Test", + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + ID: 1, + Name: "undefined", + Cluster: &netbox.VirtualizationCluster{ + ID: 1, + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + }, + }, + { + objectType: "virtualization.virtualmachine", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + ID: 1, + Name: "undefined", + Cluster: &netbox.VirtualizationCluster{ + ID: 1, + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + }, + }, + { + objectType: "virtualization.cluster", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterDataWrapper{ + Cluster: &netbox.VirtualizationCluster{ + ID: 1, + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + }, + { + objectType: "virtualization.clustergroup", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "virtualization.clustertype", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: false, + }, + { + name: "[P5] ingest empty virtualization.vminterface - error", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.vminterface", + "entity": { + "VMInterface": {} + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{}, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: true, + }, + { + name: "[P6] ingest virtualization.virtualdisk with name only - existing object not found - create", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.virtualdisk", + "entity": { + "VirtualDisk": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.virtualdisk", + objectID: 0, + queryParams: map[string]string{"q": "Test", "virtual_machine__name": "undefined", "virtual_machine__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualDiskDataWrapper{ + VirtualDisk: nil, + }, + }, + { + objectType: "virtualization.virtualmachine", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: nil, + }, + }, + { + objectType: "virtualization.cluster", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterDataWrapper{ + Cluster: nil, + }, + }, + { + objectType: "virtualization.clustergroup", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "virtualization.clustertype", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.cluster", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationCluster{ + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.virtualmachine", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationVirtualMachine{ + Name: "undefined", + Cluster: &netbox.VirtualizationCluster{ + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.virtualdisk", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationVirtualDisk{ + Name: "Test", + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + Name: "undefined", + Cluster: &netbox.VirtualizationCluster{ + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P6] ingest virtualization.virtualdisk with name only and no existing site - existing object not found - create", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.virtualdisk", + "entity": { + "VirtualDisk": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.virtualdisk", + objectID: 0, + queryParams: map[string]string{"q": "Test", "virtual_machine__name": "undefined", "virtual_machine__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualDiskDataWrapper{ + VirtualDisk: nil, + }, + }, + { + objectType: "virtualization.virtualmachine", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: nil, + }, + }, + { + objectType: "virtualization.cluster", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterDataWrapper{ + Cluster: nil, + }, + }, + { + objectType: "virtualization.clustergroup", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "virtualization.clustertype", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: nil, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "dcim.site", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.DcimSite{ + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.cluster", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationCluster{ + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.virtualmachine", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationVirtualMachine{ + Name: "undefined", + Cluster: &netbox.VirtualizationCluster{ + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + }, + Site: &netbox.DcimSite{ + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.virtualdisk", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationVirtualDisk{ + Name: "Test", + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + Name: "undefined", + Cluster: &netbox.VirtualizationCluster{ + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + }, + Site: &netbox.DcimSite{ + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + }, + Site: &netbox.DcimSite{ + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P6] ingest virtualization.virtualdisk with name only - existing object found - do nothing", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.virtualdisk", + "entity": { + "VirtualDisk": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.virtualdisk", + objectID: 0, + queryParams: map[string]string{"q": "Test", "virtual_machine__name": "undefined", "virtual_machine__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualDiskDataWrapper{ + VirtualDisk: &netbox.VirtualizationVirtualDisk{ + ID: 1, + Name: "Test", + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + ID: 1, + Name: "undefined", + Cluster: &netbox.VirtualizationCluster{ + ID: 1, + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + }, + }, + { + objectType: "virtualization.virtualmachine", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + ID: 1, + Name: "undefined", + Cluster: &netbox.VirtualizationCluster{ + ID: 1, + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + }, + { + objectType: "virtualization.cluster", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterDataWrapper{ + Cluster: &netbox.VirtualizationCluster{ + ID: 1, + Name: "undefined", + Group: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Type: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + }, + { + objectType: "virtualization.clustergroup", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterGroupDataWrapper{ + ClusterGroup: &netbox.VirtualizationClusterGroup{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "virtualization.clustertype", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationClusterTypeDataWrapper{ + ClusterType: &netbox.VirtualizationClusterType{ + ID: 1, + Name: "undefined", + Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: false, + }, + { + name: "[P6] ingest empty virtualization.virtualdisk - error", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.virtualdisk", + "entity": { + "VirtualDisk": {} + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{}, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ingestEntity changeset.IngestEntity + err := json.Unmarshal(tt.rawIngestEntity, &ingestEntity) + require.NoError(t, err) + + mockClient := mocks.NewNetBoxAPI(t) + + for _, m := range tt.retrieveObjectStates { + mockClient.EXPECT().RetrieveObjectState(context.Background(), netboxdiodeplugin.RetrieveObjectStateQueryParams{ + ObjectType: m.objectType, + ObjectID: m.objectID, + Params: m.queryParams, + }).Return(&netboxdiodeplugin.ObjectState{ + ObjectID: m.objectID, + ObjectType: m.objectType, + ObjectChangeID: m.objectChangeID, + Object: m.object, + }, nil) + } + + cs, err := changeset.Prepare(ingestEntity, mockClient) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + + require.Equal(t, len(tt.wantChangeSet.ChangeSet), len(cs.ChangeSet)) + + for i := range tt.wantChangeSet.ChangeSet { + assert.Equal(t, tt.wantChangeSet.ChangeSet[i].ChangeType, cs.ChangeSet[i].ChangeType) + assert.Equal(t, tt.wantChangeSet.ChangeSet[i].ObjectType, cs.ChangeSet[i].ObjectType) + assert.Equal(t, tt.wantChangeSet.ChangeSet[i].Data, cs.ChangeSet[i].Data) + } + }) + } +} diff --git a/diode-server/reconciler/ingestion_processor.go b/diode-server/reconciler/ingestion_processor.go index 9a102477..ea432d3b 100644 --- a/diode-server/reconciler/ingestion_processor.go +++ b/diode-server/reconciler/ingestion_processor.go @@ -346,6 +346,18 @@ func extractObjectType(in *diodepb.Entity) (string, error) { return netbox.IpamIPAddressObjectType, nil case *diodepb.Entity_Prefix: return netbox.IpamPrefixObjectType, nil + case *diodepb.Entity_ClusterGroup: + return netbox.VirtualizationClusterGroupObjectType, nil + case *diodepb.Entity_ClusterType: + return netbox.VirtualizationClusterTypeObjectType, nil + case *diodepb.Entity_Cluster: + return netbox.VirtualizationClusterObjectType, nil + case *diodepb.Entity_VirtualMachine: + return netbox.VirtualizationVirtualMachineObjectType, nil + case *diodepb.Entity_Vminterface: + return netbox.VirtualizationVMInterfaceObjectType, nil + case *diodepb.Entity_VirtualDisk: + return netbox.VirtualizationVirtualDiskObjectType, nil default: return "", fmt.Errorf("unknown data type") } From bae91c5cc47a54ff7589ec1f5237d1bc1b535176 Mon Sep 17 00:00:00 2001 From: Michal Fiedorowicz Date: Fri, 30 Aug 2024 17:21:01 +0100 Subject: [PATCH 08/13] feat: reorg proto structure (#158) * feat: reorganize protos keep diode related protobuf definitions (ingester and reconciler) under diode-proto directory and diode.v1 package Signed-off-by: Michal Fiedorowicz * tidy up Signed-off-by: Michal Fiedorowicz * remove redundant recipe Signed-off-by: Michal Fiedorowicz * generate ingester and reconciler protobuf go code into diode-server/gen Signed-off-by: Michal Fiedorowicz * tidy up Signed-off-by: Michal Fiedorowicz * resolve merge conflict Signed-off-by: Michal Fiedorowicz * regenerate proto docs Signed-off-by: Michal Fiedorowicz --------- Signed-off-by: Michal Fiedorowicz --- Makefile | 17 +- diode-proto/buf.gen.go-internal.yaml | 11 - diode-proto/buf.gen.py.yaml | 8 - .../{buf.gen.go.yaml => buf.gen.sdk.go.yaml} | 15 +- diode-proto/buf.gen.sdk.py.yaml | 10 + diode-proto/buf.gen.server.go.yaml | 20 ++ diode-proto/buf.lock | 14 +- diode-proto/buf.yaml | 16 +- .../diode}/v1/reconciler.proto | 2 +- diode-server/Makefile | 2 - .../gen/diode/v1/diodepb/ingester.pb.go | 14 +- .../diode/v1/reconcilerpb/reconciler.pb.go | 336 ++++++++++++++++++ .../v1/reconcilerpb/reconciler.pb.validate.go | 2 +- .../v1/reconcilerpb/reconciler_grpc.pb.go | 8 +- diode-server/go.mod | 10 +- diode-server/go.sum | 12 +- diode-server/ingester/component.go | 2 +- .../netboxdiodeplugin/mocks/netboxapi.go | 2 +- diode-server/proto/buf.gen.yaml | 11 - diode-server/proto/buf.lock | 8 - diode-server/proto/buf.yaml | 12 - diode-server/reconciler/client.go | 2 +- diode-server/reconciler/client_test.go | 2 +- diode-server/reconciler/mocks/client.go | 4 +- diode-server/reconciler/mocks/redisclient.go | 2 +- diode-server/reconciler/server.go | 2 +- diode-server/reconciler/server_test.go | 2 +- .../v1/reconcilerpb/reconciler.pb.go | 332 ----------------- docs/diode-proto.md | 132 ++++++- 29 files changed, 555 insertions(+), 455 deletions(-) delete mode 100644 diode-proto/buf.gen.go-internal.yaml delete mode 100644 diode-proto/buf.gen.py.yaml rename diode-proto/{buf.gen.go.yaml => buf.gen.sdk.go.yaml} (50%) create mode 100644 diode-proto/buf.gen.sdk.py.yaml create mode 100644 diode-proto/buf.gen.server.go.yaml rename {diode-server/proto/reconciler => diode-proto/diode}/v1/reconciler.proto (98%) create mode 100644 diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.go rename diode-server/{reconciler => gen/diode}/v1/reconcilerpb/reconciler.pb.validate.go (99%) rename diode-server/{reconciler => gen/diode}/v1/reconcilerpb/reconciler_grpc.pb.go (95%) delete mode 100644 diode-server/proto/buf.gen.yaml delete mode 100644 diode-server/proto/buf.lock delete mode 100644 diode-server/proto/buf.yaml delete mode 100644 diode-server/reconciler/v1/reconcilerpb/reconciler.pb.go diff --git a/Makefile b/Makefile index 1514d3a9..77d781b5 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,14 @@ -.PHONY: gen-diode-sdk-go gen-diode-sdk-python - +.PHONY: gen-diode-sdk-go gen-diode-sdk-go: - @cd diode-proto/ && buf format -w && buf generate --template buf.gen.go.yaml - -gen-diode-go-internal: - @cd diode-proto/ && buf format -w && buf generate --template buf.gen.go-internal.yaml + @cd diode-proto/ && buf format -w && buf generate --template buf.gen.sdk.go.yaml +.PHONY: gen-diode-sdk-python gen-diode-sdk-python: - @cd diode-proto/ && buf format -w && buf generate --template buf.gen.py.yaml --include-imports + @cd diode-proto/ && buf format -w && buf generate --template buf.gen.sdk.py.yaml --include-imports @find ../diode-sdk-python/netboxlabs/diode/sdk \( -name '*.py' -o -name '*.pyi' \) \ -exec sed -i.bak -e 's/^from diode.v1/from netboxlabs.diode.sdk.diode.v1/' \ - -e 's/^from validate/from netboxlabs.diode.sdk.validate/' {} \; -exec rm -f {}.bak \; \ No newline at end of file + -e 's/^from validate/from netboxlabs.diode.sdk.validate/' {} \; -exec rm -f {}.bak \; + +.PHONY: gen-diode-server-go +gen-diode-server-go: + @cd diode-proto/ && buf format -w && buf generate --template buf.gen.server.go.yaml diff --git a/diode-proto/buf.gen.go-internal.yaml b/diode-proto/buf.gen.go-internal.yaml deleted file mode 100644 index a1c55c22..00000000 --- a/diode-proto/buf.gen.go-internal.yaml +++ /dev/null @@ -1,11 +0,0 @@ -version: v1 -plugins: - - plugin: go - out: ../diode-server/gen/ - opt: module=github.com/netboxlabs/diode-sdk-go - - plugin: buf.build/grpc/go:v1.3.0 - out: ../diode-server/gen/ - opt: module=github.com/netboxlabs/diode-sdk-go - - plugin: buf.build/bufbuild/validate-go:v1.0.4 - out: ../diode-server/gen/ - opt: module=github.com/netboxlabs/diode-sdk-go diff --git a/diode-proto/buf.gen.py.yaml b/diode-proto/buf.gen.py.yaml deleted file mode 100644 index 465d094f..00000000 --- a/diode-proto/buf.gen.py.yaml +++ /dev/null @@ -1,8 +0,0 @@ -version: v1 -plugins: - - plugin: buf.build/protocolbuffers/python:v26.1 - out: ../../diode-sdk-python/netboxlabs/diode/sdk/ - - plugin: buf.build/protocolbuffers/pyi:v26.1 - out: ../../diode-sdk-python/netboxlabs/diode/sdk/ - - plugin: buf.build/grpc/python:v1.62.1 - out: ../../diode-sdk-python/netboxlabs/diode/sdk/ diff --git a/diode-proto/buf.gen.go.yaml b/diode-proto/buf.gen.sdk.go.yaml similarity index 50% rename from diode-proto/buf.gen.go.yaml rename to diode-proto/buf.gen.sdk.go.yaml index 4ac9ec6c..cd08cab5 100644 --- a/diode-proto/buf.gen.go.yaml +++ b/diode-proto/buf.gen.sdk.go.yaml @@ -1,15 +1,16 @@ -version: v1 +version: v2 +inputs: + - proto_file: diode/v1/ingester.proto plugins: - - plugin: go + - local: protoc-gen-go out: ../../diode-sdk-go/ opt: module=github.com/netboxlabs/diode-sdk-go - - plugin: buf.build/grpc/go:v1.3.0 + - remote: buf.build/grpc/go:v1.3.0 out: ../../diode-sdk-go/ opt: module=github.com/netboxlabs/diode-sdk-go - - plugin: buf.build/bufbuild/validate-go:v1.0.4 + - remote: buf.build/bufbuild/validate-go:v1.0.4 out: ../../diode-sdk-go/ opt: module=github.com/netboxlabs/diode-sdk-go - - plugin: buf.build/community/pseudomuto-doc:v1.5.1 + - remote: buf.build/community/pseudomuto-doc:v1.5.1 out: ../docs/ - opt: - - markdown,diode-proto.md + opt: markdown,diode-proto.md diff --git a/diode-proto/buf.gen.sdk.py.yaml b/diode-proto/buf.gen.sdk.py.yaml new file mode 100644 index 00000000..345b1819 --- /dev/null +++ b/diode-proto/buf.gen.sdk.py.yaml @@ -0,0 +1,10 @@ +version: v2 +inputs: + - proto_file: diode/v1/ingester.proto +plugins: + - remote: buf.build/protocolbuffers/python:v26.1 + out: ../../diode-sdk-python/netboxlabs/diode/sdk/ + - remote: buf.build/protocolbuffers/pyi:v26.1 + out: ../../diode-sdk-python/netboxlabs/diode/sdk/ + - remote: buf.build/grpc/python:v1.62.1 + out: ../../diode-sdk-python/netboxlabs/diode/sdk/ diff --git a/diode-proto/buf.gen.server.go.yaml b/diode-proto/buf.gen.server.go.yaml new file mode 100644 index 00000000..25c9a06f --- /dev/null +++ b/diode-proto/buf.gen.server.go.yaml @@ -0,0 +1,20 @@ +version: v2 +managed: + enabled: true + override: + - file_option: go_package + path: diode/v1/ingester.proto + value: github.com/netboxlabs/diode/diode-server/gen/diode/v1/diodepb + - file_option: go_package + path: diode/v1/reconciler.proto + value: github.com/netboxlabs/diode/diode-server/gen/diode/v1/reconcilerpb +plugins: + - local: protoc-gen-go + out: ../ + opt: module=github.com/netboxlabs/diode + - remote: buf.build/grpc/go:v1.3.0 + out: ../ + opt: module=github.com/netboxlabs/diode + - remote: buf.build/bufbuild/validate-go:v1.0.4 + out: ../ + opt: module=github.com/netboxlabs/diode diff --git a/diode-proto/buf.lock b/diode-proto/buf.lock index 7e30502a..d7adcd5e 100644 --- a/diode-proto/buf.lock +++ b/diode-proto/buf.lock @@ -1,13 +1,9 @@ # Generated by buf. DO NOT EDIT. -version: v1 +version: v2 deps: - - remote: buf.build - owner: envoyproxy - repository: protoc-gen-validate + - name: buf.build/envoyproxy/protoc-gen-validate commit: 71881f09a0c5420a9545a07987a86728 - digest: shake256:d320bbf06653b1b2b45a1f95bfa82bf7b998221a777a042708c50d6f86a30d1a85b50c5704c597142d9b308280efe1295d39d76d1abea5f7046d3df4c8cc3cef - - remote: buf.build - owner: googleapis - repository: googleapis + digest: b5:e8a034a81f1d6218f712879269bedac768c9e39452d7a92f83d70923fcd6aa9eb02c81ff6ff337cd0dd9b9fe719d6c4459e655f662ae7112aaa1c2110992afd0 + - name: buf.build/googleapis/googleapis commit: 7a6bc1e3207144b38e9066861e1de0ff - digest: shake256:d646836485c34192401253703c4e7ce899c826fceec060bf4b2a62c4749bd9976dc960833e134a1f814725e1ffd60b1bb3cf0335a7e99ef0e8cec34b070ffb66 + digest: b5:6d05bde5ed4cd22531d7ca6467feb828d2dc45cc9de12ce3345fbddd64ddb1bf0db756558c32ca49e6bc7de4426ada8960d5590e8446854b81f5f36f0916dc48 diff --git a/diode-proto/buf.yaml b/diode-proto/buf.yaml index 2056cfad..91ce1546 100644 --- a/diode-proto/buf.yaml +++ b/diode-proto/buf.yaml @@ -1,13 +1,19 @@ -version: v1 +version: v2 deps: - - buf.build/googleapis/googleapis - buf.build/envoyproxy/protoc-gen-validate -breaking: - use: - - FILE + - buf.build/googleapis/googleapis lint: use: - DEFAULT except: + - FIELD_NOT_REQUIRED + - PACKAGE_NO_IMPORT_CYCLE - RPC_REQUEST_STANDARD_NAME - RPC_RESPONSE_STANDARD_NAME + disallow_comment_ignores: true +breaking: + use: + - FILE + except: + - EXTENSION_NO_DELETE + - FIELD_SAME_DEFAULT diff --git a/diode-server/proto/reconciler/v1/reconciler.proto b/diode-proto/diode/v1/reconciler.proto similarity index 98% rename from diode-server/proto/reconciler/v1/reconciler.proto rename to diode-proto/diode/v1/reconciler.proto index e67d1f10..3dc624e6 100644 --- a/diode-server/proto/reconciler/v1/reconciler.proto +++ b/diode-proto/diode/v1/reconciler.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package reconciler.v1; +package diode.v1; import "validate/validate.proto"; diff --git a/diode-server/Makefile b/diode-server/Makefile index 35f31fee..70e971fc 100644 --- a/diode-server/Makefile +++ b/diode-server/Makefile @@ -88,5 +88,3 @@ docker-compose-netbox-down: clean: @rm -rf $(BUILD_DIR)/* -reconciler-proto-gen: - @cd proto/ && buf format -w && buf generate --template buf.gen.yaml diff --git a/diode-server/gen/diode/v1/diodepb/ingester.pb.go b/diode-server/gen/diode/v1/diodepb/ingester.pb.go index a20ddd22..7bff5d5e 100644 --- a/diode-server/gen/diode/v1/diodepb/ingester.pb.go +++ b/diode-server/gen/diode/v1/diodepb/ingester.pb.go @@ -2618,11 +2618,17 @@ var file_diode_v1_ingester_proto_rawDesc = []byte{ 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x22, 0x00, 0x42, 0x9d, 0x01, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, + 0x2e, 0x76, 0x31, 0x42, 0x0d, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x65, 0x74, 0x62, 0x6f, 0x78, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x64, 0x69, 0x6f, 0x64, - 0x65, 0x2d, 0x73, 0x64, 0x6b, 0x2d, 0x67, 0x6f, 0x2f, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2f, 0x76, - 0x31, 0x2f, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x65, 0x2f, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, + 0x65, 0x6e, 0x2f, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x69, 0x6f, 0x64, + 0x65, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x44, 0x69, 0x6f, 0x64, + 0x65, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x44, 0x69, 0x6f, 0x64, 0x65, 0x5c, 0x56, 0x31, 0xe2, + 0x02, 0x14, 0x44, 0x69, 0x6f, 0x64, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x44, 0x69, 0x6f, 0x64, 0x65, 0x3a, 0x3a, + 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.go b/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.go new file mode 100644 index 00000000..c6fe7d29 --- /dev/null +++ b/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.go @@ -0,0 +1,336 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc (unknown) +// source: diode/v1/reconciler.proto + +package reconcilerpb + +import ( + _ "github.com/envoyproxy/protoc-gen-validate/validate" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// An ingestion data source +type IngestionDataSource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + ApiKey string `protobuf:"bytes,2,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` +} + +func (x *IngestionDataSource) Reset() { + *x = IngestionDataSource{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_reconciler_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IngestionDataSource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IngestionDataSource) ProtoMessage() {} + +func (x *IngestionDataSource) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_reconciler_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IngestionDataSource.ProtoReflect.Descriptor instead. +func (*IngestionDataSource) Descriptor() ([]byte, []int) { + return file_diode_v1_reconciler_proto_rawDescGZIP(), []int{0} +} + +func (x *IngestionDataSource) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *IngestionDataSource) GetApiKey() string { + if x != nil { + return x.ApiKey + } + return "" +} + +// The request to retrieve ingestion data sources +type RetrieveIngestionDataSourcesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + SdkName string `protobuf:"bytes,2,opt,name=sdk_name,json=sdkName,proto3" json:"sdk_name,omitempty"` + SdkVersion string `protobuf:"bytes,3,opt,name=sdk_version,json=sdkVersion,proto3" json:"sdk_version,omitempty"` +} + +func (x *RetrieveIngestionDataSourcesRequest) Reset() { + *x = RetrieveIngestionDataSourcesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_reconciler_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RetrieveIngestionDataSourcesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveIngestionDataSourcesRequest) ProtoMessage() {} + +func (x *RetrieveIngestionDataSourcesRequest) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_reconciler_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetrieveIngestionDataSourcesRequest.ProtoReflect.Descriptor instead. +func (*RetrieveIngestionDataSourcesRequest) Descriptor() ([]byte, []int) { + return file_diode_v1_reconciler_proto_rawDescGZIP(), []int{1} +} + +func (x *RetrieveIngestionDataSourcesRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RetrieveIngestionDataSourcesRequest) GetSdkName() string { + if x != nil { + return x.SdkName + } + return "" +} + +func (x *RetrieveIngestionDataSourcesRequest) GetSdkVersion() string { + if x != nil { + return x.SdkVersion + } + return "" +} + +// The response from the retrieve ingestion data sources request +type RetrieveIngestionDataSourcesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IngestionDataSources []*IngestionDataSource `protobuf:"bytes,1,rep,name=ingestion_data_sources,json=ingestionDataSources,proto3" json:"ingestion_data_sources,omitempty"` +} + +func (x *RetrieveIngestionDataSourcesResponse) Reset() { + *x = RetrieveIngestionDataSourcesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_reconciler_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RetrieveIngestionDataSourcesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveIngestionDataSourcesResponse) ProtoMessage() {} + +func (x *RetrieveIngestionDataSourcesResponse) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_reconciler_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetrieveIngestionDataSourcesResponse.ProtoReflect.Descriptor instead. +func (*RetrieveIngestionDataSourcesResponse) Descriptor() ([]byte, []int) { + return file_diode_v1_reconciler_proto_rawDescGZIP(), []int{2} +} + +func (x *RetrieveIngestionDataSourcesResponse) GetIngestionDataSources() []*IngestionDataSource { + if x != nil { + return x.IngestionDataSources + } + return nil +} + +var File_diode_v1_reconciler_proto protoreflect.FileDescriptor + +var file_diode_v1_reconciler_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x6e, + 0x63, 0x69, 0x6c, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x64, 0x69, 0x6f, + 0x64, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x59, + 0x0a, 0x13, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x28, 0x18, + 0x28, 0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x22, 0xab, 0x01, 0x0a, 0x23, 0x52, 0x65, + 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, + 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x0a, 0xfa, 0x42, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x25, 0x0a, 0x08, 0x73, 0x64, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, + 0x07, 0x73, 0x64, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x73, 0x64, 0x6b, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, 0xfa, + 0x42, 0x19, 0x72, 0x17, 0x32, 0x15, 0x5e, 0x28, 0x5c, 0x64, 0x29, 0x2b, 0x5c, 0x2e, 0x28, 0x5c, + 0x64, 0x29, 0x2b, 0x5c, 0x2e, 0x28, 0x5c, 0x64, 0x29, 0x2b, 0x24, 0x52, 0x0a, 0x73, 0x64, 0x6b, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x7b, 0x0a, 0x24, 0x52, 0x65, 0x74, 0x72, 0x69, + 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, + 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x53, 0x0a, 0x16, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, + 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, + 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x14, + 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x32, 0x94, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, + 0x6c, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7f, 0x0a, 0x1c, 0x52, 0x65, + 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, + 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2d, 0x2e, 0x64, 0x69, 0x6f, + 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, + 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x64, 0x69, 0x6f, 0x64, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, + 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xa4, 0x01, 0x0a, 0x0c, + 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0f, 0x52, 0x65, + 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, + 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x65, 0x74, 0x62, + 0x6f, 0x78, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2f, 0x64, 0x69, 0x6f, + 0x64, 0x65, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x64, 0x69, + 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, + 0x72, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x44, 0x69, 0x6f, 0x64, + 0x65, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x44, 0x69, 0x6f, 0x64, 0x65, 0x5c, 0x56, 0x31, 0xe2, + 0x02, 0x14, 0x44, 0x69, 0x6f, 0x64, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x44, 0x69, 0x6f, 0x64, 0x65, 0x3a, 0x3a, + 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_diode_v1_reconciler_proto_rawDescOnce sync.Once + file_diode_v1_reconciler_proto_rawDescData = file_diode_v1_reconciler_proto_rawDesc +) + +func file_diode_v1_reconciler_proto_rawDescGZIP() []byte { + file_diode_v1_reconciler_proto_rawDescOnce.Do(func() { + file_diode_v1_reconciler_proto_rawDescData = protoimpl.X.CompressGZIP(file_diode_v1_reconciler_proto_rawDescData) + }) + return file_diode_v1_reconciler_proto_rawDescData +} + +var file_diode_v1_reconciler_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_diode_v1_reconciler_proto_goTypes = []interface{}{ + (*IngestionDataSource)(nil), // 0: diode.v1.IngestionDataSource + (*RetrieveIngestionDataSourcesRequest)(nil), // 1: diode.v1.RetrieveIngestionDataSourcesRequest + (*RetrieveIngestionDataSourcesResponse)(nil), // 2: diode.v1.RetrieveIngestionDataSourcesResponse +} +var file_diode_v1_reconciler_proto_depIdxs = []int32{ + 0, // 0: diode.v1.RetrieveIngestionDataSourcesResponse.ingestion_data_sources:type_name -> diode.v1.IngestionDataSource + 1, // 1: diode.v1.ReconcilerService.RetrieveIngestionDataSources:input_type -> diode.v1.RetrieveIngestionDataSourcesRequest + 2, // 2: diode.v1.ReconcilerService.RetrieveIngestionDataSources:output_type -> diode.v1.RetrieveIngestionDataSourcesResponse + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_diode_v1_reconciler_proto_init() } +func file_diode_v1_reconciler_proto_init() { + if File_diode_v1_reconciler_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_diode_v1_reconciler_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IngestionDataSource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_reconciler_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RetrieveIngestionDataSourcesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_reconciler_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RetrieveIngestionDataSourcesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_diode_v1_reconciler_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_diode_v1_reconciler_proto_goTypes, + DependencyIndexes: file_diode_v1_reconciler_proto_depIdxs, + MessageInfos: file_diode_v1_reconciler_proto_msgTypes, + }.Build() + File_diode_v1_reconciler_proto = out.File + file_diode_v1_reconciler_proto_rawDesc = nil + file_diode_v1_reconciler_proto_goTypes = nil + file_diode_v1_reconciler_proto_depIdxs = nil +} diff --git a/diode-server/reconciler/v1/reconcilerpb/reconciler.pb.validate.go b/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.validate.go similarity index 99% rename from diode-server/reconciler/v1/reconcilerpb/reconciler.pb.validate.go rename to diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.validate.go index f1d1add5..1c2570cf 100644 --- a/diode-server/reconciler/v1/reconcilerpb/reconciler.pb.validate.go +++ b/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.validate.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-validate. DO NOT EDIT. -// source: reconciler/v1/reconciler.proto +// source: diode/v1/reconciler.proto package reconcilerpb diff --git a/diode-server/reconciler/v1/reconcilerpb/reconciler_grpc.pb.go b/diode-server/gen/diode/v1/reconcilerpb/reconciler_grpc.pb.go similarity index 95% rename from diode-server/reconciler/v1/reconcilerpb/reconciler_grpc.pb.go rename to diode-server/gen/diode/v1/reconcilerpb/reconciler_grpc.pb.go index aa50e76a..b3693bbf 100644 --- a/diode-server/reconciler/v1/reconcilerpb/reconciler_grpc.pb.go +++ b/diode-server/gen/diode/v1/reconcilerpb/reconciler_grpc.pb.go @@ -2,7 +2,7 @@ // versions: // - protoc-gen-go-grpc v1.3.0 // - protoc (unknown) -// source: reconciler/v1/reconciler.proto +// source: diode/v1/reconciler.proto package reconcilerpb @@ -19,7 +19,7 @@ import ( const _ = grpc.SupportPackageIsVersion7 const ( - ReconcilerService_RetrieveIngestionDataSources_FullMethodName = "/reconciler.v1.ReconcilerService/RetrieveIngestionDataSources" + ReconcilerService_RetrieveIngestionDataSources_FullMethodName = "/diode.v1.ReconcilerService/RetrieveIngestionDataSources" ) // ReconcilerServiceClient is the client API for ReconcilerService service. @@ -98,7 +98,7 @@ func _ReconcilerService_RetrieveIngestionDataSources_Handler(srv interface{}, ct // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var ReconcilerService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "reconciler.v1.ReconcilerService", + ServiceName: "diode.v1.ReconcilerService", HandlerType: (*ReconcilerServiceServer)(nil), Methods: []grpc.MethodDesc{ { @@ -107,5 +107,5 @@ var ReconcilerService_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "reconciler/v1/reconciler.proto", + Metadata: "diode/v1/reconciler.proto", } diff --git a/diode-server/go.mod b/diode-server/go.mod index 4494917d..e780ed27 100644 --- a/diode-server/go.mod +++ b/diode-server/go.mod @@ -3,6 +3,7 @@ module github.com/netboxlabs/diode/diode-server go 1.22 require ( + github.com/alicebob/miniredis/v2 v2.33.0 github.com/envoyproxy/protoc-gen-validate v1.0.4 github.com/getsentry/sentry-go v0.27.0 github.com/google/uuid v1.6.0 @@ -17,21 +18,20 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 - github.com/alicebob/miniredis/v2 v2.33.0 ) require ( github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect - github.com/yuin/gopher-lua v1.1.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gosimple/unidecode v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + github.com/yuin/gopher-lua v1.1.1 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/diode-server/go.sum b/diode-server/go.sum index 79907dc1..ca987945 100644 --- a/diode-server/go.sum +++ b/diode-server/go.sum @@ -54,12 +54,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= diff --git a/diode-server/ingester/component.go b/diode-server/ingester/component.go index f3bdc360..adccee7e 100644 --- a/diode-server/ingester/component.go +++ b/diode-server/ingester/component.go @@ -17,8 +17,8 @@ import ( "google.golang.org/protobuf/proto" "github.com/netboxlabs/diode/diode-server/gen/diode/v1/diodepb" + "github.com/netboxlabs/diode/diode-server/gen/diode/v1/reconcilerpb" "github.com/netboxlabs/diode/diode-server/reconciler" - "github.com/netboxlabs/diode/diode-server/reconciler/v1/reconcilerpb" "github.com/netboxlabs/diode/diode-server/sentry" ) diff --git a/diode-server/netboxdiodeplugin/mocks/netboxapi.go b/diode-server/netboxdiodeplugin/mocks/netboxapi.go index a3b4c0ca..c0eeedef 100644 --- a/diode-server/netboxdiodeplugin/mocks/netboxapi.go +++ b/diode-server/netboxdiodeplugin/mocks/netboxapi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. +// Code generated by mockery v2.42.1. DO NOT EDIT. package mocks diff --git a/diode-server/proto/buf.gen.yaml b/diode-server/proto/buf.gen.yaml deleted file mode 100644 index e59915f0..00000000 --- a/diode-server/proto/buf.gen.yaml +++ /dev/null @@ -1,11 +0,0 @@ -version: v1 -plugins: - - plugin: go - out: ../ - opt: module=github.com/netboxlabs/diode/diode-server - - plugin: buf.build/grpc/go:v1.3.0 - out: ../ - opt: module=github.com/netboxlabs/diode/diode-server - - plugin: buf.build/bufbuild/validate-go:v1.0.4 - out: ../ - opt: module=github.com/netboxlabs/diode/diode-server diff --git a/diode-server/proto/buf.lock b/diode-server/proto/buf.lock deleted file mode 100644 index fafef03e..00000000 --- a/diode-server/proto/buf.lock +++ /dev/null @@ -1,8 +0,0 @@ -# Generated by buf. DO NOT EDIT. -version: v1 -deps: - - remote: buf.build - owner: envoyproxy - repository: protoc-gen-validate - commit: daf171c6cdb54629b5f51e345a79e4dd - digest: shake256:4ae167d7eed10da5f83a3f5df8c670d249170f11b1f2fd19afda06be2cff4d47dcc95e9e4a15151ecc8ce2d3d3614caf9a04d3ad82fb768a3870dedfa9455f36 diff --git a/diode-server/proto/buf.yaml b/diode-server/proto/buf.yaml deleted file mode 100644 index b2fbd76e..00000000 --- a/diode-server/proto/buf.yaml +++ /dev/null @@ -1,12 +0,0 @@ -version: v1 -deps: - - buf.build/envoyproxy/protoc-gen-validate -breaking: - use: - - FILE -lint: - use: - - DEFAULT - except: - - RPC_REQUEST_STANDARD_NAME - - RPC_RESPONSE_STANDARD_NAME diff --git a/diode-server/reconciler/client.go b/diode-server/reconciler/client.go index 0032fc90..4d6193d9 100644 --- a/diode-server/reconciler/client.go +++ b/diode-server/reconciler/client.go @@ -8,7 +8,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - pb "github.com/netboxlabs/diode/diode-server/reconciler/v1/reconcilerpb" + pb "github.com/netboxlabs/diode/diode-server/gen/diode/v1/reconcilerpb" ) const ( diff --git a/diode-server/reconciler/client_test.go b/diode-server/reconciler/client_test.go index b733ecdd..e0b5ed47 100644 --- a/diode-server/reconciler/client_test.go +++ b/diode-server/reconciler/client_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/require" + pb "github.com/netboxlabs/diode/diode-server/gen/diode/v1/reconcilerpb" "github.com/netboxlabs/diode/diode-server/reconciler" - pb "github.com/netboxlabs/diode/diode-server/reconciler/v1/reconcilerpb" ) func TestNewClient(t *testing.T) { diff --git a/diode-server/reconciler/mocks/client.go b/diode-server/reconciler/mocks/client.go index 0a25e76b..a04d287e 100644 --- a/diode-server/reconciler/mocks/client.go +++ b/diode-server/reconciler/mocks/client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. +// Code generated by mockery v2.42.1. DO NOT EDIT. package mocks @@ -9,7 +9,7 @@ import ( mock "github.com/stretchr/testify/mock" - reconcilerpb "github.com/netboxlabs/diode/diode-server/reconciler/v1/reconcilerpb" + reconcilerpb "github.com/netboxlabs/diode/diode-server/gen/diode/v1/reconcilerpb" ) // Client is an autogenerated mock type for the Client type diff --git a/diode-server/reconciler/mocks/redisclient.go b/diode-server/reconciler/mocks/redisclient.go index 76495fcc..63d622c5 100644 --- a/diode-server/reconciler/mocks/redisclient.go +++ b/diode-server/reconciler/mocks/redisclient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. +// Code generated by mockery v2.42.1. DO NOT EDIT. package mocks diff --git a/diode-server/reconciler/server.go b/diode-server/reconciler/server.go index 3f183c21..e97314bc 100644 --- a/diode-server/reconciler/server.go +++ b/diode-server/reconciler/server.go @@ -11,7 +11,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/reflection" - "github.com/netboxlabs/diode/diode-server/reconciler/v1/reconcilerpb" + "github.com/netboxlabs/diode/diode-server/gen/diode/v1/reconcilerpb" ) // Server is a reconciler Server diff --git a/diode-server/reconciler/server_test.go b/diode-server/reconciler/server_test.go index 74f5ba6d..81d9a010 100644 --- a/diode-server/reconciler/server_test.go +++ b/diode-server/reconciler/server_test.go @@ -14,8 +14,8 @@ import ( "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/test/bufconn" + pb "github.com/netboxlabs/diode/diode-server/gen/diode/v1/reconcilerpb" "github.com/netboxlabs/diode/diode-server/reconciler" - pb "github.com/netboxlabs/diode/diode-server/reconciler/v1/reconcilerpb" ) func startTestServer(ctx context.Context, t *testing.T, redisAddr string) (*reconciler.Server, *grpc.ClientConn) { diff --git a/diode-server/reconciler/v1/reconcilerpb/reconciler.pb.go b/diode-server/reconciler/v1/reconcilerpb/reconciler.pb.go deleted file mode 100644 index cfd06338..00000000 --- a/diode-server/reconciler/v1/reconcilerpb/reconciler.pb.go +++ /dev/null @@ -1,332 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.32.0 -// protoc (unknown) -// source: reconciler/v1/reconciler.proto - -package reconcilerpb - -import ( - _ "github.com/envoyproxy/protoc-gen-validate/validate" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// An ingestion data source -type IngestionDataSource struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - ApiKey string `protobuf:"bytes,2,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` -} - -func (x *IngestionDataSource) Reset() { - *x = IngestionDataSource{} - if protoimpl.UnsafeEnabled { - mi := &file_reconciler_v1_reconciler_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *IngestionDataSource) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*IngestionDataSource) ProtoMessage() {} - -func (x *IngestionDataSource) ProtoReflect() protoreflect.Message { - mi := &file_reconciler_v1_reconciler_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use IngestionDataSource.ProtoReflect.Descriptor instead. -func (*IngestionDataSource) Descriptor() ([]byte, []int) { - return file_reconciler_v1_reconciler_proto_rawDescGZIP(), []int{0} -} - -func (x *IngestionDataSource) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *IngestionDataSource) GetApiKey() string { - if x != nil { - return x.ApiKey - } - return "" -} - -// The request to retrieve ingestion data sources -type RetrieveIngestionDataSourcesRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - SdkName string `protobuf:"bytes,2,opt,name=sdk_name,json=sdkName,proto3" json:"sdk_name,omitempty"` - SdkVersion string `protobuf:"bytes,3,opt,name=sdk_version,json=sdkVersion,proto3" json:"sdk_version,omitempty"` -} - -func (x *RetrieveIngestionDataSourcesRequest) Reset() { - *x = RetrieveIngestionDataSourcesRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_reconciler_v1_reconciler_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RetrieveIngestionDataSourcesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RetrieveIngestionDataSourcesRequest) ProtoMessage() {} - -func (x *RetrieveIngestionDataSourcesRequest) ProtoReflect() protoreflect.Message { - mi := &file_reconciler_v1_reconciler_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RetrieveIngestionDataSourcesRequest.ProtoReflect.Descriptor instead. -func (*RetrieveIngestionDataSourcesRequest) Descriptor() ([]byte, []int) { - return file_reconciler_v1_reconciler_proto_rawDescGZIP(), []int{1} -} - -func (x *RetrieveIngestionDataSourcesRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *RetrieveIngestionDataSourcesRequest) GetSdkName() string { - if x != nil { - return x.SdkName - } - return "" -} - -func (x *RetrieveIngestionDataSourcesRequest) GetSdkVersion() string { - if x != nil { - return x.SdkVersion - } - return "" -} - -// The response from the retrieve ingestion data sources request -type RetrieveIngestionDataSourcesResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - IngestionDataSources []*IngestionDataSource `protobuf:"bytes,1,rep,name=ingestion_data_sources,json=ingestionDataSources,proto3" json:"ingestion_data_sources,omitempty"` -} - -func (x *RetrieveIngestionDataSourcesResponse) Reset() { - *x = RetrieveIngestionDataSourcesResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_reconciler_v1_reconciler_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RetrieveIngestionDataSourcesResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RetrieveIngestionDataSourcesResponse) ProtoMessage() {} - -func (x *RetrieveIngestionDataSourcesResponse) ProtoReflect() protoreflect.Message { - mi := &file_reconciler_v1_reconciler_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RetrieveIngestionDataSourcesResponse.ProtoReflect.Descriptor instead. -func (*RetrieveIngestionDataSourcesResponse) Descriptor() ([]byte, []int) { - return file_reconciler_v1_reconciler_proto_rawDescGZIP(), []int{2} -} - -func (x *RetrieveIngestionDataSourcesResponse) GetIngestionDataSources() []*IngestionDataSource { - if x != nil { - return x.IngestionDataSources - } - return nil -} - -var File_reconciler_v1_reconciler_proto protoreflect.FileDescriptor - -var file_reconciler_v1_reconciler_proto_rawDesc = []byte{ - 0x0a, 0x1e, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, - 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x0d, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x1a, - 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x59, 0x0a, 0x13, 0x49, 0x6e, 0x67, 0x65, - 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, - 0x1e, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, - 0x42, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x22, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x28, 0x18, 0x28, 0x52, 0x06, 0x61, 0x70, 0x69, - 0x4b, 0x65, 0x79, 0x22, 0xab, 0x01, 0x0a, 0x23, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, - 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x72, 0x05, - 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x08, 0x73, - 0x64, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, - 0x42, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x07, 0x73, 0x64, 0x6b, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x73, 0x64, 0x6b, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, 0xfa, 0x42, 0x19, 0x72, 0x17, 0x32, 0x15, - 0x5e, 0x28, 0x5c, 0x64, 0x29, 0x2b, 0x5c, 0x2e, 0x28, 0x5c, 0x64, 0x29, 0x2b, 0x5c, 0x2e, 0x28, - 0x5c, 0x64, 0x29, 0x2b, 0x24, 0x52, 0x0a, 0x73, 0x64, 0x6b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x22, 0x80, 0x01, 0x0a, 0x24, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, - 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x16, 0x69, 0x6e, - 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x65, 0x63, - 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, - 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x14, - 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x32, 0x9f, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, - 0x6c, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x1c, 0x52, - 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, - 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x32, 0x2e, 0x72, 0x65, - 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, - 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, - 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x33, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, - 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x45, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x65, 0x74, 0x62, 0x6f, 0x78, 0x6c, 0x61, 0x62, 0x73, 0x2f, - 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2f, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2d, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x2f, 0x76, 0x31, - 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x70, 0x62, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_reconciler_v1_reconciler_proto_rawDescOnce sync.Once - file_reconciler_v1_reconciler_proto_rawDescData = file_reconciler_v1_reconciler_proto_rawDesc -) - -func file_reconciler_v1_reconciler_proto_rawDescGZIP() []byte { - file_reconciler_v1_reconciler_proto_rawDescOnce.Do(func() { - file_reconciler_v1_reconciler_proto_rawDescData = protoimpl.X.CompressGZIP(file_reconciler_v1_reconciler_proto_rawDescData) - }) - return file_reconciler_v1_reconciler_proto_rawDescData -} - -var file_reconciler_v1_reconciler_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_reconciler_v1_reconciler_proto_goTypes = []interface{}{ - (*IngestionDataSource)(nil), // 0: reconciler.v1.IngestionDataSource - (*RetrieveIngestionDataSourcesRequest)(nil), // 1: reconciler.v1.RetrieveIngestionDataSourcesRequest - (*RetrieveIngestionDataSourcesResponse)(nil), // 2: reconciler.v1.RetrieveIngestionDataSourcesResponse -} -var file_reconciler_v1_reconciler_proto_depIdxs = []int32{ - 0, // 0: reconciler.v1.RetrieveIngestionDataSourcesResponse.ingestion_data_sources:type_name -> reconciler.v1.IngestionDataSource - 1, // 1: reconciler.v1.ReconcilerService.RetrieveIngestionDataSources:input_type -> reconciler.v1.RetrieveIngestionDataSourcesRequest - 2, // 2: reconciler.v1.ReconcilerService.RetrieveIngestionDataSources:output_type -> reconciler.v1.RetrieveIngestionDataSourcesResponse - 2, // [2:3] is the sub-list for method output_type - 1, // [1:2] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name -} - -func init() { file_reconciler_v1_reconciler_proto_init() } -func file_reconciler_v1_reconciler_proto_init() { - if File_reconciler_v1_reconciler_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_reconciler_v1_reconciler_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IngestionDataSource); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_reconciler_v1_reconciler_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RetrieveIngestionDataSourcesRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_reconciler_v1_reconciler_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RetrieveIngestionDataSourcesResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_reconciler_v1_reconciler_proto_rawDesc, - NumEnums: 0, - NumMessages: 3, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_reconciler_v1_reconciler_proto_goTypes, - DependencyIndexes: file_reconciler_v1_reconciler_proto_depIdxs, - MessageInfos: file_reconciler_v1_reconciler_proto_msgTypes, - }.Build() - File_reconciler_v1_reconciler_proto = out.File - file_reconciler_v1_reconciler_proto_rawDesc = nil - file_reconciler_v1_reconciler_proto_goTypes = nil - file_reconciler_v1_reconciler_proto_depIdxs = nil -} diff --git a/docs/diode-proto.md b/docs/diode-proto.md index 26bfc7bb..d624c26f 100644 --- a/docs/diode-proto.md +++ b/docs/diode-proto.md @@ -5,6 +5,9 @@ ## Table of Contents - [diode/v1/ingester.proto](#diode_v1_ingester-proto) + - [Cluster](#diode-v1-Cluster) + - [ClusterGroup](#diode-v1-ClusterGroup) + - [ClusterType](#diode-v1-ClusterType) - [Device](#diode-v1-Device) - [DeviceType](#diode-v1-DeviceType) - [Entity](#diode-v1-Entity) @@ -18,6 +21,9 @@ - [Role](#diode-v1-Role) - [Site](#diode-v1-Site) - [Tag](#diode-v1-Tag) + - [VMInterface](#diode-v1-VMInterface) + - [VirtualDisk](#diode-v1-VirtualDisk) + - [VirtualMachine](#diode-v1-VirtualMachine) - [IngesterService](#diode-v1-IngesterService) @@ -28,6 +34,48 @@ ## diode/v1/ingester.proto + + +### Cluster + +A Cluster + +| Field | Type | Label | Description | +|-------------|----------------------------------------|----------|-------------| +| name | [string](#string) | | | +| type | [ClusterType](#diode-v1-ClusterType) | | | +| group | [ClusterGroup](#diode-v1-ClusterGroup) | | | +| site | [Site](#diode-v1-Site) | | | +| status | [string](#string) | | | +| description | [string](#string) | optional | | +| tags | [Tag](#diode-v1-Tag) | repeated | | + + + +### ClusterGroup + +A Cluster Group + +| Field | Type | Label | Description | +|-------------|----------------------|----------|-------------| +| name | [string](#string) | | | +| slug | [string](#string) | | | +| description | [string](#string) | optional | | +| tags | [Tag](#diode-v1-Tag) | repeated | | + + + +### ClusterType + +A Cluster Type + +| Field | Type | Label | Description | +|-------------|----------------------|----------|-------------| +| name | [string](#string) | | | +| slug | [string](#string) | | | +| description | [string](#string) | optional | | +| tags | [Tag](#diode-v1-Tag) | repeated | | + ### Device @@ -73,18 +121,24 @@ A device type An ingest entity wrapper -| Field | Type | Label | Description | -|--------------|---------------------------------------------------------|-------|-----------------------------------------------| -| site | [Site](#diode-v1-Site) | | | -| platform | [Platform](#diode-v1-Platform) | | | -| manufacturer | [Manufacturer](#diode-v1-Manufacturer) | | | -| device | [Device](#diode-v1-Device) | | | -| device_role | [Role](#diode-v1-Role) | | | -| device_type | [DeviceType](#diode-v1-DeviceType) | | | -| interface | [Interface](#diode-v1-Interface) | | | -| ip_address | [IPAddress](#diode-v1-IPAddress) | | | -| prefix | [Prefix](#diode-v1-Prefix) | | | -| timestamp | [google.protobuf.Timestamp](#google-protobuf-Timestamp) | | The timestamp of the data discovery at source | +| Field | Type | Label | Description | +|-----------------|---------------------------------------------------------|-------|-----------------------------------------------| +| site | [Site](#diode-v1-Site) | | | +| platform | [Platform](#diode-v1-Platform) | | | +| manufacturer | [Manufacturer](#diode-v1-Manufacturer) | | | +| device | [Device](#diode-v1-Device) | | | +| device_role | [Role](#diode-v1-Role) | | | +| device_type | [DeviceType](#diode-v1-DeviceType) | | | +| interface | [Interface](#diode-v1-Interface) | | | +| ip_address | [IPAddress](#diode-v1-IPAddress) | | | +| prefix | [Prefix](#diode-v1-Prefix) | | | +| cluster_group | [ClusterGroup](#diode-v1-ClusterGroup) | | | +| cluster_type | [ClusterType](#diode-v1-ClusterType) | | | +| cluster | [Cluster](#diode-v1-Cluster) | | | +| virtual_machine | [VirtualMachine](#diode-v1-VirtualMachine) | | | +| vminterface | [VMInterface](#diode-v1-VMInterface) | | | +| virtual_disk | [VirtualDisk](#diode-v1-VirtualDisk) | | | +| timestamp | [google.protobuf.Timestamp](#google-protobuf-Timestamp) | | The timestamp of the data discovery at source | @@ -239,6 +293,60 @@ A tag | slug | [string](#string) | | | | color | [string](#string) | | | + + +### VMInterface + +A Virtual Machine Interface + +| Field | Type | Label | Description | +|-----------------|--------------------------------------------|----------|-------------| +| virtual_machine | [VirtualMachine](#diode-v1-VirtualMachine) | | | +| name | [string](#string) | | | +| enabled | [bool](#bool) | optional | | +| mtu | [int32](#int32) | optional | | +| mac_address | [string](#string) | optional | | +| description | [string](#string) | optional | | +| tags | [Tag](#diode-v1-Tag) | repeated | | + + + +### VirtualDisk + +A Virtual Disk + +| Field | Type | Label | Description | +|-----------------|--------------------------------------------|----------|-------------| +| virtual_machine | [VirtualMachine](#diode-v1-VirtualMachine) | | | +| name | [string](#string) | | | +| size | [int32](#int32) | | | +| description | [string](#string) | optional | | +| tags | [Tag](#diode-v1-Tag) | repeated | | + + + +### VirtualMachine + +A Virtual Machine + +| Field | Type | Label | Description | +|-------------|----------------------------------|----------|-------------| +| name | [string](#string) | | | +| status | [string](#string) | | | +| site | [Site](#diode-v1-Site) | | | +| cluster | [Cluster](#diode-v1-Cluster) | | | +| role | [Role](#diode-v1-Role) | | | +| device | [Device](#diode-v1-Device) | | | +| platform | [Platform](#diode-v1-Platform) | | | +| primary_ip4 | [IPAddress](#diode-v1-IPAddress) | | | +| primary_ip6 | [IPAddress](#diode-v1-IPAddress) | | | +| vcpus | [int32](#int32) | optional | | +| memory | [int32](#int32) | optional | | +| disk | [int32](#int32) | optional | | +| description | [string](#string) | optional | | +| comments | [string](#string) | optional | | +| tags | [Tag](#diode-v1-Tag) | repeated | | + ### IngesterService From 037ee270c3fc8ff15898da6760589dd1ebc98c6f Mon Sep 17 00:00:00 2001 From: Michal Fiedorowicz Date: Fri, 30 Aug 2024 17:33:10 +0100 Subject: [PATCH 09/13] feat: add authorization for reconciler service (#159) * feat: add authorization for reconciler service Signed-off-by: Michal Fiedorowicz * fix tests Signed-off-by: Michal Fiedorowicz * add unit tests for authorization check Signed-off-by: Michal Fiedorowicz * fix goimport Signed-off-by: Michal Fiedorowicz --------- Signed-off-by: Michal Fiedorowicz --- diode-server/README.md | 11 +++- diode-server/docker/docker-compose.yaml | 2 + diode-server/docker/sample.env | 1 + diode-server/ingester/component.go | 5 +- diode-server/ingester/component_test.go | 2 + diode-server/ingester/config.go | 11 ++-- diode-server/reconciler/api_keys.go | 7 +- diode-server/reconciler/config.go | 7 +- diode-server/reconciler/reconciler_test.go | 2 + diode-server/reconciler/server.go | 52 ++++++++++++++- .../reconciler/server_internal_test.go | 65 +++++++++++++++++++ 11 files changed, 148 insertions(+), 17 deletions(-) create mode 100644 diode-server/reconciler/server_internal_test.go diff --git a/diode-server/README.md b/diode-server/README.md index 39128f1d..a1ebd20b 100644 --- a/diode-server/README.md +++ b/diode-server/README.md @@ -11,7 +11,7 @@ at [https://netboxlabs.com/blog/introducing-diode-streamlining-data-ingestion-in ## Diode services -Diode server is comprised of two services: +Diode server is composed of two services: ### Ingester Service @@ -24,11 +24,13 @@ Diode server is comprised of two services: - Processes data from Redis streams and converts it for storage. - Manages data sources and their API keys. -- Implements a reconciliation engine to detect and store deltas between ingested data and the current NetBox object state. +- Implements a reconciliation engine to detect and store deltas between ingested data and the current NetBox object + state. ## Compatibility -The Diode server has been tested with NetBox versions 3.7.2 and above. The Diode server also requires the [Diode NetBox Plugin](https://github.com/netboxlabs/diode-netbox-plugin). +The Diode server has been tested with NetBox versions 3.7.2 and above. The Diode server also requires +the [Diode NetBox Plugin](https://github.com/netboxlabs/diode-netbox-plugin). ## Running the Diode server @@ -58,10 +60,13 @@ curl -o .env https://raw.githubusercontent.com/netboxlabs/diode/develop/diode-se ``` Edit the `.env` to match your environment: + * `NETBOX_DIODE_PLUGIN_API_BASE_URL`: URL for the Diode NetBox plugin API * `DIODE_TO_NETBOX_API_KEY`: API key generated with the Diode NetBox plugin installation * `DIODE_API_KEY`: API key generated with the Diode NetBox plugin installation * `NETBOX_TO_DIODE_API_KEY`: API key generated with the Diode NetBox plugin installation +* `INGESTER_TO_RECONCILER_API_KEY`: API key to authorize RPC calls between the Ingester and Reconciler services (at + least 40 characters, example generation with shell command: `openssl rand -base64 40 | head -c 40`) ### Running the Diode server diff --git a/diode-server/docker/docker-compose.yaml b/diode-server/docker/docker-compose.yaml index 313264c2..11407689 100644 --- a/diode-server/docker/docker-compose.yaml +++ b/diode-server/docker/docker-compose.yaml @@ -36,6 +36,7 @@ services: - REDIS_PORT=${REDIS_PORT} - RECONCILER_GRPC_HOST=${RECONCILER_GRPC_HOST} - RECONCILER_GRPC_PORT=${RECONCILER_GRPC_PORT} + - INGESTER_TO_RECONCILER_API_KEY=${INGESTER_TO_RECONCILER_API_KEY} - SENTRY_DSN=${SENTRY_DSN} restart: always ports: @@ -53,6 +54,7 @@ services: - NETBOX_DIODE_PLUGIN_API_BASE_URL=${NETBOX_DIODE_PLUGIN_API_BASE_URL} - DIODE_TO_NETBOX_API_KEY=${DIODE_TO_NETBOX_API_KEY} - NETBOX_TO_DIODE_API_KEY=${NETBOX_TO_DIODE_API_KEY} + - INGESTER_TO_RECONCILER_API_KEY=${INGESTER_TO_RECONCILER_API_KEY} - DIODE_API_KEY=${DIODE_API_KEY} - LOGGING_LEVEL=${LOGGING_LEVEL} - SENTRY_DSN=${SENTRY_DSN} diff --git a/diode-server/docker/sample.env b/diode-server/docker/sample.env index 85516fc5..a035f658 100644 --- a/diode-server/docker/sample.env +++ b/diode-server/docker/sample.env @@ -9,5 +9,6 @@ NETBOX_DIODE_PLUGIN_API_BASE_URL=http://NETBOX_HOST/api/plugins/diode DIODE_TO_NETBOX_API_KEY=1368dbad13e418d5a443d93cf255edde03a2a754 NETBOX_TO_DIODE_API_KEY=1e99338b8cab5fc637bc55f390bda1446f619c42 DIODE_API_KEY=5a52c45ee8231156cb620d193b0291912dd15433 +INGESTER_TO_RECONCILER_API_KEY=sXjJZe6BBzVuovrVyyH4Q3vbceqvDwh2kC3DRpML LOGGING_LEVEL=DEBUG SENTRY_DSN= diff --git a/diode-server/ingester/component.go b/diode-server/ingester/component.go index adccee7e..1fc44e14 100644 --- a/diode-server/ingester/component.go +++ b/diode-server/ingester/component.go @@ -78,7 +78,10 @@ func New(ctx context.Context, logger *slog.Logger) (*Component, error) { return nil, fmt.Errorf("failed to create reconciler client: %v", err) } - dataSources, err := reconcilerClient.RetrieveIngestionDataSources(ctx, &reconcilerpb.RetrieveIngestionDataSourcesRequest{}) + dataSources, err := reconcilerClient.RetrieveIngestionDataSources( + metadata.NewOutgoingContext(ctx, metadata.Pairs("authorization", cfg.IngesterToReconcilerAPIKey)), + &reconcilerpb.RetrieveIngestionDataSourcesRequest{}, + ) if err != nil { return nil, fmt.Errorf("failed to retrieve ingestion data sources: %v", err) } diff --git a/diode-server/ingester/component_test.go b/diode-server/ingester/component_test.go index 04e23c12..7239df51 100644 --- a/diode-server/ingester/component_test.go +++ b/diode-server/ingester/component_test.go @@ -48,6 +48,7 @@ func setupEnv(redisAddr string) { _ = os.Setenv("DIODE_TO_NETBOX_API_KEY", "diode_to_netbox_api_key") _ = os.Setenv("NETBOX_TO_DIODE_API_KEY", "netbox_to_diode_api_key") _ = os.Setenv("DIODE_API_KEY", "diode_api_key") + _ = os.Setenv("INGESTER_TO_RECONCILER_API_KEY", "ingester_to_reconciler_api_key") } func teardownEnv() { @@ -62,6 +63,7 @@ func teardownEnv() { _ = os.Unsetenv("DIODE_TO_NETBOX_API_KEY") _ = os.Unsetenv("NETBOX_TO_DIODE_API_KEY") _ = os.Unsetenv("DIODE_API_KEY") + _ = os.Unsetenv("INGESTER_TO_RECONCILER_API_KEY") } const bufSize = 1024 * 1024 diff --git a/diode-server/ingester/config.go b/diode-server/ingester/config.go index 14c0b7ac..0dddf2d8 100644 --- a/diode-server/ingester/config.go +++ b/diode-server/ingester/config.go @@ -2,9 +2,10 @@ package ingester // Config is the configuration for the ingester service type Config struct { - GRPCPort int `envconfig:"GRPC_PORT" default:"8081"` - RedisHost string `envconfig:"REDIS_HOST" default:"127.0.0.1"` - RedisPort string `envconfig:"REDIS_PORT" default:"6379"` - RedisPassword string `envconfig:"REDIS_PASSWORD" required:"true"` - RedisStreamDB int `envconfig:"REDIS_STREAM_DB" default:"1"` + GRPCPort int `envconfig:"GRPC_PORT" default:"8081"` + RedisHost string `envconfig:"REDIS_HOST" default:"127.0.0.1"` + RedisPort string `envconfig:"REDIS_PORT" default:"6379"` + RedisPassword string `envconfig:"REDIS_PASSWORD" required:"true"` + RedisStreamDB int `envconfig:"REDIS_STREAM_DB" default:"1"` + IngesterToReconcilerAPIKey string `envconfig:"INGESTER_TO_RECONCILER_API_KEY" required:"true"` } diff --git a/diode-server/reconciler/api_keys.go b/diode-server/reconciler/api_keys.go index 49a6e1e0..7677ec73 100644 --- a/diode-server/reconciler/api_keys.go +++ b/diode-server/reconciler/api_keys.go @@ -17,9 +17,10 @@ func (ak APIKeys) MarshalBinary() ([]byte, error) { func loadAPIKeys(ctx context.Context, cfg Config, rc *redis.Client) (APIKeys, error) { apiKeys := map[string]string{ - "DIODE_TO_NETBOX": cfg.DiodeToNetBoxAPIKey, - "NETBOX_TO_DIODE": cfg.NetBoxToDiodeAPIKey, - "DIODE": cfg.DiodeAPIKey, + "DIODE_TO_NETBOX": cfg.DiodeToNetBoxAPIKey, + "NETBOX_TO_DIODE": cfg.NetBoxToDiodeAPIKey, + "DIODE": cfg.DiodeAPIKey, + "INGESTER_TO_RECONCILER": cfg.IngesterToReconcilerAPIKey, } if err := rc.HSet(ctx, "diode.api_keys", apiKeys).Err(); err != nil { diff --git a/diode-server/reconciler/config.go b/diode-server/reconciler/config.go index 3e7f0868..84746abd 100644 --- a/diode-server/reconciler/config.go +++ b/diode-server/reconciler/config.go @@ -10,7 +10,8 @@ type Config struct { RedisStreamDB int `envconfig:"REDIS_STREAM_DB" default:"1"` // API keys - DiodeToNetBoxAPIKey string `envconfig:"DIODE_TO_NETBOX_API_KEY" required:"true"` - NetBoxToDiodeAPIKey string `envconfig:"NETBOX_TO_DIODE_API_KEY" required:"true"` - DiodeAPIKey string `envconfig:"DIODE_API_KEY" required:"true"` + DiodeToNetBoxAPIKey string `envconfig:"DIODE_TO_NETBOX_API_KEY" required:"true"` + NetBoxToDiodeAPIKey string `envconfig:"NETBOX_TO_DIODE_API_KEY" required:"true"` + DiodeAPIKey string `envconfig:"DIODE_API_KEY" required:"true"` + IngesterToReconcilerAPIKey string `envconfig:"INGESTER_TO_RECONCILER_API_KEY" required:"true"` } diff --git a/diode-server/reconciler/reconciler_test.go b/diode-server/reconciler/reconciler_test.go index 0a05fd8a..554bbcc5 100644 --- a/diode-server/reconciler/reconciler_test.go +++ b/diode-server/reconciler/reconciler_test.go @@ -35,6 +35,7 @@ func setupEnv(redisAddr string) { _ = os.Setenv("DIODE_TO_NETBOX_API_KEY", "diode_to_netbox_api_key") _ = os.Setenv("NETBOX_TO_DIODE_API_KEY", "netbox_to_diode_api_key") _ = os.Setenv("DIODE_API_KEY", "diode_api_key") + _ = os.Setenv("INGESTER_TO_RECONCILER_API_KEY", "ingester_to_reconciler_api_key") } func teardownEnv() { @@ -48,4 +49,5 @@ func teardownEnv() { _ = os.Unsetenv("DIODE_TO_NETBOX_API_KEY") _ = os.Unsetenv("NETBOX_TO_DIODE_API_KEY") _ = os.Unsetenv("DIODE_API_KEY") + _ = os.Unsetenv("INGESTER_TO_RECONCILER_API_KEY") } diff --git a/diode-server/reconciler/server.go b/diode-server/reconciler/server.go index e97314bc..a7d13e1b 100644 --- a/diode-server/reconciler/server.go +++ b/diode-server/reconciler/server.go @@ -2,18 +2,28 @@ package reconciler import ( "context" + "errors" "fmt" "log/slog" "net" + "strings" "github.com/kelseyhightower/envconfig" "github.com/redis/go-redis/v9" "google.golang.org/grpc" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/reflection" "github.com/netboxlabs/diode/diode-server/gen/diode/v1/reconcilerpb" ) +var ( + errMetadataNotFound = errors.New("no request metadata found") + + // ErrUnauthorized is an error for unauthorized requests + ErrUnauthorized = errors.New("missing or invalid authorization header") +) + // Server is a reconciler Server type Server struct { reconcilerpb.UnimplementedReconcilerServiceServer @@ -23,7 +33,7 @@ type Server struct { grpcListener net.Listener grpcServer *grpc.Server redisClient *redis.Client - apiKeys map[string]string + apiKeys APIKeys } // NewServer creates a new reconciler server @@ -51,7 +61,9 @@ func NewServer(ctx context.Context, logger *slog.Logger) (*Server, error) { return nil, fmt.Errorf("failed to configure data sources: %v", err) } - grpcServer := grpc.NewServer() + auth := newAuthUnaryInterceptor(logger, apiKeys) + grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor(auth)) + component := &Server{ config: cfg, logger: logger, @@ -60,12 +72,27 @@ func NewServer(ctx context.Context, logger *slog.Logger) (*Server, error) { redisClient: redisClient, apiKeys: apiKeys, } + reconcilerpb.RegisterReconcilerServiceServer(grpcServer, component) reflection.Register(grpcServer) return component, nil } +func newAuthUnaryInterceptor(logger *slog.Logger, apiKeys APIKeys) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, serverInfo *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, errMetadataNotFound + } + + if !isAuthorized(logger, serverInfo.FullMethod, apiKeys, md["authorization"]) { + return nil, ErrUnauthorized + } + return handler(ctx, req) + } +} + // Name returns the name of the server func (s *Server) Name() string { return "reconciler-grpc-server" @@ -118,3 +145,24 @@ func validateRetrieveIngestionDataSourcesRequest(in *reconcilerpb.RetrieveIngest } return nil } + +func isAuthorized(logger *slog.Logger, rpcMethod string, apiKeys APIKeys, authorization []string) bool { + if len(authorization) < 1 { + logger.Debug("missing authorization header", "rpcMethod", rpcMethod) + return false + } + + apiKey := strings.TrimSpace(authorization[0]) + + switch rpcMethod { + case reconcilerpb.ReconcilerService_RetrieveIngestionDataSources_FullMethodName: + ingesterToReconcilerAPIKey, ok := apiKeys["INGESTER_TO_RECONCILER"] + if !ok { + logger.Debug("missing INGESTER_TO_RECONCILER API key") + return false + } + return apiKey == ingesterToReconcilerAPIKey + } + + return false +} diff --git a/diode-server/reconciler/server_internal_test.go b/diode-server/reconciler/server_internal_test.go new file mode 100644 index 00000000..aebc87d9 --- /dev/null +++ b/diode-server/reconciler/server_internal_test.go @@ -0,0 +1,65 @@ +package reconciler + +import ( + "log/slog" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/netboxlabs/diode/diode-server/gen/diode/v1/reconcilerpb" +) + +func TestIsAuthorized(t *testing.T) { + tests := []struct { + name string + rpcMethod string + authorization []string + apiKeys map[string]string + isAuthorized bool + }{ + { + name: "missing authorization header", + rpcMethod: reconcilerpb.ReconcilerService_RetrieveIngestionDataSources_FullMethodName, + authorization: []string{}, + apiKeys: map[string]string{ + "INGESTER_TO_RECONCILER": "test", + }, + isAuthorized: false, + }, + { + name: "retrieve ingestion data sources with valid authorization", + rpcMethod: reconcilerpb.ReconcilerService_RetrieveIngestionDataSources_FullMethodName, + authorization: []string{"test"}, + apiKeys: map[string]string{ + "INGESTER_TO_RECONCILER": "test", + }, + isAuthorized: true, + }, + { + name: "retrieve ingestion data sources with invalid authorization", + rpcMethod: reconcilerpb.ReconcilerService_RetrieveIngestionDataSources_FullMethodName, + authorization: []string{"test0"}, + apiKeys: map[string]string{ + "INGESTER_TO_RECONCILER": "test", + }, + isAuthorized: false, + }, + { + name: "retrieve ingestion data sources for server without api key configured", + rpcMethod: reconcilerpb.ReconcilerService_RetrieveIngestionDataSources_FullMethodName, + authorization: []string{"test"}, + apiKeys: map[string]string{ + "DIODE": "foorbar", + }, + isAuthorized: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) + assert.Equal(t, tt.isAuthorized, isAuthorized(logger, tt.rpcMethod, tt.apiKeys, tt.authorization)) + }) + } +} From 44ca20ab5250cf33351e6a11f0e75239bd99f968 Mon Sep 17 00:00:00 2001 From: Michal Fiedorowicz Date: Fri, 30 Aug 2024 20:55:46 +0100 Subject: [PATCH 10/13] Update README about Go SDK (#160) Signed-off-by: Michal Fiedorowicz --- README.md | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0dbe3142..34d2b386 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,56 @@ # Diode -Diode is a NetBox data ingestion service that greatly simplifies and enhances the process to add and update network data in NetBox, ensuring your network source of truth is always accurate and can be trusted to power your network automation pipelines. Our guiding principle in designing Diode has been to make it as easy as possible to get data into NetBox, removing as much burden as possible from the user while shifting that effort to technology. - -To achieve this, Diode sits in front of NetBox and provides an API purpose built for ingestion of complex network data. Diode eliminates the need to preprocess data to make it conform to the strict object hierarchy imposed by the NetBox data model. This allows data to be sent to NetBox in a more freeform manner, in blocks that are intuitive for network engineers (such as by device or by interface) with much of the related information treated as attributes or properties of these components of interest. Then, Diode takes care of the heavy lifting, automatically transforming the data to align it with NetBox’s structured and comprehensive data model. Diode can even create placeholder objects to compensate for missing information, which means even fragmented information about the network can be captured in NetBox. +Diode is a NetBox data ingestion service that greatly simplifies and enhances the process to add and update network data +in NetBox, ensuring your network source of truth is always accurate and can be trusted to power your network automation +pipelines. Our guiding principle in designing Diode has been to make it as easy as possible to get data into NetBox, +removing as much burden as possible from the user while shifting that effort to technology. + +To achieve this, Diode sits in front of NetBox and provides an API purpose built for ingestion of complex network data. +Diode eliminates the need to preprocess data to make it conform to the strict object hierarchy imposed by the NetBox +data model. This allows data to be sent to NetBox in a more freeform manner, in blocks that are intuitive for network +engineers (such as by device or by interface) with much of the related information treated as attributes or properties +of these components of interest. Then, Diode takes care of the heavy lifting, automatically transforming the data to +align it with NetBox’s structured and comprehensive data model. Diode can even create placeholder objects to compensate +for missing information, which means even fragmented information about the network can be captured in NetBox. ## Project status -The Diode project is currently in the _Public Preview_ stage. Please see [NetBox Labs Product and Feature Lifecycle](https://docs.netboxlabs.com/product_feature_lifecycle/) for more details. We actively welcome feedback to help identify and prioritize bugs, new features and areas of improvement. +The Diode project is currently in the _Public Preview_ stage. Please +see [NetBox Labs Product and Feature Lifecycle](https://docs.netboxlabs.com/product_feature_lifecycle/) for more +details. We actively welcome feedback to help identify and prioritize bugs, new features and areas of improvement. ## Get started -Diode runs as a sidecar service to NetBox and can run anywhere with network connectivity to NetBox, whether on the same host or elsewhere. The overall Diode service is delivered through three main components (and a fourth optional component): +Diode runs as a sidecar service to NetBox and can run anywhere with network connectivity to NetBox, whether on the same +host or elsewhere. The overall Diode service is delivered through three main components (and a fourth optional +component): 1. Diode plugin - see how to [install the Diode plugin](https://github.com/netboxlabs/diode-netbox-plugin) -2. Diode server - see how to [run the Diode server](https://github.com/netboxlabs/diode/tree/develop/diode-server#readme) -3. Diode SDK - see how to [install the Diode client SDK](https://github.com/netboxlabs/diode-sdk-python) and [download Diode Python script examples](https://github.com/netboxlabs/netbox-learning/tree/develop/diode) -4. Diode agent (optional) - see how to [install and run the Diode NAPALM discovery agent](https://github.com/netboxlabs/diode-agent/tree/develop/diode-napalm-agent) +2. Diode server - see how + to [run the Diode server](https://github.com/netboxlabs/diode/tree/develop/diode-server#readme) +3. Diode SDK - see how + to [install the Diode Python client SDK](https://github.com/netboxlabs/diode-sdk-python), [download Diode Python script examples](https://github.com/netboxlabs/netbox-learning/tree/develop/diode) + and [use the Diode SDK Go](https://github.com/netboxlabs/diode-sdk-go) +4. Diode agent (optional) - see how + to [install and run the Diode NAPALM discovery agent](https://github.com/netboxlabs/diode-agent/tree/develop/diode-napalm-agent) ## Related Projects -- [diode-netbox-plugin](https://github.com/netboxlabs/diode-netbox-plugin) - The Diode NetBox plugin is a NetBox plugin and a required component of the Diode ingestion service. -- [diode-sdk-python](https://github.com/netboxlabs/diode-sdk-python) - Diode SDK Python is a Python library for interacting with the Diode ingestion service utilizing gRPC. -- [diode-agent](https://github.com/netboxlabs/diode-agent) - A collection of agents that leverage the Diode SDK to interact with the Diode server. +- [diode-netbox-plugin](https://github.com/netboxlabs/diode-netbox-plugin) - The Diode NetBox plugin is a NetBox plugin + and a required component of the Diode ingestion service. +- [diode-sdk-python](https://github.com/netboxlabs/diode-sdk-python) - Diode SDK Python is a Python library for + interacting with the Diode ingestion service utilizing gRPC. +- [diode-sdk-go](https://github.com/netboxlabs/diode-sdk-go) - Diode SDK Go is a Go module for interacting with the + Diode ingestion service utilizing gRPC. +- [diode-agent](https://github.com/netboxlabs/diode-agent) - A collection of agents that leverage the Diode SDK to + interact with the Diode server. ## License Distributed under the PolyForm Shield License 1.0.0 License. See [LICENSE.md](./LICENSE.md) for more information. -Diode protocol buffers are distributed under the Apache 2.0 License. See [LICENSE.txt](./diode-proto/LICENSE.txt) for more information. +Diode protocol buffers are distributed under the Apache 2.0 License. See [LICENSE.txt](./diode-proto/LICENSE.txt) for +more information. ## Required Notice From 910327bd5bb8ed44f0a6df2c477d1934acda517e Mon Sep 17 00:00:00 2001 From: Leonardo Parente <23251360+leoparente@users.noreply.github.com> Date: Tue, 3 Sep 2024 10:24:42 -0300 Subject: [PATCH 11/13] feat: add RetrieveIngestionLogs to reconciler proto (#154) --- diode-proto/diode/v1/reconciler.proto | 62 +- .../diode/v1/reconcilerpb/reconciler.pb.go | 814 +++++++++++++++-- .../v1/reconcilerpb/reconciler.pb.validate.go | 820 ++++++++++++++++++ .../v1/reconcilerpb/reconciler_grpc.pb.go | 39 + diode-server/reconciler/client.go | 8 + diode-server/reconciler/mocks/client.go | 74 ++ diode-server/reconciler/server.go | 6 + 7 files changed, 1756 insertions(+), 67 deletions(-) diff --git a/diode-proto/diode/v1/reconciler.proto b/diode-proto/diode/v1/reconciler.proto index 3dc624e6..861b4310 100644 --- a/diode-proto/diode/v1/reconciler.proto +++ b/diode-proto/diode/v1/reconciler.proto @@ -2,9 +2,10 @@ syntax = "proto3"; package diode.v1; +import "diode/v1/ingester.proto"; import "validate/validate.proto"; -option go_package = "github.com/netboxlabs/diode/diode-server/reconciler/v1/reconcilerpb"; +option go_package = "github.com/netboxlabs/diode/diode-server/gen/diode/v1/reconcilerpb"; // An ingestion data source message IngestionDataSource { @@ -36,8 +37,67 @@ message RetrieveIngestionDataSourcesResponse { repeated IngestionDataSource ingestion_data_sources = 1; } +// ChangeSetError represents an error when applying a change set +message ChangeSetError { + message Details { + message Error { + string error = 1; //key value pair of the error + string change_id = 2; + } + string change_set_id = 1; + string result = 2; + repeated Error errors = 3; + } + string message = 1; + int32 code = 2; + Details details = 3; +} + +enum State { + NEW = 0; + RECONCILED = 1; + FAILED = 2; + NO_CHANGES = 3; +} + +// An ingestion log +message IngestionLog { + string data_type = 1; + State state = 2; + string request_id = 3; + int64 ingestion_ts = 4; + string producer_app_name = 5; + string producer_app_version = 6; + string sdk_name = 7; + string sdk_version = 8; + diode.v1.Entity entity = 9; + ChangeSetError error = 10; +} + +// The request to retrieve ingestion logs +message RetrieveIngestionLogsRequest { + int32 page_size = 1 [(validate.rules).int32 = { + gte: 1 + lte: 1000 + }]; // Number of logs per page, default is 100 + optional State state = 2; // Optional filter by state field + string data_type = 3; // Optional filter by data type field + string request_id = 4; // Optional filter by request ID + int64 ingestion_ts_start = 5; // Optional start of ingestion timestamp range + int64 ingestion_ts_end = 6; // Optional end of ingestion timestamp range + string page_token = 7; // Token to fetch the next page of results +} + +// The response from the retrieve ingestion logs request +message RetrieveIngestionLogsResponse { + repeated IngestionLog logs = 1; // List of ingestion logs + string next_page_token = 2; // Token for the next page of results, if any +} + // Reconciler service API service ReconcilerService { // Retrieves ingestion data sources rpc RetrieveIngestionDataSources(RetrieveIngestionDataSourcesRequest) returns (RetrieveIngestionDataSourcesResponse) {} + // Retrieves ingestion logs + rpc RetrieveIngestionLogs(RetrieveIngestionLogsRequest) returns (RetrieveIngestionLogsResponse); } diff --git a/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.go b/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.go index c6fe7d29..393587af 100644 --- a/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.go +++ b/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.34.2 // protoc (unknown) // source: diode/v1/reconciler.proto @@ -8,6 +8,7 @@ package reconcilerpb import ( _ "github.com/envoyproxy/protoc-gen-validate/validate" + diodepb "github.com/netboxlabs/diode/diode-server/gen/diode/v1/diodepb" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -21,6 +22,58 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type State int32 + +const ( + State_NEW State = 0 + State_RECONCILED State = 1 + State_FAILED State = 2 + State_NO_CHANGES State = 3 +) + +// Enum value maps for State. +var ( + State_name = map[int32]string{ + 0: "NEW", + 1: "RECONCILED", + 2: "FAILED", + 3: "NO_CHANGES", + } + State_value = map[string]int32{ + "NEW": 0, + "RECONCILED": 1, + "FAILED": 2, + "NO_CHANGES": 3, + } +) + +func (x State) Enum() *State { + p := new(State) + *p = x + return p +} + +func (x State) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (State) Descriptor() protoreflect.EnumDescriptor { + return file_diode_v1_reconciler_proto_enumTypes[0].Descriptor() +} + +func (State) Type() protoreflect.EnumType { + return &file_diode_v1_reconciler_proto_enumTypes[0] +} + +func (x State) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use State.Descriptor instead. +func (State) EnumDescriptor() ([]byte, []int) { + return file_diode_v1_reconciler_proto_rawDescGZIP(), []int{0} +} + // An ingestion data source type IngestionDataSource struct { state protoimpl.MessageState @@ -189,58 +242,595 @@ func (x *RetrieveIngestionDataSourcesResponse) GetIngestionDataSources() []*Inge return nil } +// ChangeSetError represents an error when applying a change set +type ChangeSetError struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + Code int32 `protobuf:"varint,2,opt,name=code,proto3" json:"code,omitempty"` + Details *ChangeSetError_Details `protobuf:"bytes,3,opt,name=details,proto3" json:"details,omitempty"` +} + +func (x *ChangeSetError) Reset() { + *x = ChangeSetError{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_reconciler_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChangeSetError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChangeSetError) ProtoMessage() {} + +func (x *ChangeSetError) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_reconciler_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChangeSetError.ProtoReflect.Descriptor instead. +func (*ChangeSetError) Descriptor() ([]byte, []int) { + return file_diode_v1_reconciler_proto_rawDescGZIP(), []int{3} +} + +func (x *ChangeSetError) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *ChangeSetError) GetCode() int32 { + if x != nil { + return x.Code + } + return 0 +} + +func (x *ChangeSetError) GetDetails() *ChangeSetError_Details { + if x != nil { + return x.Details + } + return nil +} + +// An ingestion log +type IngestionLog struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DataType string `protobuf:"bytes,1,opt,name=data_type,json=dataType,proto3" json:"data_type,omitempty"` + State State `protobuf:"varint,2,opt,name=state,proto3,enum=diode.v1.State" json:"state,omitempty"` + RequestId string `protobuf:"bytes,3,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + IngestionTs int64 `protobuf:"varint,4,opt,name=ingestion_ts,json=ingestionTs,proto3" json:"ingestion_ts,omitempty"` + ProducerAppName string `protobuf:"bytes,5,opt,name=producer_app_name,json=producerAppName,proto3" json:"producer_app_name,omitempty"` + ProducerAppVersion string `protobuf:"bytes,6,opt,name=producer_app_version,json=producerAppVersion,proto3" json:"producer_app_version,omitempty"` + SdkName string `protobuf:"bytes,7,opt,name=sdk_name,json=sdkName,proto3" json:"sdk_name,omitempty"` + SdkVersion string `protobuf:"bytes,8,opt,name=sdk_version,json=sdkVersion,proto3" json:"sdk_version,omitempty"` + Entity *diodepb.Entity `protobuf:"bytes,9,opt,name=entity,proto3" json:"entity,omitempty"` + Error *ChangeSetError `protobuf:"bytes,10,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *IngestionLog) Reset() { + *x = IngestionLog{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_reconciler_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IngestionLog) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IngestionLog) ProtoMessage() {} + +func (x *IngestionLog) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_reconciler_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IngestionLog.ProtoReflect.Descriptor instead. +func (*IngestionLog) Descriptor() ([]byte, []int) { + return file_diode_v1_reconciler_proto_rawDescGZIP(), []int{4} +} + +func (x *IngestionLog) GetDataType() string { + if x != nil { + return x.DataType + } + return "" +} + +func (x *IngestionLog) GetState() State { + if x != nil { + return x.State + } + return State_NEW +} + +func (x *IngestionLog) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *IngestionLog) GetIngestionTs() int64 { + if x != nil { + return x.IngestionTs + } + return 0 +} + +func (x *IngestionLog) GetProducerAppName() string { + if x != nil { + return x.ProducerAppName + } + return "" +} + +func (x *IngestionLog) GetProducerAppVersion() string { + if x != nil { + return x.ProducerAppVersion + } + return "" +} + +func (x *IngestionLog) GetSdkName() string { + if x != nil { + return x.SdkName + } + return "" +} + +func (x *IngestionLog) GetSdkVersion() string { + if x != nil { + return x.SdkVersion + } + return "" +} + +func (x *IngestionLog) GetEntity() *diodepb.Entity { + if x != nil { + return x.Entity + } + return nil +} + +func (x *IngestionLog) GetError() *ChangeSetError { + if x != nil { + return x.Error + } + return nil +} + +// The request to retrieve ingestion logs +type RetrieveIngestionLogsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` // Number of logs per page, default is 100 + State *State `protobuf:"varint,2,opt,name=state,proto3,enum=diode.v1.State,oneof" json:"state,omitempty"` // Optional filter by state field + DataType string `protobuf:"bytes,3,opt,name=data_type,json=dataType,proto3" json:"data_type,omitempty"` // Optional filter by data type field + RequestId string `protobuf:"bytes,4,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` // Optional filter by request ID + IngestionTsStart int64 `protobuf:"varint,5,opt,name=ingestion_ts_start,json=ingestionTsStart,proto3" json:"ingestion_ts_start,omitempty"` // Optional start of ingestion timestamp range + IngestionTsEnd int64 `protobuf:"varint,6,opt,name=ingestion_ts_end,json=ingestionTsEnd,proto3" json:"ingestion_ts_end,omitempty"` // Optional end of ingestion timestamp range + PageToken string `protobuf:"bytes,7,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` // Token to fetch the next page of results +} + +func (x *RetrieveIngestionLogsRequest) Reset() { + *x = RetrieveIngestionLogsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_reconciler_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RetrieveIngestionLogsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveIngestionLogsRequest) ProtoMessage() {} + +func (x *RetrieveIngestionLogsRequest) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_reconciler_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetrieveIngestionLogsRequest.ProtoReflect.Descriptor instead. +func (*RetrieveIngestionLogsRequest) Descriptor() ([]byte, []int) { + return file_diode_v1_reconciler_proto_rawDescGZIP(), []int{5} +} + +func (x *RetrieveIngestionLogsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *RetrieveIngestionLogsRequest) GetState() State { + if x != nil && x.State != nil { + return *x.State + } + return State_NEW +} + +func (x *RetrieveIngestionLogsRequest) GetDataType() string { + if x != nil { + return x.DataType + } + return "" +} + +func (x *RetrieveIngestionLogsRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *RetrieveIngestionLogsRequest) GetIngestionTsStart() int64 { + if x != nil { + return x.IngestionTsStart + } + return 0 +} + +func (x *RetrieveIngestionLogsRequest) GetIngestionTsEnd() int64 { + if x != nil { + return x.IngestionTsEnd + } + return 0 +} + +func (x *RetrieveIngestionLogsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +// The response from the retrieve ingestion logs request +type RetrieveIngestionLogsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Logs []*IngestionLog `protobuf:"bytes,1,rep,name=logs,proto3" json:"logs,omitempty"` // List of ingestion logs + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` // Token for the next page of results, if any +} + +func (x *RetrieveIngestionLogsResponse) Reset() { + *x = RetrieveIngestionLogsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_reconciler_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RetrieveIngestionLogsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveIngestionLogsResponse) ProtoMessage() {} + +func (x *RetrieveIngestionLogsResponse) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_reconciler_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetrieveIngestionLogsResponse.ProtoReflect.Descriptor instead. +func (*RetrieveIngestionLogsResponse) Descriptor() ([]byte, []int) { + return file_diode_v1_reconciler_proto_rawDescGZIP(), []int{6} +} + +func (x *RetrieveIngestionLogsResponse) GetLogs() []*IngestionLog { + if x != nil { + return x.Logs + } + return nil +} + +func (x *RetrieveIngestionLogsResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +type ChangeSetError_Details struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChangeSetId string `protobuf:"bytes,1,opt,name=change_set_id,json=changeSetId,proto3" json:"change_set_id,omitempty"` + Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` + Errors []*ChangeSetError_Details_Error `protobuf:"bytes,3,rep,name=errors,proto3" json:"errors,omitempty"` +} + +func (x *ChangeSetError_Details) Reset() { + *x = ChangeSetError_Details{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_reconciler_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChangeSetError_Details) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChangeSetError_Details) ProtoMessage() {} + +func (x *ChangeSetError_Details) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_reconciler_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChangeSetError_Details.ProtoReflect.Descriptor instead. +func (*ChangeSetError_Details) Descriptor() ([]byte, []int) { + return file_diode_v1_reconciler_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *ChangeSetError_Details) GetChangeSetId() string { + if x != nil { + return x.ChangeSetId + } + return "" +} + +func (x *ChangeSetError_Details) GetResult() string { + if x != nil { + return x.Result + } + return "" +} + +func (x *ChangeSetError_Details) GetErrors() []*ChangeSetError_Details_Error { + if x != nil { + return x.Errors + } + return nil +} + +type ChangeSetError_Details_Error struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` //key value pair of the error + ChangeId string `protobuf:"bytes,2,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` +} + +func (x *ChangeSetError_Details_Error) Reset() { + *x = ChangeSetError_Details_Error{} + if protoimpl.UnsafeEnabled { + mi := &file_diode_v1_reconciler_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChangeSetError_Details_Error) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChangeSetError_Details_Error) ProtoMessage() {} + +func (x *ChangeSetError_Details_Error) ProtoReflect() protoreflect.Message { + mi := &file_diode_v1_reconciler_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChangeSetError_Details_Error.ProtoReflect.Descriptor instead. +func (*ChangeSetError_Details_Error) Descriptor() ([]byte, []int) { + return file_diode_v1_reconciler_proto_rawDescGZIP(), []int{3, 0, 0} +} + +func (x *ChangeSetError_Details_Error) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *ChangeSetError_Details_Error) GetChangeId() string { + if x != nil { + return x.ChangeId + } + return "" +} + var File_diode_v1_reconciler_proto protoreflect.FileDescriptor var file_diode_v1_reconciler_proto_rawDesc = []byte{ 0x0a, 0x19, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x64, 0x69, 0x6f, - 0x64, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x59, - 0x0a, 0x13, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x28, 0x18, - 0x28, 0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x22, 0xab, 0x01, 0x0a, 0x23, 0x52, 0x65, - 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, - 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x0a, 0xfa, 0x42, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x25, 0x0a, 0x08, 0x73, 0x64, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, - 0x07, 0x73, 0x64, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x73, 0x64, 0x6b, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, 0xfa, - 0x42, 0x19, 0x72, 0x17, 0x32, 0x15, 0x5e, 0x28, 0x5c, 0x64, 0x29, 0x2b, 0x5c, 0x2e, 0x28, 0x5c, - 0x64, 0x29, 0x2b, 0x5c, 0x2e, 0x28, 0x5c, 0x64, 0x29, 0x2b, 0x24, 0x52, 0x0a, 0x73, 0x64, 0x6b, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x7b, 0x0a, 0x24, 0x52, 0x65, 0x74, 0x72, 0x69, - 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, - 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x53, 0x0a, 0x16, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, - 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, - 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x14, - 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x32, 0x94, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, - 0x6c, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7f, 0x0a, 0x1c, 0x52, 0x65, - 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, - 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2d, 0x2e, 0x64, 0x69, 0x6f, - 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, - 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x64, 0x69, 0x6f, 0x64, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, - 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xa4, 0x01, 0x0a, 0x0c, - 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0f, 0x52, 0x65, - 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, - 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x65, 0x74, 0x62, - 0x6f, 0x78, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2f, 0x64, 0x69, 0x6f, - 0x64, 0x65, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x64, 0x69, - 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, - 0x72, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x44, 0x69, 0x6f, 0x64, - 0x65, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x44, 0x69, 0x6f, 0x64, 0x65, 0x5c, 0x56, 0x31, 0xe2, - 0x02, 0x14, 0x44, 0x69, 0x6f, 0x64, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x44, 0x69, 0x6f, 0x64, 0x65, 0x3a, 0x3a, - 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x17, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x31, 0x2f, + 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x59, 0x0a, 0x13, 0x49, 0x6e, 0x67, 0x65, 0x73, + 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1e, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, + 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, + 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x28, 0x18, 0x28, 0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, + 0x65, 0x79, 0x22, 0xab, 0x01, 0x0a, 0x23, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, + 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x72, 0x05, 0x10, + 0x01, 0x18, 0xff, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x08, 0x73, 0x64, + 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, + 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x07, 0x73, 0x64, 0x6b, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x73, 0x64, 0x6b, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, 0xfa, 0x42, 0x19, 0x72, 0x17, 0x32, 0x15, 0x5e, + 0x28, 0x5c, 0x64, 0x29, 0x2b, 0x5c, 0x2e, 0x28, 0x5c, 0x64, 0x29, 0x2b, 0x5c, 0x2e, 0x28, 0x5c, + 0x64, 0x29, 0x2b, 0x24, 0x52, 0x0a, 0x73, 0x64, 0x6b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0x7b, 0x0a, 0x24, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, + 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x16, 0x69, 0x6e, 0x67, 0x65, + 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, + 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x14, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, + 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22, 0xbe, 0x02, + 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x3a, + 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x20, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x53, 0x65, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x73, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x1a, 0xc1, 0x01, 0x0a, 0x07, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x3e, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x44, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x73, 0x1a, 0x3a, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x49, 0x64, 0x22, 0x88, + 0x03, 0x0a, 0x0c, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x67, 0x12, + 0x1b, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x64, 0x69, + 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, + 0x69, 0x6f, 0x6e, 0x54, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, + 0x72, 0x5f, 0x61, 0x70, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x41, 0x70, 0x70, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x30, 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x5f, 0x61, 0x70, + 0x70, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x12, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x41, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x64, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x64, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x64, 0x6b, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x64, 0x6b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x28, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x74, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xb0, 0x02, 0x0a, 0x1c, 0x52, 0x65, + 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4c, + 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0xfa, + 0x42, 0x07, 0x1a, 0x05, 0x18, 0xe8, 0x07, 0x28, 0x01, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x88, 0x01, 0x01, 0x12, + 0x1b, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x69, + 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, + 0x6f, 0x6e, 0x54, 0x73, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x6e, 0x67, + 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x73, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x73, + 0x45, 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x73, 0x0a, 0x1d, + 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, + 0x6e, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, + 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x69, + 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, + 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, + 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x2a, 0x3c, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4e, 0x45, + 0x57, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x43, 0x49, 0x4c, 0x45, + 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x12, + 0x0e, 0x0a, 0x0a, 0x4e, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x53, 0x10, 0x03, 0x32, + 0xfe, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7f, 0x0a, 0x1c, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, + 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, + 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, + 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x68, 0x0a, 0x15, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, + 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x67, 0x73, 0x12, + 0x26, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, + 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x67, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x49, 0x6e, 0x67, 0x65, 0x73, + 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0xa4, 0x01, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x76, + 0x31, 0x42, 0x0f, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x6e, 0x65, 0x74, 0x62, 0x6f, 0x78, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x64, 0x69, 0x6f, 0x64, + 0x65, 0x2f, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, + 0x65, 0x6e, 0x2f, 0x64, 0x69, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x63, 0x6f, + 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x72, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, + 0x08, 0x44, 0x69, 0x6f, 0x64, 0x65, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x44, 0x69, 0x6f, 0x64, + 0x65, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x14, 0x44, 0x69, 0x6f, 0x64, 0x65, 0x5c, 0x56, 0x31, 0x5c, + 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x44, 0x69, + 0x6f, 0x64, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -255,21 +845,39 @@ func file_diode_v1_reconciler_proto_rawDescGZIP() []byte { return file_diode_v1_reconciler_proto_rawDescData } -var file_diode_v1_reconciler_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_diode_v1_reconciler_proto_goTypes = []interface{}{ - (*IngestionDataSource)(nil), // 0: diode.v1.IngestionDataSource - (*RetrieveIngestionDataSourcesRequest)(nil), // 1: diode.v1.RetrieveIngestionDataSourcesRequest - (*RetrieveIngestionDataSourcesResponse)(nil), // 2: diode.v1.RetrieveIngestionDataSourcesResponse +var file_diode_v1_reconciler_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_diode_v1_reconciler_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_diode_v1_reconciler_proto_goTypes = []any{ + (State)(0), // 0: diode.v1.State + (*IngestionDataSource)(nil), // 1: diode.v1.IngestionDataSource + (*RetrieveIngestionDataSourcesRequest)(nil), // 2: diode.v1.RetrieveIngestionDataSourcesRequest + (*RetrieveIngestionDataSourcesResponse)(nil), // 3: diode.v1.RetrieveIngestionDataSourcesResponse + (*ChangeSetError)(nil), // 4: diode.v1.ChangeSetError + (*IngestionLog)(nil), // 5: diode.v1.IngestionLog + (*RetrieveIngestionLogsRequest)(nil), // 6: diode.v1.RetrieveIngestionLogsRequest + (*RetrieveIngestionLogsResponse)(nil), // 7: diode.v1.RetrieveIngestionLogsResponse + (*ChangeSetError_Details)(nil), // 8: diode.v1.ChangeSetError.Details + (*ChangeSetError_Details_Error)(nil), // 9: diode.v1.ChangeSetError.Details.Error + (*diodepb.Entity)(nil), // 10: diode.v1.Entity } var file_diode_v1_reconciler_proto_depIdxs = []int32{ - 0, // 0: diode.v1.RetrieveIngestionDataSourcesResponse.ingestion_data_sources:type_name -> diode.v1.IngestionDataSource - 1, // 1: diode.v1.ReconcilerService.RetrieveIngestionDataSources:input_type -> diode.v1.RetrieveIngestionDataSourcesRequest - 2, // 2: diode.v1.ReconcilerService.RetrieveIngestionDataSources:output_type -> diode.v1.RetrieveIngestionDataSourcesResponse - 2, // [2:3] is the sub-list for method output_type - 1, // [1:2] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 1, // 0: diode.v1.RetrieveIngestionDataSourcesResponse.ingestion_data_sources:type_name -> diode.v1.IngestionDataSource + 8, // 1: diode.v1.ChangeSetError.details:type_name -> diode.v1.ChangeSetError.Details + 0, // 2: diode.v1.IngestionLog.state:type_name -> diode.v1.State + 10, // 3: diode.v1.IngestionLog.entity:type_name -> diode.v1.Entity + 4, // 4: diode.v1.IngestionLog.error:type_name -> diode.v1.ChangeSetError + 0, // 5: diode.v1.RetrieveIngestionLogsRequest.state:type_name -> diode.v1.State + 5, // 6: diode.v1.RetrieveIngestionLogsResponse.logs:type_name -> diode.v1.IngestionLog + 9, // 7: diode.v1.ChangeSetError.Details.errors:type_name -> diode.v1.ChangeSetError.Details.Error + 2, // 8: diode.v1.ReconcilerService.RetrieveIngestionDataSources:input_type -> diode.v1.RetrieveIngestionDataSourcesRequest + 6, // 9: diode.v1.ReconcilerService.RetrieveIngestionLogs:input_type -> diode.v1.RetrieveIngestionLogsRequest + 3, // 10: diode.v1.ReconcilerService.RetrieveIngestionDataSources:output_type -> diode.v1.RetrieveIngestionDataSourcesResponse + 7, // 11: diode.v1.ReconcilerService.RetrieveIngestionLogs:output_type -> diode.v1.RetrieveIngestionLogsResponse + 10, // [10:12] is the sub-list for method output_type + 8, // [8:10] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_diode_v1_reconciler_proto_init() } @@ -278,7 +886,7 @@ func file_diode_v1_reconciler_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_diode_v1_reconciler_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_reconciler_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*IngestionDataSource); i { case 0: return &v.state @@ -290,7 +898,7 @@ func file_diode_v1_reconciler_proto_init() { return nil } } - file_diode_v1_reconciler_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_reconciler_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*RetrieveIngestionDataSourcesRequest); i { case 0: return &v.state @@ -302,7 +910,7 @@ func file_diode_v1_reconciler_proto_init() { return nil } } - file_diode_v1_reconciler_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_diode_v1_reconciler_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*RetrieveIngestionDataSourcesResponse); i { case 0: return &v.state @@ -314,19 +922,93 @@ func file_diode_v1_reconciler_proto_init() { return nil } } + file_diode_v1_reconciler_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*ChangeSetError); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_reconciler_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*IngestionLog); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_reconciler_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*RetrieveIngestionLogsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_reconciler_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*RetrieveIngestionLogsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_reconciler_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*ChangeSetError_Details); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_diode_v1_reconciler_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*ChangeSetError_Details_Error); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } + file_diode_v1_reconciler_proto_msgTypes[5].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_diode_v1_reconciler_proto_rawDesc, - NumEnums: 0, - NumMessages: 3, + NumEnums: 1, + NumMessages: 9, NumExtensions: 0, NumServices: 1, }, GoTypes: file_diode_v1_reconciler_proto_goTypes, DependencyIndexes: file_diode_v1_reconciler_proto_depIdxs, + EnumInfos: file_diode_v1_reconciler_proto_enumTypes, MessageInfos: file_diode_v1_reconciler_proto_msgTypes, }.Build() File_diode_v1_reconciler_proto = out.File diff --git a/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.validate.go b/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.validate.go index 1c2570cf..149f2aaa 100644 --- a/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.validate.go +++ b/diode-server/gen/diode/v1/reconcilerpb/reconciler.pb.validate.go @@ -438,3 +438,823 @@ var _ interface { Cause() error ErrorName() string } = RetrieveIngestionDataSourcesResponseValidationError{} + +// Validate checks the field values on ChangeSetError with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *ChangeSetError) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ChangeSetError with the rules defined +// in the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in ChangeSetErrorMultiError, +// or nil if none found. +func (m *ChangeSetError) ValidateAll() error { + return m.validate(true) +} + +func (m *ChangeSetError) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Message + + // no validation rules for Code + + if all { + switch v := interface{}(m.GetDetails()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ChangeSetErrorValidationError{ + field: "Details", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ChangeSetErrorValidationError{ + field: "Details", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetDetails()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ChangeSetErrorValidationError{ + field: "Details", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if len(errors) > 0 { + return ChangeSetErrorMultiError(errors) + } + + return nil +} + +// ChangeSetErrorMultiError is an error wrapping multiple validation errors +// returned by ChangeSetError.ValidateAll() if the designated constraints +// aren't met. +type ChangeSetErrorMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ChangeSetErrorMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ChangeSetErrorMultiError) AllErrors() []error { return m } + +// ChangeSetErrorValidationError is the validation error returned by +// ChangeSetError.Validate if the designated constraints aren't met. +type ChangeSetErrorValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ChangeSetErrorValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ChangeSetErrorValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ChangeSetErrorValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ChangeSetErrorValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ChangeSetErrorValidationError) ErrorName() string { return "ChangeSetErrorValidationError" } + +// Error satisfies the builtin error interface +func (e ChangeSetErrorValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sChangeSetError.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ChangeSetErrorValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ChangeSetErrorValidationError{} + +// Validate checks the field values on IngestionLog with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *IngestionLog) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on IngestionLog with the rules defined +// in the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in IngestionLogMultiError, or +// nil if none found. +func (m *IngestionLog) ValidateAll() error { + return m.validate(true) +} + +func (m *IngestionLog) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for DataType + + // no validation rules for State + + // no validation rules for RequestId + + // no validation rules for IngestionTs + + // no validation rules for ProducerAppName + + // no validation rules for ProducerAppVersion + + // no validation rules for SdkName + + // no validation rules for SdkVersion + + if all { + switch v := interface{}(m.GetEntity()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, IngestionLogValidationError{ + field: "Entity", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, IngestionLogValidationError{ + field: "Entity", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetEntity()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return IngestionLogValidationError{ + field: "Entity", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetError()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, IngestionLogValidationError{ + field: "Error", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, IngestionLogValidationError{ + field: "Error", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetError()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return IngestionLogValidationError{ + field: "Error", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if len(errors) > 0 { + return IngestionLogMultiError(errors) + } + + return nil +} + +// IngestionLogMultiError is an error wrapping multiple validation errors +// returned by IngestionLog.ValidateAll() if the designated constraints aren't met. +type IngestionLogMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m IngestionLogMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m IngestionLogMultiError) AllErrors() []error { return m } + +// IngestionLogValidationError is the validation error returned by +// IngestionLog.Validate if the designated constraints aren't met. +type IngestionLogValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e IngestionLogValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e IngestionLogValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e IngestionLogValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e IngestionLogValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e IngestionLogValidationError) ErrorName() string { return "IngestionLogValidationError" } + +// Error satisfies the builtin error interface +func (e IngestionLogValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sIngestionLog.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = IngestionLogValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = IngestionLogValidationError{} + +// Validate checks the field values on RetrieveIngestionLogsRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *RetrieveIngestionLogsRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on RetrieveIngestionLogsRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// RetrieveIngestionLogsRequestMultiError, or nil if none found. +func (m *RetrieveIngestionLogsRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *RetrieveIngestionLogsRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if val := m.GetPageSize(); val < 1 || val > 1000 { + err := RetrieveIngestionLogsRequestValidationError{ + field: "PageSize", + reason: "value must be inside range [1, 1000]", + } + if !all { + return err + } + errors = append(errors, err) + } + + // no validation rules for DataType + + // no validation rules for RequestId + + // no validation rules for IngestionTsStart + + // no validation rules for IngestionTsEnd + + // no validation rules for PageToken + + if m.State != nil { + // no validation rules for State + } + + if len(errors) > 0 { + return RetrieveIngestionLogsRequestMultiError(errors) + } + + return nil +} + +// RetrieveIngestionLogsRequestMultiError is an error wrapping multiple +// validation errors returned by RetrieveIngestionLogsRequest.ValidateAll() if +// the designated constraints aren't met. +type RetrieveIngestionLogsRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m RetrieveIngestionLogsRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m RetrieveIngestionLogsRequestMultiError) AllErrors() []error { return m } + +// RetrieveIngestionLogsRequestValidationError is the validation error returned +// by RetrieveIngestionLogsRequest.Validate if the designated constraints +// aren't met. +type RetrieveIngestionLogsRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e RetrieveIngestionLogsRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e RetrieveIngestionLogsRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e RetrieveIngestionLogsRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e RetrieveIngestionLogsRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e RetrieveIngestionLogsRequestValidationError) ErrorName() string { + return "RetrieveIngestionLogsRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e RetrieveIngestionLogsRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sRetrieveIngestionLogsRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = RetrieveIngestionLogsRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = RetrieveIngestionLogsRequestValidationError{} + +// Validate checks the field values on RetrieveIngestionLogsResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *RetrieveIngestionLogsResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on RetrieveIngestionLogsResponse with +// the rules defined in the proto definition for this message. If any rules +// are violated, the result is a list of violation errors wrapped in +// RetrieveIngestionLogsResponseMultiError, or nil if none found. +func (m *RetrieveIngestionLogsResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *RetrieveIngestionLogsResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + for idx, item := range m.GetLogs() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, RetrieveIngestionLogsResponseValidationError{ + field: fmt.Sprintf("Logs[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, RetrieveIngestionLogsResponseValidationError{ + field: fmt.Sprintf("Logs[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return RetrieveIngestionLogsResponseValidationError{ + field: fmt.Sprintf("Logs[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + // no validation rules for NextPageToken + + if len(errors) > 0 { + return RetrieveIngestionLogsResponseMultiError(errors) + } + + return nil +} + +// RetrieveIngestionLogsResponseMultiError is an error wrapping multiple +// validation errors returned by RetrieveIngestionLogsResponse.ValidateAll() +// if the designated constraints aren't met. +type RetrieveIngestionLogsResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m RetrieveIngestionLogsResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m RetrieveIngestionLogsResponseMultiError) AllErrors() []error { return m } + +// RetrieveIngestionLogsResponseValidationError is the validation error +// returned by RetrieveIngestionLogsResponse.Validate if the designated +// constraints aren't met. +type RetrieveIngestionLogsResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e RetrieveIngestionLogsResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e RetrieveIngestionLogsResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e RetrieveIngestionLogsResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e RetrieveIngestionLogsResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e RetrieveIngestionLogsResponseValidationError) ErrorName() string { + return "RetrieveIngestionLogsResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e RetrieveIngestionLogsResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sRetrieveIngestionLogsResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = RetrieveIngestionLogsResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = RetrieveIngestionLogsResponseValidationError{} + +// Validate checks the field values on ChangeSetError_Details with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *ChangeSetError_Details) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ChangeSetError_Details with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// ChangeSetError_DetailsMultiError, or nil if none found. +func (m *ChangeSetError_Details) ValidateAll() error { + return m.validate(true) +} + +func (m *ChangeSetError_Details) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for ChangeSetId + + // no validation rules for Result + + for idx, item := range m.GetErrors() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ChangeSetError_DetailsValidationError{ + field: fmt.Sprintf("Errors[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ChangeSetError_DetailsValidationError{ + field: fmt.Sprintf("Errors[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ChangeSetError_DetailsValidationError{ + field: fmt.Sprintf("Errors[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return ChangeSetError_DetailsMultiError(errors) + } + + return nil +} + +// ChangeSetError_DetailsMultiError is an error wrapping multiple validation +// errors returned by ChangeSetError_Details.ValidateAll() if the designated +// constraints aren't met. +type ChangeSetError_DetailsMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ChangeSetError_DetailsMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ChangeSetError_DetailsMultiError) AllErrors() []error { return m } + +// ChangeSetError_DetailsValidationError is the validation error returned by +// ChangeSetError_Details.Validate if the designated constraints aren't met. +type ChangeSetError_DetailsValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ChangeSetError_DetailsValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ChangeSetError_DetailsValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ChangeSetError_DetailsValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ChangeSetError_DetailsValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ChangeSetError_DetailsValidationError) ErrorName() string { + return "ChangeSetError_DetailsValidationError" +} + +// Error satisfies the builtin error interface +func (e ChangeSetError_DetailsValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sChangeSetError_Details.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ChangeSetError_DetailsValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ChangeSetError_DetailsValidationError{} + +// Validate checks the field values on ChangeSetError_Details_Error with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *ChangeSetError_Details_Error) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ChangeSetError_Details_Error with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// ChangeSetError_Details_ErrorMultiError, or nil if none found. +func (m *ChangeSetError_Details_Error) ValidateAll() error { + return m.validate(true) +} + +func (m *ChangeSetError_Details_Error) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Error + + // no validation rules for ChangeId + + if len(errors) > 0 { + return ChangeSetError_Details_ErrorMultiError(errors) + } + + return nil +} + +// ChangeSetError_Details_ErrorMultiError is an error wrapping multiple +// validation errors returned by ChangeSetError_Details_Error.ValidateAll() if +// the designated constraints aren't met. +type ChangeSetError_Details_ErrorMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ChangeSetError_Details_ErrorMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ChangeSetError_Details_ErrorMultiError) AllErrors() []error { return m } + +// ChangeSetError_Details_ErrorValidationError is the validation error returned +// by ChangeSetError_Details_Error.Validate if the designated constraints +// aren't met. +type ChangeSetError_Details_ErrorValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ChangeSetError_Details_ErrorValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ChangeSetError_Details_ErrorValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ChangeSetError_Details_ErrorValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ChangeSetError_Details_ErrorValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ChangeSetError_Details_ErrorValidationError) ErrorName() string { + return "ChangeSetError_Details_ErrorValidationError" +} + +// Error satisfies the builtin error interface +func (e ChangeSetError_Details_ErrorValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sChangeSetError_Details_Error.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ChangeSetError_Details_ErrorValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ChangeSetError_Details_ErrorValidationError{} diff --git a/diode-server/gen/diode/v1/reconcilerpb/reconciler_grpc.pb.go b/diode-server/gen/diode/v1/reconcilerpb/reconciler_grpc.pb.go index b3693bbf..1721feae 100644 --- a/diode-server/gen/diode/v1/reconcilerpb/reconciler_grpc.pb.go +++ b/diode-server/gen/diode/v1/reconcilerpb/reconciler_grpc.pb.go @@ -20,6 +20,7 @@ const _ = grpc.SupportPackageIsVersion7 const ( ReconcilerService_RetrieveIngestionDataSources_FullMethodName = "/diode.v1.ReconcilerService/RetrieveIngestionDataSources" + ReconcilerService_RetrieveIngestionLogs_FullMethodName = "/diode.v1.ReconcilerService/RetrieveIngestionLogs" ) // ReconcilerServiceClient is the client API for ReconcilerService service. @@ -28,6 +29,8 @@ const ( type ReconcilerServiceClient interface { // Retrieves ingestion data sources RetrieveIngestionDataSources(ctx context.Context, in *RetrieveIngestionDataSourcesRequest, opts ...grpc.CallOption) (*RetrieveIngestionDataSourcesResponse, error) + // Retrieves ingestion logs + RetrieveIngestionLogs(ctx context.Context, in *RetrieveIngestionLogsRequest, opts ...grpc.CallOption) (*RetrieveIngestionLogsResponse, error) } type reconcilerServiceClient struct { @@ -47,12 +50,23 @@ func (c *reconcilerServiceClient) RetrieveIngestionDataSources(ctx context.Conte return out, nil } +func (c *reconcilerServiceClient) RetrieveIngestionLogs(ctx context.Context, in *RetrieveIngestionLogsRequest, opts ...grpc.CallOption) (*RetrieveIngestionLogsResponse, error) { + out := new(RetrieveIngestionLogsResponse) + err := c.cc.Invoke(ctx, ReconcilerService_RetrieveIngestionLogs_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // ReconcilerServiceServer is the server API for ReconcilerService service. // All implementations must embed UnimplementedReconcilerServiceServer // for forward compatibility type ReconcilerServiceServer interface { // Retrieves ingestion data sources RetrieveIngestionDataSources(context.Context, *RetrieveIngestionDataSourcesRequest) (*RetrieveIngestionDataSourcesResponse, error) + // Retrieves ingestion logs + RetrieveIngestionLogs(context.Context, *RetrieveIngestionLogsRequest) (*RetrieveIngestionLogsResponse, error) mustEmbedUnimplementedReconcilerServiceServer() } @@ -63,6 +77,9 @@ type UnimplementedReconcilerServiceServer struct { func (UnimplementedReconcilerServiceServer) RetrieveIngestionDataSources(context.Context, *RetrieveIngestionDataSourcesRequest) (*RetrieveIngestionDataSourcesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RetrieveIngestionDataSources not implemented") } +func (UnimplementedReconcilerServiceServer) RetrieveIngestionLogs(context.Context, *RetrieveIngestionLogsRequest) (*RetrieveIngestionLogsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RetrieveIngestionLogs not implemented") +} func (UnimplementedReconcilerServiceServer) mustEmbedUnimplementedReconcilerServiceServer() {} // UnsafeReconcilerServiceServer may be embedded to opt out of forward compatibility for this service. @@ -94,6 +111,24 @@ func _ReconcilerService_RetrieveIngestionDataSources_Handler(srv interface{}, ct return interceptor(ctx, in, info, handler) } +func _ReconcilerService_RetrieveIngestionLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RetrieveIngestionLogsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReconcilerServiceServer).RetrieveIngestionLogs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ReconcilerService_RetrieveIngestionLogs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReconcilerServiceServer).RetrieveIngestionLogs(ctx, req.(*RetrieveIngestionLogsRequest)) + } + return interceptor(ctx, in, info, handler) +} + // ReconcilerService_ServiceDesc is the grpc.ServiceDesc for ReconcilerService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -105,6 +140,10 @@ var ReconcilerService_ServiceDesc = grpc.ServiceDesc{ MethodName: "RetrieveIngestionDataSources", Handler: _ReconcilerService_RetrieveIngestionDataSources_Handler, }, + { + MethodName: "RetrieveIngestionLogs", + Handler: _ReconcilerService_RetrieveIngestionLogs_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "diode/v1/reconciler.proto", diff --git a/diode-server/reconciler/client.go b/diode-server/reconciler/client.go index 4d6193d9..fd0e19da 100644 --- a/diode-server/reconciler/client.go +++ b/diode-server/reconciler/client.go @@ -36,6 +36,9 @@ type Client interface { // RetrieveIngestionDataSources retrieves ingestion data sources RetrieveIngestionDataSources(context.Context, *pb.RetrieveIngestionDataSourcesRequest, ...grpc.CallOption) (*pb.RetrieveIngestionDataSourcesResponse, error) + + // RetrieveIngestionLogs retrieves ingestion logs + RetrieveIngestionLogs(ctx context.Context, req *pb.RetrieveIngestionLogsRequest, opt ...grpc.CallOption) (*pb.RetrieveIngestionLogsResponse, error) } // GRPCClient is a gRPC implementation of the distributor service @@ -62,6 +65,11 @@ func (g *GRPCClient) RetrieveIngestionDataSources(ctx context.Context, req *pb.R return g.client.RetrieveIngestionDataSources(ctx, req, opt...) } +// RetrieveIngestionLogs retrieves ingestion logs +func (g *GRPCClient) RetrieveIngestionLogs(ctx context.Context, req *pb.RetrieveIngestionLogsRequest, opt ...grpc.CallOption) (*pb.RetrieveIngestionLogsResponse, error) { + return g.client.RetrieveIngestionLogs(ctx, req, opt...) +} + // NewClient creates a new reconciler client based on gRPC func NewClient() (Client, error) { dialOpts := []grpc.DialOption{ diff --git a/diode-server/reconciler/mocks/client.go b/diode-server/reconciler/mocks/client.go index a04d287e..e91ebb7c 100644 --- a/diode-server/reconciler/mocks/client.go +++ b/diode-server/reconciler/mocks/client.go @@ -144,6 +144,80 @@ func (_c *Client_RetrieveIngestionDataSources_Call) RunAndReturn(run func(contex return _c } +// RetrieveIngestionLogs provides a mock function with given fields: ctx, req, opt +func (_m *Client) RetrieveIngestionLogs(ctx context.Context, req *reconcilerpb.RetrieveIngestionLogsRequest, opt ...grpc.CallOption) (*reconcilerpb.RetrieveIngestionLogsResponse, error) { + _va := make([]interface{}, len(opt)) + for _i := range opt { + _va[_i] = opt[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for RetrieveIngestionLogs") + } + + var r0 *reconcilerpb.RetrieveIngestionLogsResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *reconcilerpb.RetrieveIngestionLogsRequest, ...grpc.CallOption) (*reconcilerpb.RetrieveIngestionLogsResponse, error)); ok { + return rf(ctx, req, opt...) + } + if rf, ok := ret.Get(0).(func(context.Context, *reconcilerpb.RetrieveIngestionLogsRequest, ...grpc.CallOption) *reconcilerpb.RetrieveIngestionLogsResponse); ok { + r0 = rf(ctx, req, opt...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*reconcilerpb.RetrieveIngestionLogsResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *reconcilerpb.RetrieveIngestionLogsRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, req, opt...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_RetrieveIngestionLogs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveIngestionLogs' +type Client_RetrieveIngestionLogs_Call struct { + *mock.Call +} + +// RetrieveIngestionLogs is a helper method to define mock.On call +// - ctx context.Context +// - req *reconcilerpb.RetrieveIngestionLogsRequest +// - opt ...grpc.CallOption +func (_e *Client_Expecter) RetrieveIngestionLogs(ctx interface{}, req interface{}, opt ...interface{}) *Client_RetrieveIngestionLogs_Call { + return &Client_RetrieveIngestionLogs_Call{Call: _e.mock.On("RetrieveIngestionLogs", + append([]interface{}{ctx, req}, opt...)...)} +} + +func (_c *Client_RetrieveIngestionLogs_Call) Run(run func(ctx context.Context, req *reconcilerpb.RetrieveIngestionLogsRequest, opt ...grpc.CallOption)) *Client_RetrieveIngestionLogs_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*reconcilerpb.RetrieveIngestionLogsRequest), variadicArgs...) + }) + return _c +} + +func (_c *Client_RetrieveIngestionLogs_Call) Return(_a0 *reconcilerpb.RetrieveIngestionLogsResponse, _a1 error) *Client_RetrieveIngestionLogs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Client_RetrieveIngestionLogs_Call) RunAndReturn(run func(context.Context, *reconcilerpb.RetrieveIngestionLogsRequest, ...grpc.CallOption) (*reconcilerpb.RetrieveIngestionLogsResponse, error)) *Client_RetrieveIngestionLogs_Call { + _c.Call.Return(run) + return _c +} + // NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewClient(t interface { diff --git a/diode-server/reconciler/server.go b/diode-server/reconciler/server.go index a7d13e1b..1b90d418 100644 --- a/diode-server/reconciler/server.go +++ b/diode-server/reconciler/server.go @@ -136,6 +136,12 @@ func (s *Server) RetrieveIngestionDataSources(_ context.Context, in *reconcilerp return &reconcilerpb.RetrieveIngestionDataSourcesResponse{IngestionDataSources: dataSources}, nil } +// RetrieveIngestionLogs retrieves logs +func (s *Server) RetrieveIngestionLogs(_ context.Context, _ *reconcilerpb.RetrieveIngestionLogsRequest) (*reconcilerpb.RetrieveIngestionLogsResponse, error) { + logs := make([]*reconcilerpb.IngestionLog, 0) + return &reconcilerpb.RetrieveIngestionLogsResponse{Logs: logs}, nil +} + func validateRetrieveIngestionDataSourcesRequest(in *reconcilerpb.RetrieveIngestionDataSourcesRequest) error { if in.GetSdkName() == "" { return fmt.Errorf("sdk name is empty") From 10eac34bcf88226fa92fe1870bcfc78852b60579 Mon Sep 17 00:00:00 2001 From: Leonardo Parente <23251360+leoparente@users.noreply.github.com> Date: Tue, 3 Sep 2024 11:09:30 -0300 Subject: [PATCH 12/13] feat: add cluster objects integration tests (#161) --- tests/features/cleanup.feature | 17 +- .../ingestion_cluster_group_objects.feature | 24 +++ .../ingestion_cluster_objects.feature | 24 +++ .../ingestion_cluster_type_objects.feature | 24 +++ tests/features/steps/cleanup.py | 27 +++ .../steps/ingestion_cluster_group_object.py | 78 +++++++++ .../steps/ingestion_cluster_object.py | 160 ++++++++++++++++++ .../steps/ingestion_cluster_type_object.py | 78 +++++++++ .../steps/ingestion_ip_address_object.py | 3 + 9 files changed, 432 insertions(+), 3 deletions(-) create mode 100644 tests/features/ingestion_cluster_group_objects.feature create mode 100644 tests/features/ingestion_cluster_objects.feature create mode 100644 tests/features/ingestion_cluster_type_objects.feature create mode 100644 tests/features/steps/ingestion_cluster_group_object.py create mode 100644 tests/features/steps/ingestion_cluster_object.py create mode 100644 tests/features/steps/ingestion_cluster_type_object.py diff --git a/tests/features/cleanup.feature b/tests/features/cleanup.feature index d5dc8463..e9a89267 100644 --- a/tests/features/cleanup.feature +++ b/tests/features/cleanup.feature @@ -11,16 +11,27 @@ Scenario: Cleanup Given the device role "WAN Router" is deleted Given the platform "Cisco IOS 15.6" is deleted Given the manufacturer "Cisco" is deleted + Given the cluster "aws-us-east-1" is deleted - Given the site "undefined" is deleted + Given the device "undefined" is deleted Given the device type "undefined" is deleted Given the device role "undefined" is deleted Given the site "undefined" is deleted Given the platform "undefined" is deleted - Given the manufacturer "undefined" is deleted + Given the cluster type "undefined" is deleted + Given the cluster group "undefined" is deleted + + Given the site "NA-NY" is deleted + Given the device type "ISR4321-1" is deleted Given the interface "GigabitEthernet0/0/0" is deleted Given the IP address "192.168.0.1/32" is deleted - Given the prefix "192.168.0.0/32" is deleted + + Given the cluster type "VMWare" is deleted + Given the cluster type "AWS" is deleted + Given the cluster group "North America" is deleted + Given the cluster group "NA" is deleted + + Given the manufacturer "undefined" is deleted Given the tag "tag 100" is deleted diff --git a/tests/features/ingestion_cluster_group_objects.feature b/tests/features/ingestion_cluster_group_objects.feature new file mode 100644 index 00000000..55173661 --- /dev/null +++ b/tests/features/ingestion_cluster_group_objects.feature @@ -0,0 +1,24 @@ +Feature: Tests for ingestion of cluster group + Validate the behavior of the ingestion of cluster group + +@smoke +@ingestion.cluster_group +Scenario: Ingestion of new cluster group + Given a new cluster group "North America" + When the cluster group is ingested + Then the cluster group is created in the database + +@smoke +@ingestion.cluster_group +Scenario: Ingestion of existing cluster group + Given cluster group "North America" already exists in the database + When the cluster group is ingested + Then the cluster group remains the same + + +@smoke +@ingestion.cluster_group +Scenario: Ingestion of cluster group to update its description + Given cluster group "North America" with description "some string" + When the cluster group is ingested with the updates + Then the cluster group is updated in the database diff --git a/tests/features/ingestion_cluster_objects.feature b/tests/features/ingestion_cluster_objects.feature new file mode 100644 index 00000000..56ef7218 --- /dev/null +++ b/tests/features/ingestion_cluster_objects.feature @@ -0,0 +1,24 @@ +Feature: Tests for ingestion of cluster + Validate the behavior of the ingestion of cluster + +@smoke +@ingestion.cluster +Scenario: Ingestion of new cluster + Given a new cluster "aws-us-east-1" + When the cluster is ingested + Then the cluster, "undefined" group, "undefined" type and "undefined" site are created in the database + +@smoke +@ingestion.cluster +Scenario: Ingestion of existing cluster + Given cluster "aws-us-east-1" already exists in the database + When the cluster is ingested + Then the cluster remains the same + +@smoke +@ingestion.cluster +Scenario: Ingestion of cluster object to update the group, type site and description + Given cluster "aws-us-east-1" with group "NA", type "AWS", site "NA-NY" and description "some string" + Then check if the group "NA", type "AWS" and site "NA-NY" exist in the database and remove them + When the cluster object is ingested with the updates + Then the group "NA", type "AWS" and site "NA-NY" are created and the cluster updated diff --git a/tests/features/ingestion_cluster_type_objects.feature b/tests/features/ingestion_cluster_type_objects.feature new file mode 100644 index 00000000..2d1df030 --- /dev/null +++ b/tests/features/ingestion_cluster_type_objects.feature @@ -0,0 +1,24 @@ +Feature: Tests for ingestion of cluster type + Validate the behavior of the ingestion of cluster type + +@smoke +@ingestion.cluster_type +Scenario: Ingestion of new cluster type + Given a new cluster type "VMWare" + When the cluster type is ingested + Then the cluster type is created in the database + +@smoke +@ingestion.cluster_type +Scenario: Ingestion of existing cluster type + Given cluster type "VMWare" already exists in the database + When the cluster type is ingested + Then the cluster type remains the same + + +@smoke +@ingestion.cluster_type +Scenario: Ingestion of cluster type to update its description + Given cluster type "VMWare" with description "some string" + When the cluster type is ingested with the updates + Then the cluster type is updated in the database diff --git a/tests/features/steps/cleanup.py b/tests/features/steps/cleanup.py index d72a7ecd..7532eafc 100644 --- a/tests/features/steps/cleanup.py +++ b/tests/features/steps/cleanup.py @@ -84,6 +84,33 @@ def delete_prefix(context, prefix): send_delete_request(endpoint, ip.get("id")) +@given('the cluster type "{cluster_type_name}" is deleted') +def delete_cluster_type(context, cluster_type_name): + """Delete the cluster type""" + endpoint = "virtualization/cluster-types/" + cluster_type = get_object_by_name(cluster_type_name, endpoint) + if cluster_type: + send_delete_request(endpoint, cluster_type.get("id")) + + +@given('the cluster group "{cluster_group_name}" is deleted') +def delete_cluster_type(context, cluster_group_name): + """Delete the cluster group""" + endpoint = "virtualization/cluster-groups/" + cluster_group = get_object_by_name(cluster_group_name, endpoint) + if cluster_group: + send_delete_request(endpoint, cluster_group.get("id")) + + +@given('the cluster "{cluster_name}" is deleted') +def delete_cluster_type(context, cluster_name): + """Delete the cluster group""" + endpoint = "virtualization/clusters/" + cluster = get_object_by_name(cluster_name, endpoint) + if cluster: + send_delete_request(endpoint, cluster.get("id")) + + @given('the tag "{tag}" is deleted') def delete_tag(context, tag): """Delete the tag""" diff --git a/tests/features/steps/ingestion_cluster_group_object.py b/tests/features/steps/ingestion_cluster_group_object.py new file mode 100644 index 00000000..e76205e6 --- /dev/null +++ b/tests/features/steps/ingestion_cluster_group_object.py @@ -0,0 +1,78 @@ +import time + +from behave import given, when, then +from netboxlabs.diode.sdk.ingester import Entity, ClusterGroup +from steps.utils import get_object_by_name, ingester + +endpoint = "virtualization/cluster-groups/" + + +@given('a new cluster group "{cluster_group_name}"') +def step_create_new_cluster_group(context, cluster_group_name): + """Set the body of the request to create a new cluster group.""" + context.cluster_group_name = cluster_group_name + + +@when("the cluster group is ingested") +def ingest_cluster_group(context): + """Ingest the cluster group using the Diode SDK""" + + entities = [ + Entity(cluster_group=ClusterGroup(name=context.cluster_group_name)), + ] + + context.response = ingester(entities) + return context.response + + +@then("the cluster group is created in the database") +@then("the cluster group remains the same") +def check_cluster_group_(context): + """Check if the response is not None and the is created in the database.""" + time.sleep(3) + assert context.response is not None + cluster_group = get_object_by_name(context.cluster_group_name, endpoint) + assert cluster_group.get("name") == context.cluster_group_name + assert cluster_group.get("slug") == "north-america" + + +@given('cluster group "{cluster_group_name}" already exists in the database') +def retrieve_existing_cluster_group(context, cluster_group_name): + """Retrieve the cluster group from the database""" + context.cluster_group_name = cluster_group_name + cluster_group = get_object_by_name(context.cluster_group_name, endpoint) + context.cluster_group_name = cluster_group.get("name") + + +@given('cluster group "{cluster_group_name}" with description "{description}"') +def create_cluster_group_to_update(context, cluster_group_name, description): + """Create a cluster group with a description to update""" + context.cluster_group_name = cluster_group_name + context.description = description + + +@when("the cluster group is ingested with the updates") +def ingest_to_update_cluster_group(context): + """Update the cluster group using the Diode SDK""" + + entities = [ + Entity( + cluster_group=ClusterGroup( + name=context.cluster_group_name, + description=context.description, + ) + ), + ] + + context.response = ingester(entities) + return context.response + + +@then("the cluster group is updated in the database") +def check_cluster_group_updated(context): + """Check if the response is not None and the is updated in the database.""" + time.sleep(3) + assert context.response is not None + cluster_group = get_object_by_name(context.cluster_group_name, endpoint) + assert cluster_group.get("name") == context.cluster_group_name + assert cluster_group.get("description") == context.description diff --git a/tests/features/steps/ingestion_cluster_object.py b/tests/features/steps/ingestion_cluster_object.py new file mode 100644 index 00000000..279c179f --- /dev/null +++ b/tests/features/steps/ingestion_cluster_object.py @@ -0,0 +1,160 @@ +import time + +from behave import given, when, then +from netboxlabs.diode.sdk.ingester import ( + Cluster, + Entity, + ClusterGroup, + ClusterType, + Site, +) +from steps.utils import ( + get_object_by_name, + get_object_state, + send_delete_request, + ingester, +) + +endpoint = "virtualization/clusters/" + + +@given('a new cluster "{cluster_name}"') +def step_create_new_cluster(context, cluster_name): + """Set the body of the request to create a new cluster.""" + context.cluster_name = cluster_name + + +@when("the cluster is ingested") +def ingest_cluster(context): + """Ingest the cluster object using the Diode SDK""" + + entities = [ + Entity(cluster=Cluster(name=context.cluster_name)), + ] + + context.response = ingester(entities) + return context.response + + +@then( + 'the cluster, "{group_name}" group, "{type_name}" type and "{site_name}" site are created in the database' +) +def check_cluster_groups_and_types(context, group_name, type_name, site_name): + """Check if the response is not None and the object is created in the database.""" + time.sleep(3) + assert context.response is not None + cluster = get_object_by_name(context.cluster_name, endpoint) + cluster_group = get_object_by_name(group_name, "virtualization/cluster-groups/") + cluster_type = get_object_by_name(type_name, "virtualization/cluster-types/") + site = get_object_by_name(site_name, "dcim/sites/") + assert cluster.get("name") == context.cluster_name + assert cluster_group.get("name") == group_name + assert cluster_type.get("name") == type_name + assert site.get("name") == site_name + + +@then("the cluster remains the same") +def check_cluster_object(context): + """Check if the response is not None and the object is created in the database.""" + time.sleep(3) + assert context.response is not None + cluster = get_object_by_name(context.cluster_name, endpoint) + assert cluster.get("name") == context.cluster_name + + +@given('cluster "{cluster_name}" already exists in the database') +def retrieve_existing_cluster(context, cluster_name): + """Retrieve the cluster object from the database""" + time.sleep(3) + context.cluster_name = cluster_name + cluster = get_object_by_name(context.cluster_name, endpoint) + context.cluster_name = cluster.get("name") + + +@given( + 'cluster "{cluster_name}" with group "{group_name}", type "{type_name}", ' + 'site "{site_name}" and description "{description}"' +) +def create_cluster_to_update( + context, cluster_name, group_name, type_name, site_name, description +): + """Create a cluster object with a description to update""" + context.cluster_name = cluster_name + context.group_name = group_name + context.type_name = type_name + context.site_name = site_name + context.description = description + + +@then( + 'check if the group "{group_name}", type "{type_name}" and site "{site_name}" ' + "exist in the database and remove them" +) +def remove_cluster_objects(context, group_name, type_name, site_name): + time.sleep(3) + cluster_group = get_object_by_name(group_name, "virtualization/cluster-groups/") + if cluster_group is not None: + assert cluster_group.get("name") == group_name + send_delete_request("virtualization/cluster-groups/", cluster_group.get("id")) + + cluster_type = get_object_by_name(type_name, "virtualization/cluster-types/") + if cluster_type is not None: + assert cluster_type.get("name") == type_name + send_delete_request("virtualization/cluster-types/", cluster_type.get("id")) + + site = get_object_by_name(site_name, "dcim/sites/") + if site is not None: + assert site.get("name") == site_name + send_delete_request("dcim/sites/", site.get("id")) + + +@when("the cluster object is ingested with the updates") +def ingest_to_update_cluster(context): + """Update the object using the Diode SDK""" + + entities = [ + Entity( + cluster=Cluster( + name=context.cluster_name, + group=ClusterGroup(name=context.group_name), + type=ClusterType(name=context.type_name), + site=Site(name=context.site_name), + description=context.description, + ), + ), + ] + + context.response = ingester(entities) + return context.response + + +@then( + 'the group "{group_name}", type "{type_name}" and site "{site_name}" are created and the cluster updated' +) +def check_updated_cluster_objects(context, group_name, type_name, site_name): + """Check if the response is not None and the object is updated in the database and cluster objects created.""" + time.sleep(3) + assert context.response is not None + + cluster_group = get_object_by_name(group_name, "virtualization/cluster-groups/") + assert cluster_group.get("name") == group_name + + cluster_type = get_object_by_name(type_name, "virtualization/cluster-types/") + assert cluster_type.get("name") == type_name + + site = get_object_by_name(site_name, "dcim/sites/") + assert site.get("name") == site_name + + params = { + "object_type": "virtualization.cluster", + "q": context.cluster_name, + "site__name": context.site_name, + } + + cluster = get_object_state(params) + + assert cluster.get("name") == context.cluster_name + assert cluster.get("group").get("name") == cluster_group.get("name") + assert cluster.get("type").get("name") == cluster_type.get("name") + assert cluster.get("site").get("name") == site.get("name") + assert cluster.get("description") == context.description diff --git a/tests/features/steps/ingestion_cluster_type_object.py b/tests/features/steps/ingestion_cluster_type_object.py new file mode 100644 index 00000000..caa5b3e6 --- /dev/null +++ b/tests/features/steps/ingestion_cluster_type_object.py @@ -0,0 +1,78 @@ +import time + +from behave import given, when, then +from netboxlabs.diode.sdk.ingester import Entity, ClusterType +from steps.utils import get_object_by_name, ingester + +endpoint = "virtualization/cluster-types/" + + +@given('a new cluster type "{cluster_type_name}"') +def step_create_new_cluster_type(context, cluster_type_name): + """Set the body of the request to create a new cluster type.""" + context.cluster_type_name = cluster_type_name + + +@when("the cluster type is ingested") +def ingest_cluster_type(context): + """Ingest the cluster type using the Diode SDK""" + + entities = [ + Entity(cluster_type=ClusterType(name=context.cluster_type_name)), + ] + + context.response = ingester(entities) + return context.response + + +@then("the cluster type is created in the database") +@then("the cluster type remains the same") +def check_cluster_type_(context): + """Check if the response is not None and the is created in the database.""" + time.sleep(3) + assert context.response is not None + cluster_type = get_object_by_name(context.cluster_type_name, endpoint) + assert cluster_type.get("name") == context.cluster_type_name + assert cluster_type.get("slug") == "vmware" + + +@given('cluster type "{cluster_type_name}" already exists in the database') +def retrieve_existing_cluster_type(context, cluster_type_name): + """Retrieve the cluster type from the database""" + context.cluster_type_name = cluster_type_name + cluster_type = get_object_by_name(context.cluster_type_name, endpoint) + context.cluster_type_name = cluster_type.get("name") + + +@given('cluster type "{cluster_type_name}" with description "{description}"') +def create_cluster_type_to_update(context, cluster_type_name, description): + """Create a cluster type with a description to update""" + context.cluster_type_name = cluster_type_name + context.description = description + + +@when("the cluster type is ingested with the updates") +def ingest_to_update_cluster_type(context): + """Update the cluster type using the Diode SDK""" + + entities = [ + Entity( + cluster_type=ClusterType( + name=context.cluster_type_name, + description=context.description, + ) + ), + ] + + context.response = ingester(entities) + return context.response + + +@then("the cluster type is updated in the database") +def check_cluster_type_updated(context): + """Check if the response is not None and the is updated in the database.""" + time.sleep(3) + assert context.response is not None + cluster_type = get_object_by_name(context.cluster_type_name, endpoint) + assert cluster_type.get("name") == context.cluster_type_name + assert cluster_type.get("description") == context.description diff --git a/tests/features/steps/ingestion_ip_address_object.py b/tests/features/steps/ingestion_ip_address_object.py index ae6af335..27f67142 100644 --- a/tests/features/steps/ingestion_ip_address_object.py +++ b/tests/features/steps/ingestion_ip_address_object.py @@ -1,3 +1,4 @@ +import time from behave import given, when, then from netboxlabs.diode.sdk.ingester import Entity, Interface, IPAddress from steps.utils import ( @@ -66,6 +67,8 @@ def assert_ip_address_exists(context): if hasattr(context, "description"): params["description"] = context.description + time.sleep(1) + ip_address = get_object_state(params) assert ip_address.get("address") == context.ip_address From 07d1808bb6c1d833aa7d72f24e0559adf7d22b89 Mon Sep 17 00:00:00 2001 From: Leonardo Parente <23251360+leoparente@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:38:38 -0300 Subject: [PATCH 13/13] feat: add integration test for Virtual Machine Objects (#162) --- diode-server/netbox/virtualization.go | 9 +- .../netbox/virtualization_wrappers.go | 122 +-- .../changeset/changeset_virt_test.go | 777 ++++++++---------- .../ingestion_virtual_disk_objects.feature | 24 + ...ngestion_virtual_interface_objects.feature | 24 + .../ingestion_virtual_machine_objects.feature | 50 ++ .../steps/ingestion_virtual_disk_object.py | 176 ++++ .../ingestion_virtual_interface_object.py | 173 ++++ .../steps/ingestion_virtual_machine_object.py | 206 +++++ 9 files changed, 1085 insertions(+), 476 deletions(-) create mode 100644 tests/features/ingestion_virtual_disk_objects.feature create mode 100644 tests/features/ingestion_virtual_interface_objects.feature create mode 100644 tests/features/ingestion_virtual_machine_objects.feature create mode 100644 tests/features/steps/ingestion_virtual_disk_object.py create mode 100644 tests/features/steps/ingestion_virtual_interface_object.py create mode 100644 tests/features/steps/ingestion_virtual_machine_object.py diff --git a/diode-server/netbox/virtualization.go b/diode-server/netbox/virtualization.go index 15018f9b..e13c0fb9 100644 --- a/diode-server/netbox/virtualization.go +++ b/diode-server/netbox/virtualization.go @@ -164,11 +164,10 @@ func NewVirtualizationCluster() *VirtualizationCluster { func NewVirtualizationVirtualMachine() *VirtualizationVirtualMachine { status := "active" return &VirtualizationVirtualMachine{ - Name: "undefined", - Status: &status, - Site: NewDcimSite(), - Cluster: NewVirtualizationCluster(), - Role: NewDcimDeviceRole(), + Name: "undefined", + Status: &status, + Site: NewDcimSite(), + Role: NewDcimDeviceRole(), } } diff --git a/diode-server/netbox/virtualization_wrappers.go b/diode-server/netbox/virtualization_wrappers.go index 019aa0e7..74e52f60 100644 --- a/diode-server/netbox/virtualization_wrappers.go +++ b/diode-server/netbox/virtualization_wrappers.go @@ -715,16 +715,18 @@ func (vw *VirtualizationVirtualMachineDataWrapper) NestedObjects() ([]Comparable vw.VirtualMachine.PrimaryIPv6 = nil vw.VirtualMachine.Device = nil - cluster := VirtualizationClusterDataWrapper{Cluster: vw.VirtualMachine.Cluster, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} + if vw.VirtualMachine.Cluster != nil { + cluster := VirtualizationClusterDataWrapper{Cluster: vw.VirtualMachine.Cluster, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} - co, err := cluster.NestedObjects() - if err != nil { - return nil, err - } + co, err := cluster.NestedObjects() + if err != nil { + return nil, err + } - objects = append(objects, co...) + objects = append(objects, co...) - vw.VirtualMachine.Cluster = cluster.Cluster + vw.VirtualMachine.Cluster = cluster.Cluster + } site := DcimSiteDataWrapper{Site: vw.VirtualMachine.Site, BaseDataWrapper: BaseDataWrapper{placeholder: vw.placeholder, hasParent: true, intended: vw.intended}} @@ -885,40 +887,50 @@ func (vw *VirtualizationVirtualMachineDataWrapper) Patch(cmp ComparableData, int vw.objectsToReconcile = append(vw.objectsToReconcile, siteObjectsToReconcile...) - if actualCluster.IsPlaceholder() && intended.VirtualMachine.Cluster != nil { - intendedCluster = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.VirtualMachine.Cluster)) - } - - clusterObjectsToReconcile, clusterErr := actualCluster.Patch(intendedCluster, intendedNestedObjects) - if clusterErr != nil { - return nil, clusterErr - } + if actualCluster != nil { + if actualCluster.IsPlaceholder() && intended.VirtualMachine.Cluster != nil { + intendedCluster = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.VirtualMachine.Cluster)) + } - cluster, err := copyData(actualCluster.Data().(*VirtualizationCluster)) - if err != nil { - return nil, err - } - cluster.Tags = nil + clusterObjectsToReconcile, clusterErr := actualCluster.Patch(intendedCluster, intendedNestedObjects) + if clusterErr != nil { + return nil, clusterErr + } - if !actualCluster.HasChanged() { - cluster = &VirtualizationCluster{ - ID: actualCluster.ID(), + cluster, err := copyData(actualCluster.Data().(*VirtualizationCluster)) + if err != nil { + return nil, err } + cluster.Tags = nil + + if !actualCluster.HasChanged() { + cluster = &VirtualizationCluster{ + ID: actualCluster.ID(), + } + + intendedClusterID := intendedCluster.ID() + if intended.VirtualMachine.Cluster != nil { + intendedClusterID = intended.VirtualMachine.Cluster.ID + } - intendedClusterID := intendedCluster.ID() - if intended.VirtualMachine.Cluster != nil { - intendedClusterID = intended.VirtualMachine.Cluster.ID + intended.VirtualMachine.Cluster = &VirtualizationCluster{ + ID: intendedClusterID, + } } + vw.VirtualMachine.Cluster = cluster + + vw.objectsToReconcile = append(vw.objectsToReconcile, clusterObjectsToReconcile...) + } else if intended.VirtualMachine.Cluster != nil { + clusterID := intended.VirtualMachine.Cluster.ID + vw.VirtualMachine.Cluster = &VirtualizationCluster{ + ID: clusterID, + } intended.VirtualMachine.Cluster = &VirtualizationCluster{ - ID: intendedClusterID, + ID: clusterID, } } - vw.VirtualMachine.Cluster = cluster - - vw.objectsToReconcile = append(vw.objectsToReconcile, clusterObjectsToReconcile...) - if actualRole.IsPlaceholder() && intended.VirtualMachine.Role != nil { intendedRole = extractFromObjectsMap(currentNestedObjectsMap, fmt.Sprintf("%p", intended.VirtualMachine.Role)) } @@ -1023,15 +1035,13 @@ func (vw *VirtualizationVirtualMachineDataWrapper) Patch(cmp ComparableData, int vw.VirtualMachine.Platform = platform vw.objectsToReconcile = append(vw.objectsToReconcile, platformObjectsToReconcile...) - } else { - if intended.VirtualMachine.Platform != nil { - platformID := intended.VirtualMachine.Platform.ID - vw.VirtualMachine.Platform = &DcimPlatform{ - ID: platformID, - } - intended.VirtualMachine.Platform = &DcimPlatform{ - ID: platformID, - } + } else if intended.VirtualMachine.Platform != nil { + platformID := intended.VirtualMachine.Platform.ID + vw.VirtualMachine.Platform = &DcimPlatform{ + ID: platformID, + } + intended.VirtualMachine.Platform = &DcimPlatform{ + ID: platformID, } } @@ -1089,25 +1099,27 @@ func (vw *VirtualizationVirtualMachineDataWrapper) Patch(cmp ComparableData, int vw.objectsToReconcile = append(vw.objectsToReconcile, siteObjectsToReconcile...) - clusterObjectsToReconcile, clusterErr := actualCluster.Patch(intendedCluster, intendedNestedObjects) - if clusterErr != nil { - return nil, clusterErr - } + if actualCluster != nil { + clusterObjectsToReconcile, clusterErr := actualCluster.Patch(intendedCluster, intendedNestedObjects) + if clusterErr != nil { + return nil, clusterErr + } - cluster, err := copyData(actualCluster.Data().(*VirtualizationCluster)) - if err != nil { - return nil, err - } - cluster.Tags = nil + cluster, err := copyData(actualCluster.Data().(*VirtualizationCluster)) + if err != nil { + return nil, err + } + cluster.Tags = nil - if !actualCluster.HasChanged() { - cluster = &VirtualizationCluster{ - ID: actualCluster.ID(), + if !actualCluster.HasChanged() { + cluster = &VirtualizationCluster{ + ID: actualCluster.ID(), + } } - } - vw.VirtualMachine.Cluster = cluster + vw.VirtualMachine.Cluster = cluster - vw.objectsToReconcile = append(vw.objectsToReconcile, clusterObjectsToReconcile...) + vw.objectsToReconcile = append(vw.objectsToReconcile, clusterObjectsToReconcile...) + } roleObjectsToReconcile, roleErr := actualRole.Patch(intendedRole, intendedNestedObjects) if roleErr != nil { diff --git a/diode-server/reconciler/changeset/changeset_virt_test.go b/diode-server/reconciler/changeset/changeset_virt_test.go index b3b5af57..22fe3702 100644 --- a/diode-server/reconciler/changeset/changeset_virt_test.go +++ b/diode-server/reconciler/changeset/changeset_virt_test.go @@ -401,6 +401,83 @@ func TestVirtualizationPrepare(t *testing.T) { }, "state": 0 }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.virtualmachine", + objectID: 0, + queryParams: map[string]string{"q": "Test", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: nil, + }, + }, + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.virtualmachine", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationVirtualMachine{ + Name: "Test", + Role: &netbox.DcimDeviceRole{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "[P4] ingest virtualization.virtualmachine with name and cluster - existing objects not found - create", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.virtualmachine", + "entity": { + "VirtualMachine": { + "name": "Test", + "cluster": { + "name": "Cluster-1" + } + } + }, + "state": 0 + }`), retrieveObjectStates: []mockRetrieveObjectState{ { objectType: "virtualization.virtualmachine", @@ -414,7 +491,7 @@ func TestVirtualizationPrepare(t *testing.T) { { objectType: "virtualization.cluster", objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + queryParams: map[string]string{"q": "Cluster-1", "site__name": "undefined"}, objectChangeID: 0, object: &netbox.VirtualizationClusterDataWrapper{ Cluster: nil, @@ -485,7 +562,7 @@ func TestVirtualizationPrepare(t *testing.T) { ObjectID: nil, ObjectVersion: nil, Data: &netbox.VirtualizationCluster{ - Name: "undefined", + Name: "Cluster-1", Group: &netbox.VirtualizationClusterGroup{ ID: 1, }, @@ -495,7 +572,6 @@ func TestVirtualizationPrepare(t *testing.T) { Site: &netbox.DcimSite{ ID: 1, }, - Status: strPtr(netbox.DefaultVirtualizationStatus), }, }, { @@ -507,7 +583,7 @@ func TestVirtualizationPrepare(t *testing.T) { Data: &netbox.VirtualizationVirtualMachine{ Name: "Test", Cluster: &netbox.VirtualizationCluster{ - Name: "undefined", + Name: "Cluster-1", Group: &netbox.VirtualizationClusterGroup{ ID: 1, }, @@ -517,7 +593,6 @@ func TestVirtualizationPrepare(t *testing.T) { Site: &netbox.DcimSite{ ID: 1, }, - Status: strPtr(netbox.DefaultVirtualizationStatus), }, Role: &netbox.DcimDeviceRole{ ID: 1, @@ -532,7 +607,7 @@ func TestVirtualizationPrepare(t *testing.T) { wantErr: false, }, { - name: "[P4] ingest virtualization.virtualmachine with name only - existing object found - do nothing", + name: "[P4] ingest virtualization.virtualmachine with name and existing cluster - existing vm not found - create", rawIngestEntity: []byte(`{ "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", "data_type": "virtualization.virtualmachine", @@ -540,7 +615,7 @@ func TestVirtualizationPrepare(t *testing.T) { "VirtualMachine": { "name": "Test", "cluster": { - "name": "cluster1" + "name": "Cluster-2" } } }, @@ -553,42 +628,18 @@ func TestVirtualizationPrepare(t *testing.T) { queryParams: map[string]string{"q": "Test", "site__name": "undefined"}, objectChangeID: 0, object: &netbox.VirtualizationVirtualMachineDataWrapper{ - VirtualMachine: &netbox.VirtualizationVirtualMachine{ - ID: 1, - Name: "Test", - Site: &netbox.DcimSite{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - Cluster: &netbox.VirtualizationCluster{ - ID: 1, - Name: "cluster1", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, + VirtualMachine: nil, }, }, { objectType: "virtualization.cluster", objectID: 0, - queryParams: map[string]string{"q": "cluster1", "site__name": "undefined"}, + queryParams: map[string]string{"q": "Cluster-2", "site__name": "undefined"}, objectChangeID: 0, object: &netbox.VirtualizationClusterDataWrapper{ Cluster: &netbox.VirtualizationCluster{ ID: 1, - Name: "cluster1", + Name: "Cluster-2", Group: &netbox.VirtualizationClusterGroup{ ID: 1, Name: "undefined", @@ -599,6 +650,12 @@ func TestVirtualizationPrepare(t *testing.T) { Name: "undefined", Slug: "undefined", }, + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, }, }, }, @@ -628,20 +685,6 @@ func TestVirtualizationPrepare(t *testing.T) { }, }, }, - { - objectType: "dcim.devicerole", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceRoleDataWrapper{ - DeviceRole: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - }, - }, { objectType: "dcim.site", objectID: 0, @@ -673,62 +716,70 @@ func TestVirtualizationPrepare(t *testing.T) { }, wantChangeSet: changeset.ChangeSet{ ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{}, + ChangeSet: []changeset.Change{ + + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.virtualmachine", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationVirtualMachine{ + Name: "Test", + Cluster: &netbox.VirtualizationCluster{ + ID: 1, + }, + Role: &netbox.DcimDeviceRole{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + }, + }, + }, }, wantErr: false, }, { - name: "[P4] ingest empty virtualization.virtualmachine - error", + name: "[P4] ingest virtualization.virtualmachine with name and cluster - existing vm found - create cluster", rawIngestEntity: []byte(`{ "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", "data_type": "virtualization.virtualmachine", "entity": { - "VirtualMachine": {} - }, - "state": 0 - }`), - retrieveObjectStates: []mockRetrieveObjectState{}, - wantChangeSet: changeset.ChangeSet{ - ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{}, - }, - wantErr: true, - }, - { - name: "[P5] ingest virtualization.vminterface with name only - existing object not found - create", - rawIngestEntity: []byte(`{ - "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "virtualization.vminterface", - "entity": { - "VMInterface": { - "name": "Test" + "VirtualMachine": { + "name": "Test", + "cluster": { + "name": "Cluster-3" + } } }, "state": 0 }`), retrieveObjectStates: []mockRetrieveObjectState{ - { - objectType: "virtualization.vminterface", - objectID: 0, - queryParams: map[string]string{"q": "Test", "virtual_machine__name": "undefined", "virtual_machine__site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.VirtualizationVMInterfaceDataWrapper{ - VMInterface: nil, - }, - }, { objectType: "virtualization.virtualmachine", objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + queryParams: map[string]string{"q": "Test", "site__name": "undefined"}, objectChangeID: 0, object: &netbox.VirtualizationVirtualMachineDataWrapper{ - VirtualMachine: nil, + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + ID: 1, + Name: "Test", + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, }, }, { objectType: "virtualization.cluster", objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + queryParams: map[string]string{"q": "Cluster-3", "site__name": "undefined"}, objectChangeID: 0, object: &netbox.VirtualizationClusterDataWrapper{ Cluster: nil, @@ -799,7 +850,7 @@ func TestVirtualizationPrepare(t *testing.T) { ObjectID: nil, ObjectVersion: nil, Data: &netbox.VirtualizationCluster{ - Name: "undefined", + Name: "Cluster-3", Group: &netbox.VirtualizationClusterGroup{ ID: 1, }, @@ -809,19 +860,19 @@ func TestVirtualizationPrepare(t *testing.T) { Site: &netbox.DcimSite{ ID: 1, }, - Status: strPtr(netbox.DefaultVirtualizationStatus), }, }, { ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, + ChangeType: changeset.ChangeTypeUpdate, ObjectType: "virtualization.virtualmachine", - ObjectID: nil, + ObjectID: intPtr(1), ObjectVersion: nil, Data: &netbox.VirtualizationVirtualMachine{ - Name: "undefined", + ID: 1, + Name: "Test", Cluster: &netbox.VirtualizationCluster{ - Name: "undefined", + Name: "Cluster-3", Group: &netbox.VirtualizationClusterGroup{ ID: 1, }, @@ -831,7 +882,6 @@ func TestVirtualizationPrepare(t *testing.T) { Site: &netbox.DcimSite{ ID: 1, }, - Status: strPtr(netbox.DefaultVirtualizationStatus), }, Role: &netbox.DcimDeviceRole{ ID: 1, @@ -842,100 +892,44 @@ func TestVirtualizationPrepare(t *testing.T) { Status: strPtr(netbox.DefaultVirtualizationStatus), }, }, - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "virtualization.vminterface", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.VirtualizationVMInterface{ - Name: "Test", - VirtualMachine: &netbox.VirtualizationVirtualMachine{ - Name: "undefined", - Cluster: &netbox.VirtualizationCluster{ - Name: "undefined", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - }, - Site: &netbox.DcimSite{ - ID: 1, - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, - Role: &netbox.DcimDeviceRole{ - ID: 1, - }, - Site: &netbox.DcimSite{ - ID: 1, - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, - }, - }, }, }, wantErr: false, }, { - name: "[P5] ingest virtualization.vminterface with name only - existing object found - do nothing", + name: "[P4] ingest virtualization.virtualmachine with name only - existing object found - do nothing", rawIngestEntity: []byte(`{ "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "virtualization.vminterface", + "data_type": "virtualization.virtualmachine", "entity": { - "VMInterface": { - "name": "Test" + "VirtualMachine": { + "name": "Test", + "cluster": { + "name": "cluster1" + } } }, "state": 0 }`), retrieveObjectStates: []mockRetrieveObjectState{ { - objectType: "virtualization.vminterface", + objectType: "virtualization.virtualmachine", objectID: 0, - queryParams: map[string]string{"q": "Test", "virtual_machine__name": "undefined", "virtual_machine__site__name": "undefined"}, + queryParams: map[string]string{"q": "Test", "site__name": "undefined"}, objectChangeID: 0, - object: &netbox.VirtualizationVMInterfaceDataWrapper{ - VMInterface: &netbox.VirtualizationVMInterface{ + object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: &netbox.VirtualizationVirtualMachine{ ID: 1, Name: "Test", - VirtualMachine: &netbox.VirtualizationVirtualMachine{ - ID: 1, - Name: "undefined", - Cluster: &netbox.VirtualizationCluster{ - ID: 1, - Name: "undefined", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), }, - }, - }, - }, - { - objectType: "virtualization.virtualmachine", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.VirtualizationVirtualMachineDataWrapper{ - VirtualMachine: &netbox.VirtualizationVirtualMachine{ - ID: 1, - Name: "undefined", Cluster: &netbox.VirtualizationCluster{ ID: 1, - Name: "undefined", + Name: "cluster1", Group: &netbox.VirtualizationClusterGroup{ ID: 1, Name: "undefined", @@ -946,20 +940,20 @@ func TestVirtualizationPrepare(t *testing.T) { Name: "undefined", Slug: "undefined", }, - Status: strPtr(netbox.DefaultVirtualizationStatus), }, + Status: strPtr(netbox.DefaultVirtualizationStatus), }, }, }, { objectType: "virtualization.cluster", objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + queryParams: map[string]string{"q": "cluster1", "site__name": "undefined"}, objectChangeID: 0, object: &netbox.VirtualizationClusterDataWrapper{ Cluster: &netbox.VirtualizationCluster{ ID: 1, - Name: "undefined", + Name: "cluster1", Group: &netbox.VirtualizationClusterGroup{ ID: 1, Name: "undefined", @@ -1049,12 +1043,12 @@ func TestVirtualizationPrepare(t *testing.T) { wantErr: false, }, { - name: "[P5] ingest empty virtualization.vminterface - error", + name: "[P4] ingest empty virtualization.virtualmachine - error", rawIngestEntity: []byte(`{ "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "virtualization.vminterface", + "data_type": "virtualization.virtualmachine", "entity": { - "VMInterface": {} + "VirtualMachine": {} }, "state": 0 }`), @@ -1066,12 +1060,12 @@ func TestVirtualizationPrepare(t *testing.T) { wantErr: true, }, { - name: "[P6] ingest virtualization.virtualdisk with name only - existing object not found - create", + name: "[P5] ingest virtualization.vminterface with name only - existing object not found - create", rawIngestEntity: []byte(`{ "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", - "data_type": "virtualization.virtualdisk", + "data_type": "virtualization.vminterface", "entity": { - "VirtualDisk": { + "VMInterface": { "name": "Test" } }, @@ -1079,12 +1073,12 @@ func TestVirtualizationPrepare(t *testing.T) { }`), retrieveObjectStates: []mockRetrieveObjectState{ { - objectType: "virtualization.virtualdisk", + objectType: "virtualization.vminterface", objectID: 0, queryParams: map[string]string{"q": "Test", "virtual_machine__name": "undefined", "virtual_machine__site__name": "undefined"}, objectChangeID: 0, - object: &netbox.VirtualizationVirtualDiskDataWrapper{ - VirtualDisk: nil, + object: &netbox.VirtualizationVMInterfaceDataWrapper{ + VMInterface: nil, }, }, { @@ -1097,37 +1091,131 @@ func TestVirtualizationPrepare(t *testing.T) { }, }, { - objectType: "virtualization.cluster", + objectType: "dcim.site", objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + queryParams: map[string]string{"q": "undefined"}, objectChangeID: 0, - object: &netbox.VirtualizationClusterDataWrapper{ - Cluster: nil, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, }, }, { - objectType: "virtualization.clustergroup", + objectType: "dcim.devicerole", objectID: 0, queryParams: map[string]string{"q": "undefined"}, objectChangeID: 0, - object: &netbox.VirtualizationClusterGroupDataWrapper{ - ClusterGroup: &netbox.VirtualizationClusterGroup{ - ID: 1, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), + }, + }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.virtualmachine", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationVirtualMachine{ Name: "undefined", - Slug: "undefined", + Role: &netbox.DcimDeviceRole{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + { + ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeType: changeset.ChangeTypeCreate, + ObjectType: "virtualization.vminterface", + ObjectID: nil, + ObjectVersion: nil, + Data: &netbox.VirtualizationVMInterface{ + Name: "Test", + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + Name: "undefined", + Role: &netbox.DcimDeviceRole{ + ID: 1, + }, + Site: &netbox.DcimSite{ + ID: 1, + }, + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, }, }, }, + }, + wantErr: false, + }, + { + name: "[P5] ingest virtualization.vminterface with name only - existing object found - do nothing", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.vminterface", + "entity": { + "VMInterface": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ { - objectType: "virtualization.clustertype", + objectType: "virtualization.vminterface", objectID: 0, - queryParams: map[string]string{"q": "undefined"}, + queryParams: map[string]string{"q": "Test", "virtual_machine__name": "undefined", "virtual_machine__site__name": "undefined"}, objectChangeID: 0, - object: &netbox.VirtualizationClusterTypeDataWrapper{ - ClusterType: &netbox.VirtualizationClusterType{ + object: &netbox.VirtualizationVMInterfaceDataWrapper{ + VMInterface: &netbox.VirtualizationVMInterface{ + ID: 1, + Name: "Test", + VirtualMachine: &netbox.VirtualizationVirtualMachine{ + ID: 1, + Name: "undefined", + Status: strPtr(netbox.DefaultVirtualizationStatus), + }, + }, + }, + }, + { + objectType: "virtualization.virtualmachine", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: &netbox.VirtualizationVirtualMachine{ ID: 1, Name: "undefined", - Slug: "undefined", + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), }, }, }, @@ -1162,27 +1250,90 @@ func TestVirtualizationPrepare(t *testing.T) { }, wantChangeSet: changeset.ChangeSet{ ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeSet: []changeset.Change{ - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "virtualization.cluster", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.VirtualizationCluster{ - Name: "undefined", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - }, - Site: &netbox.DcimSite{ - ID: 1, - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), + ChangeSet: []changeset.Change{}, + }, + wantErr: false, + }, + { + name: "[P5] ingest empty virtualization.vminterface - error", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.vminterface", + "entity": { + "VMInterface": {} + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{}, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{}, + }, + wantErr: true, + }, + { + name: "[P6] ingest virtualization.virtualdisk with name only - existing object not found - create", + rawIngestEntity: []byte(`{ + "request_id": "cfa0f129-125c-440d-9e41-e87583cd7d89", + "data_type": "virtualization.virtualdisk", + "entity": { + "VirtualDisk": { + "name": "Test" + } + }, + "state": 0 + }`), + retrieveObjectStates: []mockRetrieveObjectState{ + { + objectType: "virtualization.virtualdisk", + objectID: 0, + queryParams: map[string]string{"q": "Test", "virtual_machine__name": "undefined", "virtual_machine__site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualDiskDataWrapper{ + VirtualDisk: nil, + }, + }, + { + objectType: "virtualization.virtualmachine", + objectID: 0, + queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, + objectChangeID: 0, + object: &netbox.VirtualizationVirtualMachineDataWrapper{ + VirtualMachine: nil, + }, + }, + { + objectType: "dcim.site", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimSiteDataWrapper{ + Site: &netbox.DcimSite{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), + }, + }, + }, + { + objectType: "dcim.devicerole", + objectID: 0, + queryParams: map[string]string{"q": "undefined"}, + objectChangeID: 0, + object: &netbox.DcimDeviceRoleDataWrapper{ + DeviceRole: &netbox.DcimDeviceRole{ + ID: 1, + Name: "undefined", + Slug: "undefined", + Color: strPtr("000000"), }, }, + }, + }, + wantChangeSet: changeset.ChangeSet{ + ChangeSetID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", + ChangeSet: []changeset.Change{ { ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", ChangeType: changeset.ChangeTypeCreate, @@ -1191,19 +1342,6 @@ func TestVirtualizationPrepare(t *testing.T) { ObjectVersion: nil, Data: &netbox.VirtualizationVirtualMachine{ Name: "undefined", - Cluster: &netbox.VirtualizationCluster{ - Name: "undefined", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - }, - Site: &netbox.DcimSite{ - ID: 1, - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, Role: &netbox.DcimDeviceRole{ ID: 1, }, @@ -1223,19 +1361,6 @@ func TestVirtualizationPrepare(t *testing.T) { Name: "Test", VirtualMachine: &netbox.VirtualizationVirtualMachine{ Name: "undefined", - Cluster: &netbox.VirtualizationCluster{ - Name: "undefined", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - }, - Site: &netbox.DcimSite{ - ID: 1, - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, Role: &netbox.DcimDeviceRole{ ID: 1, }, @@ -1281,41 +1406,6 @@ func TestVirtualizationPrepare(t *testing.T) { VirtualMachine: nil, }, }, - { - objectType: "virtualization.cluster", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.VirtualizationClusterDataWrapper{ - Cluster: nil, - }, - }, - { - objectType: "virtualization.clustergroup", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.VirtualizationClusterGroupDataWrapper{ - ClusterGroup: &netbox.VirtualizationClusterGroup{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - { - objectType: "virtualization.clustertype", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.VirtualizationClusterTypeDataWrapper{ - ClusterType: &netbox.VirtualizationClusterType{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, { objectType: "dcim.site", objectID: 0, @@ -1355,28 +1445,6 @@ func TestVirtualizationPrepare(t *testing.T) { Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), }, }, - { - ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", - ChangeType: changeset.ChangeTypeCreate, - ObjectType: "virtualization.cluster", - ObjectID: nil, - ObjectVersion: nil, - Data: &netbox.VirtualizationCluster{ - Name: "undefined", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - }, - Site: &netbox.DcimSite{ - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, - }, { ChangeID: "5663a77e-9bad-4981-afe9-77d8a9f2b8b5", ChangeType: changeset.ChangeTypeCreate, @@ -1385,21 +1453,6 @@ func TestVirtualizationPrepare(t *testing.T) { ObjectVersion: nil, Data: &netbox.VirtualizationVirtualMachine{ Name: "undefined", - Cluster: &netbox.VirtualizationCluster{ - Name: "undefined", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - }, - Site: &netbox.DcimSite{ - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, Role: &netbox.DcimDeviceRole{ ID: 1, }, @@ -1421,21 +1474,6 @@ func TestVirtualizationPrepare(t *testing.T) { Name: "Test", VirtualMachine: &netbox.VirtualizationVirtualMachine{ Name: "undefined", - Cluster: &netbox.VirtualizationCluster{ - Name: "undefined", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - }, - Site: &netbox.DcimSite{ - Name: "undefined", - Slug: "undefined", - Status: (*netbox.DcimSiteStatus)(strPtr(string(netbox.DcimSiteStatusActive))), - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, Role: &netbox.DcimDeviceRole{ ID: 1, }, @@ -1475,23 +1513,8 @@ func TestVirtualizationPrepare(t *testing.T) { ID: 1, Name: "Test", VirtualMachine: &netbox.VirtualizationVirtualMachine{ - ID: 1, - Name: "undefined", - Cluster: &netbox.VirtualizationCluster{ - ID: 1, - Name: "undefined", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, + ID: 1, + Name: "undefined", Status: strPtr(netbox.DefaultVirtualizationStatus), }, }, @@ -1504,90 +1527,12 @@ func TestVirtualizationPrepare(t *testing.T) { objectChangeID: 0, object: &netbox.VirtualizationVirtualMachineDataWrapper{ VirtualMachine: &netbox.VirtualizationVirtualMachine{ - ID: 1, - Name: "undefined", - Cluster: &netbox.VirtualizationCluster{ - ID: 1, - Name: "undefined", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, - Status: strPtr(netbox.DefaultVirtualizationStatus), - }, - }, - }, - { - objectType: "virtualization.cluster", - objectID: 0, - queryParams: map[string]string{"q": "undefined", "site__name": "undefined"}, - objectChangeID: 0, - object: &netbox.VirtualizationClusterDataWrapper{ - Cluster: &netbox.VirtualizationCluster{ - ID: 1, - Name: "undefined", - Group: &netbox.VirtualizationClusterGroup{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - Type: &netbox.VirtualizationClusterType{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, + ID: 1, + Name: "undefined", Status: strPtr(netbox.DefaultVirtualizationStatus), }, }, }, - { - objectType: "virtualization.clustergroup", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.VirtualizationClusterGroupDataWrapper{ - ClusterGroup: &netbox.VirtualizationClusterGroup{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - { - objectType: "virtualization.clustertype", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.VirtualizationClusterTypeDataWrapper{ - ClusterType: &netbox.VirtualizationClusterType{ - ID: 1, - Name: "undefined", - Slug: "undefined", - }, - }, - }, - { - objectType: "dcim.devicerole", - objectID: 0, - queryParams: map[string]string{"q": "undefined"}, - objectChangeID: 0, - object: &netbox.DcimDeviceRoleDataWrapper{ - DeviceRole: &netbox.DcimDeviceRole{ - ID: 1, - Name: "undefined", - Slug: "undefined", - Color: strPtr("000000"), - }, - }, - }, { objectType: "dcim.site", objectID: 0, diff --git a/tests/features/ingestion_virtual_disk_objects.feature b/tests/features/ingestion_virtual_disk_objects.feature new file mode 100644 index 00000000..f15eee6d --- /dev/null +++ b/tests/features/ingestion_virtual_disk_objects.feature @@ -0,0 +1,24 @@ +Feature: Tests for ingestion of virtual disk + Validate the behavior of the ingestion of virtual disk + +@smoke +@ingestion.virtual_disk +Scenario: Ingestion of new virtual disk + Given a new virtual disk "Disk-1" with size "1234" + When the virtual disk is ingested + Then the virtual disk and "undefined" virtual machine with "undefined" site are created in the database + +@smoke +@ingestion.virtual_disk +Scenario: Ingestion of existing virtual disk + Given virtual disk "Disk-1" with "undefined" virtual machine and "undefined" site already exists in the database + When the virtual disk is ingested + Then the virtual disk remains the same + +@smoke +@ingestion.virtual_disk +Scenario: Ingestion of virtual disk object to update the virtual machine, description and size + Given virtual disk "Disk-1" with virtual machine "VM-2", description "some string" and size "15232" + Then check if the virtual machine "VM-2" related to disk exists in the database and remove it + When the virtual disk object is ingested with the updates + Then the virtual machine "VM-2" is created and the disk updated diff --git a/tests/features/ingestion_virtual_interface_objects.feature b/tests/features/ingestion_virtual_interface_objects.feature new file mode 100644 index 00000000..0436e495 --- /dev/null +++ b/tests/features/ingestion_virtual_interface_objects.feature @@ -0,0 +1,24 @@ +Feature: Tests for ingestion of virtual interface + Validate the behavior of the ingestion of virtual interface + +@smoke +@ingestion.virtual_interface +Scenario: Ingestion of new virtual interface + Given a new virtual interface "eth0" + When the virtual interface is ingested + Then the virtual interface and "undefined" virtual machine with "undefined" site are created in the database + +@smoke +@ingestion.virtual_interface +Scenario: Ingestion of existing virtual interface + Given virtual interface "eth0" with "undefined" virtual machine and "undefined" site already exists in the database + When the virtual interface is ingested + Then the virtual interface remains the same + +@smoke +@ingestion.virtual_interface +Scenario: Ingestion of virtual interface object to update the virtual machine, description and MTU + Given virtual interface "eth0" with virtual machine "VM-1", description "some string" and MTU "1500" + Then check if the virtual machine "VM-1" related interface exists in the database and remove it + When the virtual interface object is ingested with the updates + Then the virtual machine "VM-1" is created and the interface updated diff --git a/tests/features/ingestion_virtual_machine_objects.feature b/tests/features/ingestion_virtual_machine_objects.feature new file mode 100644 index 00000000..c8122746 --- /dev/null +++ b/tests/features/ingestion_virtual_machine_objects.feature @@ -0,0 +1,50 @@ +Feature: Tests for ingestion of virtual machine + Validate the behavior of the ingestion of virtual machine + +@smoke +@ingestion.virtual_machine +Scenario: Ingestion of a new virtual machine (site not provided) + Given virtual machine "vm01" with site not provided + And virtual machine "vm01" with site "undefined" does not exist + When the virtual machine without site is ingested + Then the virtual machine is found + And device role is "undefined" + +@smoke +@ingestion.virtual_machine +Scenario: Ingestion of existing virtual machine (site not provided) + Given virtual machine "vm01" with site not provided + And virtual machine "vm01" with site "undefined" exists + When the virtual machine without site is ingested + Then the virtual machine is found + And device role is "undefined" + +@smoke +@ingestion.virtual_machine +Scenario: Ingestion of a new virtual machine (site provided) + Given a new virtual machine "vm02" with site "Site B" + And virtual machine "vm02" with site "Site B" does not exist + When the virtual machine with site is ingested + Then the virtual machine is found + And device role is "undefined" + +@smoke +@ingestion.virtual_machine +Scenario: Ingestion of existing virtual machine (site provided) with different device role + Given virtual machine "vm02" with site "Site B" and role "Server" + And virtual machine "vm02" with site "Site B" exists + When the virtual machine with site and device role is ingested + Then the virtual machine is found + And device role is "Server" + +@smoke +@ingestion.virtual_machine +Scenario: Ingestion of existing virtual machine with description set to empty + Given virtual machine "vm01" with "lorem ipsum" description + When the virtual machine with description is ingested + Then the virtual machine with ingested "description" field is found + And vm description is "lorem ipsum" + Given virtual machine "vm01" with "empty" description + When the virtual machine with description is ingested + Then the virtual machine with ingested "description" field is found + And vm description is "empty" diff --git a/tests/features/steps/ingestion_virtual_disk_object.py b/tests/features/steps/ingestion_virtual_disk_object.py new file mode 100644 index 00000000..94d02efb --- /dev/null +++ b/tests/features/steps/ingestion_virtual_disk_object.py @@ -0,0 +1,176 @@ +import time + +from behave import given, when, then +from netboxlabs.diode.sdk.ingester import VirtualDisk, Entity, VirtualMachine +from steps.utils import ( + get_object_by_name, + send_delete_request, + get_object_state, + ingester, +) + +endpoint = "virtualization/virtual-disks/" + + +@given('a new virtual disk "{virtual_disk_name}" with size "{virtual_disk_size}"') +def step_create_new_virtual_machine(context, virtual_disk_name, virtual_disk_size): + """Set the body of the request to create a new virtual disk.""" + context.virtual_disk_name = virtual_disk_name + context.virtual_disk_size = int(virtual_disk_size) + + +@when("the virtual disk is ingested") +def ingest_virtual_disk(context): + """Ingest the virtual disk object using the Diode SDK""" + + entities = [ + Entity( + virtual_disk=VirtualDisk( + name=context.virtual_disk_name, size=context.virtual_disk_size + ) + ), + ] + + context.response = ingester(entities) + return context.response + + +@then( + 'the virtual disk and "{virtual_machine_name}" virtual machine with "{site_name}" site are created in the database' +) +def check_virtual_disk_and_virtual_machine(context, virtual_machine_name, site_name): + """Check if the response is not None and the object is created in the database.""" + time.sleep(3) + assert context.response is not None + + params = { + "object_type": "virtualization.virtualdisk", + "q": context.virtual_disk_name, + "virtual_machine__name": virtual_machine_name, + "virtual_machine__site__name": site_name, + } + + virtual_disk = get_object_state(params) + + assert virtual_disk.get("name") == context.virtual_disk_name + assert virtual_disk.get("size") == context.virtual_disk_size + assert virtual_disk.get("virtual_machine").get("name") == virtual_machine_name + + +@then("the virtual disk remains the same") +def check_virtual_disk_object(context): + """Check if the response is not None and the object is created in the database.""" + time.sleep(3) + assert context.response is not None + + params = { + "object_type": "virtualization.virtualdisk", + "q": context.virtual_disk_name, + "virtual_machine__name": context.virtual_machine_name, + "virtual_machine__site__name": context.site_name, + } + + virtual_disk = get_object_state(params) + + assert virtual_disk.get("name") == context.virtual_disk_name + + +@given( + 'virtual disk "{virtual_disk_name}" with "{virtual_machine_name}" virtual machine and "{site_name}" site already exists in the database' +) +def retrieve_existing_virtual_machine( + context, virtual_disk_name, virtual_machine_name, site_name +): + """Retrieve the virtual disk object from the database""" + time.sleep(3) + context.virtual_disk_name = virtual_disk_name + context.virtual_machine_name = virtual_machine_name + context.site_name = site_name + + params = { + "object_type": "virtualization.virtualdisk", + "q": context.virtual_disk_name, + "virtual_machine__name": virtual_machine_name, + "virtual_machine__site__name": site_name, + } + + virtual_disk = get_object_state(params) + + context.virtual_disk_name = virtual_disk.get("name") + context.virtual_disk_size = virtual_disk.get("size") + + +@given( + 'virtual disk "{virtual_disk_name}" with virtual machine "{virtual_machine_name}", description "{description}" ' + 'and size "{size}"' +) +def create_virtual_disk_to_update( + context, virtual_disk_name, virtual_machine_name, description, size +): + """Create a virtual disk object with a description to update""" + context.virtual_disk_name = virtual_disk_name + context.virtual_machine_name = virtual_machine_name + context.description = description + context.size = int(size) + + +@then( + 'check if the virtual machine "{virtual_machine_name}" related to disk exists in the database and remove it' +) +def remove_virtual_machine(context, virtual_machine_name): + time.sleep(3) + virtual_machine = get_object_by_name( + virtual_machine_name, "virtualization/virtual-machines/" + ) + if virtual_machine is not None: + assert virtual_machine.get("name") == virtual_machine_name + send_delete_request( + "virtualization/virtual-machines/", virtual_machine.get("id") + ) + + +@when("the virtual disk object is ingested with the updates") +def ingest_to_update_virtual_disk(context): + """Update the object using the Diode SDK""" + + entities = [ + Entity( + virtual_disk=VirtualDisk( + name=context.virtual_disk_name, + virtual_machine=VirtualMachine(name=context.virtual_machine_name), + description=context.description, + size=context.size, + ), + ), + ] + + context.response = ingester(entities) + return context.response + + +@then('the virtual machine "{virtual_machine_name}" is created and the disk updated') +def check_updated_virtual_disk_object(context, virtual_machine_name): + """Check if the response is not None and the object is updated in the database and virtual machine created.""" + time.sleep(3) + assert context.response is not None + + virtual_machine = get_object_by_name( + virtual_machine_name, "virtualization/virtual-machines/" + ) + assert virtual_machine.get("name") == virtual_machine_name + + params = { + "object_type": "virtualization.virtualdisk", + "q": context.virtual_disk_name, + "virtual_machine__name": virtual_machine_name, + "virtual_machine__site__name": virtual_machine.get("site").get("name"), + } + + virtual_disk = get_object_state(params) + + assert virtual_disk.get("name") == context.virtual_disk_name + assert virtual_disk.get("virtual_machine").get("name") == virtual_machine.get( + "name" + ) + assert virtual_disk.get("description") == context.description + assert virtual_disk.get("size") == context.size diff --git a/tests/features/steps/ingestion_virtual_interface_object.py b/tests/features/steps/ingestion_virtual_interface_object.py new file mode 100644 index 00000000..82d62d53 --- /dev/null +++ b/tests/features/steps/ingestion_virtual_interface_object.py @@ -0,0 +1,173 @@ +import time + +from behave import given, when, then +from netboxlabs.diode.sdk.ingester import VMInterface, Entity, VirtualMachine +from steps.utils import ( + get_object_by_name, + send_delete_request, + get_object_state, + ingester, +) + +endpoint = "virtualization/virtual-interfaces/" + + +@given('a new virtual interface "{vminterface_name}"') +def step_create_new_virtual_machine(context, vminterface_name): + """Set the body of the request to create a new virtual interface.""" + context.vminterface_name = vminterface_name + + +@when("the virtual interface is ingested") +def ingest_virtual_interface(context): + """Ingest the virtual interface object using the Diode SDK""" + + entities = [ + Entity(vminterface=VMInterface(name=context.vminterface_name)), + ] + + context.response = ingester(entities) + return context.response + + +@then( + 'the virtual interface and "{virtual_machine_name}" virtual machine with "{site_name}" site are created in the database' +) +def check_virtual_interface_and_virtual_machine( + context, virtual_machine_name, site_name +): + """Check if the response is not None and the object is created in the database.""" + time.sleep(3) + assert context.response is not None + + params = { + "object_type": "virtualization.vminterface", + "q": context.vminterface_name, + "virtual_machine__name": virtual_machine_name, + "virtual_machine__site__name": site_name, + } + + virtual_interface = get_object_state(params) + + assert virtual_interface.get("name") == context.vminterface_name + assert virtual_interface.get("virtual_machine").get("name") == virtual_machine_name + + +@then("the virtual interface remains the same") +def check_virtual_interface_object(context): + """Check if the response is not None and the object is created in the database.""" + time.sleep(3) + assert context.response is not None + + params = { + "object_type": "virtualization.vminterface", + "q": context.vminterface_name, + "virtual_machine__name": context.virtual_machine_name, + "virtual_machine__site__name": context.site_name, + } + + virtual_interface = get_object_state(params) + + assert virtual_interface.get("name") == context.vminterface_name + + +@given( + 'virtual interface "{vminterface_name}" with "{virtual_machine_name}" virtual machine and "{site_name}" site already exists in the database' +) +def retrieve_existing_virtual_machine( + context, vminterface_name, virtual_machine_name, site_name +): + """Retrieve the virtual interface object from the database""" + time.sleep(3) + context.vminterface_name = vminterface_name + context.virtual_machine_name = virtual_machine_name + context.site_name = site_name + + params = { + "object_type": "virtualization.vminterface", + "q": context.vminterface_name, + "virtual_machine__name": virtual_machine_name, + "virtual_machine__site__name": site_name, + } + + virtual_interface = get_object_state(params) + + context.vminterface_name = virtual_interface.get("name") + + +@given( + 'virtual interface "{vminterface_name}" with virtual machine "{virtual_machine_name}", description "{description}" ' + 'and MTU "{mtu}"' +) +def create_virtual_interface_to_update( + context, vminterface_name, virtual_machine_name, description, mtu +): + """Create a virtual interface object with a description to update""" + context.vminterface_name = vminterface_name + context.virtual_machine_name = virtual_machine_name + context.description = description + context.mtu = int(mtu) + + +@then( + 'check if the virtual machine "{virtual_machine_name}" related interface exists in the database and remove it' +) +def remove_virtual_machine(context, virtual_machine_name): + time.sleep(3) + virtual_machine = get_object_by_name( + virtual_machine_name, "virtualization/virtual-machines/" + ) + if virtual_machine is not None: + assert virtual_machine.get("name") == virtual_machine_name + send_delete_request( + "virtualization/virtual-machines/", virtual_machine.get("id") + ) + + +@when("the virtual interface object is ingested with the updates") +def ingest_to_update_virtual_interface(context): + """Update the object using the Diode SDK""" + + entities = [ + Entity( + vminterface=VMInterface( + name=context.vminterface_name, + virtual_machine=VirtualMachine(name=context.virtual_machine_name), + description=context.description, + mtu=context.mtu, + ), + ), + ] + + context.response = ingester(entities) + return context.response + + +@then( + 'the virtual machine "{virtual_machine_name}" is created and the interface updated' +) +def check_updated_virtual_interface_object(context, virtual_machine_name): + """Check if the response is not None and the object is updated in the database and virtual machine created.""" + time.sleep(3) + assert context.response is not None + + virtual_machine = get_object_by_name( + virtual_machine_name, "virtualization/virtual-machines/" + ) + assert virtual_machine.get("name") == virtual_machine_name + + params = { + "object_type": "virtualization.vminterface", + "q": context.vminterface_name, + "virtual_machine__name": virtual_machine_name, + "virtual_machine__site__name": virtual_machine.get("site").get("name"), + } + + virtual_interface = get_object_state(params) + + assert virtual_interface.get("name") == context.vminterface_name + assert virtual_interface.get("virtual_machine").get("name") == virtual_machine.get( + "name" + ) + assert virtual_interface.get("description") == context.description + assert virtual_interface.get("mtu") == context.mtu diff --git a/tests/features/steps/ingestion_virtual_machine_object.py b/tests/features/steps/ingestion_virtual_machine_object.py new file mode 100644 index 00000000..8bb82eb4 --- /dev/null +++ b/tests/features/steps/ingestion_virtual_machine_object.py @@ -0,0 +1,206 @@ +import time +from behave import given, when, then +from netboxlabs.diode.sdk.ingester import ( + VirtualMachine, + Entity, +) +from steps.utils import ( + get_object_state, + ingester, + send_delete_request, +) + + +endpoint = "virtualization/virtual-machines/" + + +@given('virtual machine "{virtual_machine_name}" with site not provided') +def set_virtual_machine_without_site(context, virtual_machine_name): + """Set the body of the request to ingest the virtual machine.""" + context.virtual_machine_name = virtual_machine_name + context.site_name = "undefined" + context.virtual_machine_role_name = "undefined" + +@given('virtual machine "{virtual_machine_name}" with site "{site_name}" does not exist') +def ensure_virtual_machine_does_not_exists(context, virtual_machine_name, site_name): + """Ensure that the virtual machine does not exist.""" + virtual_machine = get_object_state( + { + "object_type": "virtualization.virtualmachine", + "q": virtual_machine_name, + "site__name": site_name, + }, + ) + if virtual_machine: + send_delete_request(endpoint, virtual_machine.get("id")) + + +@when("the virtual machine without site is ingested") +def ingest_virtual_machine_without_site(context): + """Ingest the virtual machine using the Diode SDK""" + + entities = [ + Entity(virtual_machine=context.virtual_machine_name), + ] + + response = ingester(entities) + assert response.errors == [] + + context.response = response + return response + + +@then("the virtual machine is found") +def assert_virtual_machine_exists(context): + """Assert that the virtual machine was created.""" + time.sleep(2) + assert context.response is not None + + params = { + "object_type": "virtualization.virtualmachine", + "q": context.virtual_machine_name, + "site__name": context.site_name, + } + + virtual_machine = get_object_state(params) + + assert virtual_machine.get("name") == context.virtual_machine_name + assert virtual_machine.get("site").get("name") == context.site_name + context.existing_virtual_machine = virtual_machine + + +@then('device role is "{virtual_machine_role_name}"') +def assert_virtual_machine_role(context, virtual_machine_role_name): + """Assert that the virtual machine role is correct.""" + assert context.existing_virtual_machine is not None + assert context.existing_virtual_machine.get("role").get("name") == virtual_machine_role_name + + +@given('virtual machine "{virtual_machine_name}" with site "{site_name}" exists') +def assert_virtual_machine_exists_with_site(context, virtual_machine_name, site_name): + """Assert that the virtual machine exists.""" + virtual_machine = get_object_state( + { + "object_type": "virtualization.virtualmachine", + "q": virtual_machine_name, + "site__name": site_name, + }, + ) + + assert virtual_machine.get("name") == virtual_machine_name + assert virtual_machine.get("site").get("name") == site_name + context.existing_virtual_machine = virtual_machine + + +@given('a new virtual machine "{virtual_machine_name}" with site "{site_name}"') +def create_new_virtual_machine_with_site(context, virtual_machine_name, site_name): + """Set the body of the request to create a new virtual machine with site.""" + context.virtual_machine_name = virtual_machine_name + context.site_name = site_name + context.virtual_machine_role_name = "undefined" + + +@when("the virtual machine with site is ingested") +def ingest_virtual_machine_with_site(context): + """Ingest the virtual machine using the Diode SDK""" + entities = [ + Entity( + virtual_machine=VirtualMachine( + name=context.virtual_machine_name, + site=context.site_name, + ), + ), + ] + + context.response = ingester(entities) + assert context.response.errors == [] + + return context.response + + +@given( + 'virtual machine "{virtual_machine_name}" with site "{site_name}" and role "{virtual_machine_role_name}"' +) +def update_virtual_machine(context, virtual_machine_name, site_name, virtual_machine_role_name): + """Set the body of the request to update a virtual machine.""" + context.virtual_machine_name = virtual_machine_name + context.site_name = site_name + context.virtual_machine_role_name = virtual_machine_role_name + + +@when("the virtual machine with site and device role is ingested") +def ingest_virtual_machine_with_site_device_role(context): + """Ingest the virtual machine using the Diode SDK""" + entities = [ + Entity( + virtual_machine=VirtualMachine( + name=context.virtual_machine_name, + site=context.site_name, + role=context.virtual_machine_role_name, + ), + ), + ] + + context.response = ingester(entities) + assert context.response.errors == [] + + return context.response + +@given('virtual machine "{virtual_machine_name}" with "{description}" description') +def set_virtual_machine_with_description(context, virtual_machine_name, description): + """Set the body of the request to ingest the virtual machine.""" + context.virtual_machine_name = virtual_machine_name + context.site_name = "undefined" + if description == "empty": + description = "" + context.description = description + + +@when("the virtual machine with description is ingested") +def ingest_virtual_machine_with_description(context): + """Ingest the virtual machine using the Diode SDK""" + entities = [ + Entity( + virtual_machine=VirtualMachine( + name=context.virtual_machine_name, + description=context.description, + ), + ) + ] + + context.response = ingester(entities) + assert context.response.errors == [] + + return context.response + + +@then('the virtual machine with ingested "{field_name}" field is found') +def assert_virtual_machine_with_ingested_field_is_found(context, field_name): + """Assert that the virtual machine exists.""" + assert context.response is not None + + params = { + "object_type": "virtualization.virtualmachine", + "q": context.virtual_machine_name, + "site__name": context.site_name, + field_name: getattr(context, field_name), + } + + if hasattr(context, "virtual_machine_role_name"): + params["role__name"] = context.virtual_machine_role_name + + virtual_machine = get_object_state(params) + + assert virtual_machine.get("name") == context.virtual_machine_name + assert virtual_machine.get("site").get("name") == context.site_name + + context.existing_virtual_machine = virtual_machine + + +@then('vm description is "{description}"') +def assert_description(context, description): + """Assert that the description is correct.""" + if description == "empty": + description = "" + assert context.existing_virtual_machine is not None + assert context.existing_virtual_machine.get("description") == description