diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..d05fc9763 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,52 @@ +version: 2 + +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" + allow: + - dependency-type: "all" + commit-message: + prefix: "fix" + open-pull-requests-limit: 10 + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + allow: + - dependency-type: "all" + commit-message: + prefix: "fix" + open-pull-requests-limit: 10 + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + allow: + - dependency-type: "all" + commit-message: + prefix: "fix" + open-pull-requests-limit: 10 + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + allow: + - dependency-type: "all" + commit-message: + prefix: "fix" + open-pull-requests-limit: 10 + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + allow: + - dependency-type: "all" + commit-message: + prefix: "fix" + open-pull-requests-limit: 10 diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..7903615fb --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,73 @@ +documentation: + - docs/* + - docs/**/* + - mkdocs.yml + +github-actions: + - .github/workflows/* + - .github/workflows/**/* + - .github/dependabot.yml + - .github/boring-cyborg.yml + - .github/release-drafter.yml + - .github/semantic.yml + - .github/stale.yml + - .github/mergify.yml + - .github/labeler.yml + +github-templates: + - .github/ISSUE_TEMPLATE/* + - .github/PULL_REQUEST_TEMPLATE.md + - .github/.chglog/* + - .github/.chglog/**/* + +internal: + - .flake8 + - .bandit.baseline + - .gitignore + - .pre-commit-config.yaml + - MANIFEST.in + - Makefile + - CONTRIBUTING.md + - MAINTAINERS.md + - CODE_OF_CONDUCT.md + - LICENSE + - THIRD-PARTY-LICENSES + - .dockerignore + - .editorconfig + - setup.cfg + +dependencies: + - pyproject.toml + - poetry.lock + - requirements.txt + +tests: + - automated_tests/* + - automated_tests/**/* + - tests/* + - tests/**/* + +build: + - Dockerfile + +golang: + - "**/*.go" + +javascript: + - "**/*.json" + +json: + - "**/*.json" + +markdown: + - "**/*.md" + +python: + - "**/*.py" + +sql: + - "**/*.py" + +typescript: + - "**/*.ts" + - "**/*.tsx" diff --git a/.github/workflows/agent.yml b/.github/workflows/agent.yml index 23565ec43..89a7adc40 100644 --- a/.github/workflows/agent.yml +++ b/.github/workflows/agent.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 diff --git a/.github/workflows/go-develop.yml b/.github/workflows/go-develop.yml index 02cd7b8e7..984b20d0c 100644 --- a/.github/workflows/go-develop.yml +++ b/.github/workflows/go-develop.yml @@ -21,7 +21,7 @@ jobs: docs: ${{ steps.filter.outputs.docs }} VERSION: ${{ env.VERSION }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dorny/paths-filter@v2 id: filter with: @@ -80,7 +80,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: name: workspace @@ -112,9 +112,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -151,9 +151,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -190,9 +190,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -229,9 +229,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -268,9 +268,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -307,9 +307,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -348,7 +348,7 @@ jobs: - test-agent runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -404,7 +404,7 @@ jobs: - test-fleet runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -431,7 +431,7 @@ jobs: - test-policies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -458,7 +458,7 @@ jobs: - test-sinker runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -485,7 +485,7 @@ jobs: - test-sinks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -512,7 +512,7 @@ jobs: - test-maestro runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -538,7 +538,7 @@ jobs: - prebuild runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dorny/paths-filter@v2 id: filter with: @@ -569,7 +569,7 @@ jobs: - package-ui-dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -599,7 +599,7 @@ jobs: git push origin develop - name: Login to Docker Hub orbcommunity - uses: docker/login-action@v1 + uses: docker/login-action@v2 if: github.event_name != 'pull_request' with: username: ${{ secrets.ORB_DOCKERHUB_USERNAME }} @@ -616,7 +616,7 @@ jobs: - package-ui runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build orb migrate service image if: ${{ needs.prebuild.outputs.migrate == 'true' }} @@ -648,7 +648,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name != 'pull_request' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Debug values run: | diff --git a/.github/workflows/go-main.yml b/.github/workflows/go-main.yml index 0f7c4c045..108fbe466 100644 --- a/.github/workflows/go-main.yml +++ b/.github/workflows/go-main.yml @@ -20,7 +20,7 @@ jobs: ui: ${{ steps.filter.outputs.ui }} VERSION: ${{ env.VERSION }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dorny/paths-filter@v2 id: filter with: @@ -61,9 +61,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -74,9 +74,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -88,9 +88,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -102,9 +102,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -116,9 +116,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -130,9 +130,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -148,7 +148,7 @@ jobs: - test-agent runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -202,7 +202,7 @@ jobs: - test-fleet runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -228,7 +228,7 @@ jobs: - test-policies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -254,7 +254,7 @@ jobs: - test-sinker runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -280,7 +280,7 @@ jobs: - test-sinks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -306,7 +306,7 @@ jobs: - test-maestro runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -331,7 +331,7 @@ jobs: - prebuild runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dorny/paths-filter@v2 id: filter with: @@ -362,7 +362,7 @@ jobs: - package-ui-dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -373,7 +373,7 @@ jobs: run: make ui - name: Login to Docker Hub orbcommunity - uses: docker/login-action@v1 + uses: docker/login-action@v2 if: github.event_name != 'pull_request' with: username: ${{ secrets.ORB_DOCKERHUB_USERNAME }} @@ -397,7 +397,7 @@ jobs: if: github.event_name != 'pull_request' steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 diff --git a/.github/workflows/go-production.yml b/.github/workflows/go-production.yml index 61298d9bd..1b43dff8a 100644 --- a/.github/workflows/go-production.yml +++ b/.github/workflows/go-production.yml @@ -20,7 +20,7 @@ jobs: ui: ${{ steps.filter.outputs.ui }} VERSION: ${{ env.VERSION }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dorny/paths-filter@v2 id: filter with: @@ -66,9 +66,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -80,9 +80,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -96,9 +96,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -110,9 +110,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -124,9 +124,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -138,9 +138,9 @@ jobs: runs-on: ubuntu-latest needs: prebuild steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.19 @@ -157,7 +157,7 @@ jobs: - test-agent runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -197,7 +197,7 @@ jobs: fi - name: Login to Docker Hub orbcommunity if: github.event_name != 'pull_request' - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -223,7 +223,7 @@ jobs: - test-fleet runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -249,7 +249,7 @@ jobs: - test-policies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -275,7 +275,7 @@ jobs: - test-sinker runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -301,7 +301,7 @@ jobs: - test-sinks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -327,7 +327,7 @@ jobs: - test-maestro runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -352,7 +352,7 @@ jobs: - prebuild runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dorny/paths-filter@v2 id: filter with: @@ -383,7 +383,7 @@ jobs: - package-ui-dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get short commit hash to a variable id: commit_hash run: | @@ -412,7 +412,7 @@ jobs: git push origin main - name: Login to Docker Hub orbcommunity - uses: docker/login-action@v1 + uses: docker/login-action@v2 if: github.event_name != 'pull_request' with: username: ${{ secrets.ORB_DOCKERHUB_USERNAME }} @@ -435,7 +435,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name != 'pull_request' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Debug values run: | @@ -480,7 +480,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name != 'pull_request' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Generating changelog and post it in slack channel uses: ./.github/actions/slack-post with: @@ -495,7 +495,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name != 'pull_request' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set branch name shell: bash @@ -528,7 +528,7 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Login to Docker Hub orbcommunity - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.ORB_DOCKERHUB_USERNAME }} password: ${{ secrets.ORB_DOCKERHUB_TOKEN }} @@ -559,7 +559,7 @@ jobs: IMAGE_NAME: orbcommunity/orb-agent:latest IMAGE_NAME_2: orbcommunity/orb-agent:${{ env.VERSION }}-${{ steps.commit_hash.outputs.sha_short }} - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: builder: ${{ steps.buildx.outputs.name }} context: . diff --git a/.github/workflows/labeler.yaml b/.github/workflows/labeler.yaml new file mode 100644 index 000000000..857cfb4a7 --- /dev/null +++ b/.github/workflows/labeler.yaml @@ -0,0 +1,12 @@ +name: "Pull Request Labeler" +on: + - pull_request_target + +jobs: + triage: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v4 diff --git a/Makefile b/Makefile index ca36f38ab..746989174 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,20 @@ define make_docker -f docker/Dockerfile . $(eval SERVICE="") endef +define make_docker_debug + $(eval SERVICE=$(shell [ -z "$(SERVICE)" ] && echo $(subst docker_,,$(1)) || echo $(SERVICE))) + docker build \ + --no-cache \ + --build-arg SVC=$(SERVICE) \ + --build-arg GOARCH=$(GOARCH) \ + --build-arg GOARM=$(GOARM) \ + --tag=$(ORB_DOCKERHUB_REPO)/$(DOCKER_IMAGE_NAME_PREFIX)-$(SERVICE):$(REF_TAG) \ + --tag=$(ORB_DOCKERHUB_REPO)/$(DOCKER_IMAGE_NAME_PREFIX)-$(SERVICE):$(ORB_VERSION) \ + --tag=$(ORB_DOCKERHUB_REPO)/$(DOCKER_IMAGE_NAME_PREFIX)-$(SERVICE):$(ORB_VERSION)-$(COMMIT_HASH) \ + -f docker/Dockerfile.debug . + $(eval SERVICE="") +endef + define make_docker_dev $(eval svc=$(shell [ -z "$(SERVICE)" ] && echo $(subst docker_dev_,,$(1)) || echo $(svc))) docker build \ @@ -122,6 +136,9 @@ dockers_dev: $(DOCKERS_DEV) build_docker: $(call make_docker,$(@),$(GOARCH)) +build_docker_debug: + $(call make_docker_debug,$(@),$(GOARCH)) + # install tools for kind install-kind: @@ -260,8 +277,6 @@ ui-modules: ui: cd ui/ && docker build \ - --build-arg ENV_PS_SID=${PS_SID} \ - --build-arg ENV_PS_GROUP_KEY=${PS_GROUP_KEY} \ --build-arg ENV=${ENVIRONMENT} \ --tag=$(ORB_DOCKERHUB_REPO)/$(DOCKER_IMAGE_NAME_PREFIX)-ui:$(REF_TAG) \ --tag=$(ORB_DOCKERHUB_REPO)/$(DOCKER_IMAGE_NAME_PREFIX)-ui:$(ORB_VERSION) \ diff --git a/README.md b/README.md index fb6a7cb0d..89f2b12d4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/orb-community/orb)](https://goreportcard.com/report/github.com/orb-community/orb) [![CodeCov](https://codecov.io/gh/orb-community/orb/branch/develop/graph/badge.svg)](https://app.codecov.io/gh/orb-community/orb/tree/develop) +[![Go Reference](https://pkg.go.dev/badge/github.com/orb-community/orb.svg)](https://pkg.go.dev/github.com/orb-community/orb) **Orb** is a modern network observability platform built to provide critical visibility into increasingly complex and distributed networks. It can analyze network traffic, run synthetic network probes, and connect the resulting telemetry directly to your existing observability stacks with OpenTelemetry. Orb differentiates from other solutions by pushing analysis close to the traffic sources (reducing inactionable metrics and processing costs), and allows for dynamic reconfiguration of remote agents in real time. @@ -28,7 +29,7 @@ with more analyzers in the works. ## Realtime Agent Orchestration -Orb uses IoT principals to allow the observability agents to connect out to the Orb central control plane, avoiding +Orb uses IoT principles to allow the observability agents to connect out to the Orb central control plane, avoiding firewall problems. Once connected, agents are controlled in real time from the Orb Portal or REST API, orchestrating observability [policies](https://orb.community/about/#policies) designed to precisely extract the desired insights. Agents are grouped and addressed based on [tags](https://orb.community/about/#agent-group). diff --git a/VERSION b/VERSION index d21d277be..4e8f395fa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.25.0 +0.26.0 diff --git a/agent/backend/diode/scrape.go b/agent/backend/diode/scrape.go index b1fdfba9d..c4a2d86e2 100644 --- a/agent/backend/diode/scrape.go +++ b/agent/backend/diode/scrape.go @@ -26,7 +26,7 @@ const ( func (d *diodeBackend) createOtlpMqttExporter(ctx context.Context, cancelFunc context.CancelFunc) (exporter.Logs, error) { - bridgeService := otel.NewBridgeService(ctx, &d.policyRepo, d.agentTags) + bridgeService := otel.NewBridgeService(ctx, cancelFunc, &d.policyRepo, d.agentTags) if d.mqttClient != nil { cfg := otlpmqttexporter.CreateConfigClient(d.mqttClient, d.logTopic, d.version, bridgeService) set := otlpmqttexporter.CreateDefaultSettings(d.logger) diff --git a/agent/backend/pktvisor/scrape.go b/agent/backend/pktvisor/scrape.go index 120e42473..60089085d 100644 --- a/agent/backend/pktvisor/scrape.go +++ b/agent/backend/pktvisor/scrape.go @@ -37,26 +37,26 @@ func (p *pktvisorBackend) scrapeMetrics(period uint) (map[string]interface{}, er func (p *pktvisorBackend) createOtlpMqttExporter(ctx context.Context, cancelFunc context.CancelFunc) (exporter.Metrics, error) { - bridgeService := otel.NewBridgeService(ctx, &p.policyRepo, p.agentTags) + bridgeService := otel.NewBridgeService(ctx, cancelFunc, &p.policyRepo, p.agentTags) if p.mqttClient != nil { cfg := otlpmqttexporter.CreateConfigClient(p.mqttClient, p.otlpMetricsTopic, p.pktvisorVersion, bridgeService) set := otlpmqttexporter.CreateDefaultSettings(p.logger) - // Create the OTLP metrics exporter that'll receive and verify the metrics produced. - exporter, err := otlpmqttexporter.CreateMetricsExporter(ctx, set, cfg) + // Create the OTLP metrics metricsExporter that'll receive and verify the metrics produced. + metricsExporter, err := otlpmqttexporter.CreateMetricsExporter(ctx, set, cfg) if err != nil { return nil, err } - return exporter, nil + return metricsExporter, nil } else { cfg := otlpmqttexporter.CreateConfig(p.mqttConfig.Address, p.mqttConfig.Id, p.mqttConfig.Key, p.mqttConfig.ChannelID, p.pktvisorVersion, p.otlpMetricsTopic, bridgeService) set := otlpmqttexporter.CreateDefaultSettings(p.logger) // Create the OTLP metrics exporter that'll receive and verify the metrics produced. - exporter, err := otlpmqttexporter.CreateMetricsExporter(ctx, set, cfg) + metricsExporter, err := otlpmqttexporter.CreateMetricsExporter(ctx, set, cfg) if err != nil { return nil, err } - return exporter, nil + return metricsExporter, nil } } diff --git a/agent/otel/bridgeservice.go b/agent/otel/bridgeservice.go index 9e1ea7d2a..b933a344f 100644 --- a/agent/otel/bridgeservice.go +++ b/agent/otel/bridgeservice.go @@ -2,8 +2,9 @@ package otel import ( "context" - "github.com/orb-community/orb/agent/policies" "strings" + + "github.com/orb-community/orb/agent/policies" ) type AgentBridgeService interface { @@ -21,13 +22,15 @@ var _ AgentBridgeService = (*BridgeService)(nil) type BridgeService struct { bridgeContext context.Context + cancelFunc context.CancelFunc policyRepo policies.PolicyRepo AgentTags map[string]string } -func NewBridgeService(ctx context.Context, policyRepo *policies.PolicyRepo, agentTags map[string]string) *BridgeService { +func NewBridgeService(ctx context.Context, cancelFunc context.CancelFunc, policyRepo *policies.PolicyRepo, agentTags map[string]string) *BridgeService { return &BridgeService{ bridgeContext: ctx, + cancelFunc: cancelFunc, policyRepo: *policyRepo, AgentTags: agentTags, } @@ -47,5 +50,5 @@ func (b *BridgeService) RetrieveAgentInfoByPolicyName(policyName string) (*Agent func (b *BridgeService) NotifyAgentDisconnection(ctx context.Context, err error) { ctx.Done() - b.bridgeContext.Done() + b.cancelFunc() } diff --git a/cmd/sinker/main.go b/cmd/sinker/main.go index 078e1f710..e1ebf79cf 100644 --- a/cmd/sinker/main.go +++ b/cmd/sinker/main.go @@ -65,6 +65,7 @@ func main() { policiesGRPCCfg := config.LoadGRPCConfig("orb", "policies") sinksGRPCCfg := config.LoadGRPCConfig("orb", "sinks") otelCfg := config.LoadOtelConfig(envPrefix) + inMemoryCacheConfig := config.LoadInMemoryCacheConfig(envPrefix) // main logger var logger *zap.Logger @@ -191,7 +192,8 @@ func main() { otelEnabled := otelCfg.Enable == "true" otelKafkaUrl := otelCfg.KafkaUrl - svc := sinker.New(logger, pubSub, esClient, configRepo, policiesGRPCClient, fleetGRPCClient, sinksGRPCClient, otelKafkaUrl, otelEnabled, gauge, counter, inputCounter) + svc := sinker.New(logger, pubSub, esClient, configRepo, policiesGRPCClient, fleetGRPCClient, sinksGRPCClient, + otelKafkaUrl, otelEnabled, gauge, counter, inputCounter, inMemoryCacheConfig.DefaultExpiration) defer func(svc sinker.Service) { err := svc.Stop() if err != nil { diff --git a/cmd/sinks/main.go b/cmd/sinks/main.go index 2042053cb..be4dffe70 100644 --- a/cmd/sinks/main.go +++ b/cmd/sinks/main.go @@ -18,6 +18,8 @@ import ( "github.com/orb-community/orb/sinks" sinksgrpc "github.com/orb-community/orb/sinks/api/grpc" sinkshttp "github.com/orb-community/orb/sinks/api/http" + "github.com/orb-community/orb/sinks/authentication_type" + "github.com/orb-community/orb/sinks/migrate" "github.com/orb-community/orb/sinks/pb" "github.com/orb-community/orb/sinks/postgres" rediscons "github.com/orb-community/orb/sinks/redis/consumer" @@ -110,10 +112,17 @@ func main() { auth := authapi.NewClient(tracer, authConn, authTimeout) sinkRepo := postgres.NewSinksRepository(db, logger) - - svc := newSinkService(auth, logger, esClient, sdkCfg, sinkRepo, encryptionKey) + pwdSvc := authentication_type.NewPasswordService(logger, encryptionKey.Key) + svc := newSinkService(auth, logger, esClient, sdkCfg, sinkRepo, pwdSvc) errs := make(chan error, 2) + plan1 := migrate.NewPlan1(logger, svc, sinkRepo, pwdSvc) + migrateService := migrate.NewService(logger, sinkRepo) + err = migrateService.Migrate(plan1) + if err != nil { + log.Fatalf("Migration failed with error %e", err) + } + go startHTTPServer(tracer, svc, svcCfg, logger, errs) go startGRPCServer(svc, tracer, sinksGRPCCfg, logger, errs) go subscribeToSinkerES(svc, esClient, esCfg, logger) @@ -175,15 +184,15 @@ func initJaeger(svcName, url string, logger *zap.Logger) (opentracing.Tracer, io return tracer, closer } -func newSinkService(auth mainflux.AuthServiceClient, logger *zap.Logger, esClient *r.Client, sdkCfg config.MFSDKConfig, repoSink sinks.SinkRepository, encriptionKey config.EncryptionKey) sinks.SinkService { +func newSinkService(auth mainflux.AuthServiceClient, logger *zap.Logger, esClient *r.Client, sdkCfg config.MFSDKConfig, repoSink sinks.SinkRepository, passwordService authentication_type.PasswordService) sinks.SinkService { config := mfsdk.Config{ ThingsURL: sdkCfg.ThingsURL, } mfsdk := mfsdk.NewSDK(config) - pwdSvc := sinks.NewPasswordService(logger, encriptionKey.Key) - svc := sinks.NewSinkService(logger, auth, repoSink, mfsdk, pwdSvc) + + svc := sinks.NewSinkService(logger, auth, repoSink, mfsdk, passwordService) svc = redisprod.NewEventStoreMiddleware(svc, esClient) svc = sinkshttp.NewLoggingMiddleware(svc, logger) svc = sinkshttp.MetricsMiddleware( diff --git a/docker/Dockerfile.debug b/docker/Dockerfile.debug new file mode 100644 index 000000000..fa0504598 --- /dev/null +++ b/docker/Dockerfile.debug @@ -0,0 +1,22 @@ +FROM golang:1.19-alpine AS builder +ARG SVC +ARG GOARCH +ARG GOARM + +WORKDIR /go/src/github.com/orbcommunity/orb +COPY . . +RUN apk update \ + && apk add make +RUN make $SVC \ + && mv build/orb-$SVC /exe +RUN go install github.com/go-delve/delve/cmd/dlv@latest + +FROM alpine:latest +ARG SVC + +RUN if [[ "maestro" == "$SVC" ]]; then apk update && apk add --no-cache docker-cli bash curl && curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kubectl && chmod +x ./kubectl && mv ./kubectl /usr/local/bin/kubectl; fi +# Certificates are needed so that mailing util can work. +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=builder /exe / +COPY --from=builder /go/bin/dlv /dlv +CMD ["/dlv", "--listen=:2345", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", "/exe"] diff --git a/fleet/api/http/endpoint.go b/fleet/api/http/endpoint.go index d63b8b765..4aa369e44 100644 --- a/fleet/api/http/endpoint.go +++ b/fleet/api/http/endpoint.go @@ -304,15 +304,16 @@ func listAgentsEndpoint(svc fleet.Service) endpoint.Endpoint { policyState, _ := svc.GetPolicyState(ctx, ag) view := agentRes{ - ID: ag.MFThingID, - Name: ag.Name.String(), - ChannelID: ag.MFChannelID, - AgentTags: ag.AgentTags, - OrbTags: *ag.OrbTags, - TsCreated: ag.Created, - State: ag.State.String(), - TsLastHB: ag.LastHB, - PolicyState: policyState, + ID: ag.MFThingID, + Name: ag.Name.String(), + ChannelID: ag.MFChannelID, + AgentTags: ag.AgentTags, + OrbTags: *ag.OrbTags, + TsCreated: ag.Created, + State: ag.State.String(), + TsLastHB: ag.LastHB, + PolicyState: policyState, + AgentMetadata: ag.AgentMetadata, } res.Agents = append(res.Agents, view) } diff --git a/fleet/api/http/transport.go b/fleet/api/http/transport.go index 7fd929dc3..265f680bb 100644 --- a/fleet/api/http/transport.go +++ b/fleet/api/http/transport.go @@ -7,6 +7,10 @@ package http import ( "context" "encoding/json" + "io" + "net/http" + "strings" + kitot "github.com/go-kit/kit/tracing/opentracing" kithttp "github.com/go-kit/kit/transport/http" "github.com/go-zoo/bone" @@ -19,9 +23,6 @@ import ( "github.com/orb-community/orb/pkg/errors" "github.com/orb-community/orb/pkg/types" "github.com/prometheus/client_golang/prometheus/promhttp" - "io" - "net/http" - "strings" ) const ( @@ -282,6 +283,7 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) { case errors.Contains(errorVal, errors.ErrUnsupportedContentType): w.WriteHeader(http.StatusUnsupportedMediaType) + case errors.Contains(errorVal, errors.ErrMalformedEntity): w.WriteHeader(http.StatusBadRequest) case errors.Contains(errorVal, errors.ErrNotFound): diff --git a/go.mod b/go.mod index 5b0ac2347..44791ff7a 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,9 @@ module github.com/orb-community/orb go 1.19 require ( - github.com/andybalholm/brotli v1.0.4 + github.com/andybalholm/brotli v1.0.5 github.com/aws/aws-sdk-go v1.44.232 + github.com/benbjohnson/immutable v0.4.3 github.com/eclipse/paho.mqtt.golang v1.4.2 github.com/fatih/structs v1.1.0 github.com/ghodss/yaml v1.0.0 @@ -18,15 +19,15 @@ require ( github.com/golang/snappy v0.0.4 github.com/hashicorp/go-version v1.6.0 github.com/jmoiron/sqlx v1.3.4 - github.com/lib/pq v1.10.4 + github.com/lib/pq v1.10.7 github.com/mainflux/mainflux v0.0.0-20220415135135-92d8fb99bf82 github.com/mattn/go-sqlite3 v1.14.16 github.com/mitchellh/mapstructure v1.5.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor v0.75.0 github.com/opentracing/opentracing-go v1.2.0 - github.com/ory/dockertest/v3 v3.8.1 + github.com/ory/dockertest/v3 v3.10.0 github.com/pkg/profile v1.7.0 - github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/client_golang v1.16.0 github.com/prometheus/prometheus v0.43.0 github.com/rubenv/sql-migrate v1.1.1 github.com/spf13/cobra v1.7.0 @@ -38,11 +39,11 @@ require ( go.opentelemetry.io/collector/consumer v0.75.0 go.opentelemetry.io/collector/exporter v0.75.0 go.opentelemetry.io/collector/receiver v0.75.0 - go.uber.org/multierr v1.10.0 + go.uber.org/multierr v1.11.0 go.uber.org/zap v1.24.0 golang.org/x/exp v0.0.0-20230321023759-10a507213a29 google.golang.org/grpc v1.54.0 - google.golang.org/protobuf v1.30.0 + google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.26.3 @@ -53,8 +54,7 @@ require ( require ( github.com/gogo/protobuf v1.3.2 github.com/google/uuid v1.3.0 - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.75.0 - github.com/prometheus/common v0.42.0 + github.com/prometheus/common v0.44.0 // indirect go.opentelemetry.io/collector v0.75.0 go.opentelemetry.io/collector/pdata v1.0.0-rc9 go.opentelemetry.io/otel/metric v0.37.0 @@ -64,99 +64,51 @@ require ( ) require ( - cloud.google.com/go/compute/metadata v0.2.3 // indirect - github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.28 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect - github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect - github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect - github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect - github.com/Azure/go-autorest/logger v0.2.1 // indirect - github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/alecthomas/participle/v2 v2.0.0 // indirect - github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect - github.com/armon/go-metrics v0.4.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43 // indirect - github.com/containerd/continuity v0.2.2 // indirect + github.com/containerd/continuity v0.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dennwc/varint v1.0.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/digitalocean/godo v1.97.0 // indirect - github.com/docker/cli v20.10.12+incompatible // indirect - github.com/docker/distribution v2.8.1+incompatible // indirect - github.com/docker/docker v23.0.1+incompatible // indirect + github.com/docker/cli v24.0.2+incompatible // indirect + github.com/docker/docker v24.0.2+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/envoyproxy/go-control-plane v0.11.0 // indirect - github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect - github.com/fatih/color v1.14.1 // indirect github.com/felixge/fgprof v0.9.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fxamacker/cbor/v2 v2.4.0 // indirect - github.com/go-gorp/gorp/v3 v3.0.2 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-resty/resty/v2 v2.7.0 // indirect - github.com/go-zookeeper/zk v1.0.3 // indirect github.com/gobwas/glob v0.2.3 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/googleapis/gax-go/v2 v2.7.1 // indirect - github.com/gophercloud/gophercloud v1.2.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect - github.com/hashicorp/consul/api v1.20.0 // indirect - github.com/hashicorp/cronexpr v1.1.1 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.5.0 // indirect - github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.2 // indirect - github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/nomad/api v0.0.0-20230308192510-48e7d70fcd4b // indirect - github.com/hashicorp/serf v0.10.1 // indirect - github.com/hetznercloud/hcloud-go v1.41.0 // indirect github.com/iancoleman/strcase v0.2.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/ionos-cloud/sdk-go/v6 v6.1.4 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.16.3 // indirect github.com/knadh/koanf v1.5.0 // indirect - github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect - github.com/linode/linodego v1.14.1 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mainflux/senml v1.5.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/miekg/dns v1.1.51 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mostynb/go-grpc-compression v1.1.17 // indirect - github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/nats-io/jwt/v2 v2.3.0 // indirect github.com/nats-io/nats-server/v2 v2.7.4 // indirect github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d // indirect @@ -164,19 +116,16 @@ require ( github.com/nats-io/nuid v1.0.1 // indirect github.com/oklog/ulid/v2 v2.0.2 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl v0.75.0 // indirect - github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.75.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.1.0 // indirect + github.com/opencontainers/runc v1.1.5 // indirect github.com/ory/keto/proto/ory/keto/acl/v1alpha1 v0.0.0-20210616104402-80e043246cf9 // indirect - github.com/ovh/go-ovh v1.3.0 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common/sigv4 v0.1.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect @@ -184,7 +133,6 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect - github.com/vultr/govultr/v2 v2.17.2 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -194,18 +142,16 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0 // indirect go.opentelemetry.io/otel v1.14.0 // indirect go.uber.org/atomic v1.10.0 // indirect - go.uber.org/goleak v1.2.1 // indirect golang.org/x/crypto v0.7.0 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/oauth2 v0.6.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.7.0 // indirect - google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect @@ -217,7 +163,6 @@ require ( //These libs are used to allow orb extend opentelemetry features require ( - cloud.google.com/go/compute v1.18.0 // indirect github.com/Shopify/sarama v1.37.2 github.com/apache/thrift v0.17.0 // indirect github.com/eapache/go-resiliency v1.3.0 // indirect @@ -229,7 +174,6 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/google/gnostic v0.6.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect @@ -244,7 +188,6 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.75.0 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.62.0 - github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.75.0 // indirect github.com/orb-community/diode v0.0.0-20230419222319-4ec19ba89e9f github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect diff --git a/go.sum b/go.sum index 750b4fb8a..09c6ef051 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -29,13 +28,10 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -48,39 +44,14 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= -github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= -github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= -github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc= -github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= -github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= -github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= @@ -99,21 +70,15 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.17.0 h1:cMd2aj52n+8VoAtvSvLn4kDC3aZ6IAkBuqWQ2IDu7wo= github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= -github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.44.232 h1:rZ9gv+v7GAcWspk1JMa28L3XamRwoiMzD1vphUIm8Xg= github.com/aws/aws-sdk-go v1.44.232/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= @@ -127,32 +92,28 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+ github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/immutable v0.4.3 h1:GYHcksoJ9K6HyAUpGxwZURrbTkXA0Dh4otXGqbhdrjA= +github.com/benbjohnson/immutable v0.4.3/go.mod h1:qJIKKSmdqz1tVzNtst1DZzvaqOU1onk1rc03IeM3Owk= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= -github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -162,49 +123,27 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43 h1:XP+uhjN0yBCN/tPkr8Z0BNDc5rZam9RG6UWyf2FrSQ0= -github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.2.2 h1:QSqfxcn8c+12slxwu00AtzXrsami0MJb/MQs9lOLHLA= -github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= -github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.97.0 h1:p9w1yCcWMZcxFSLPToNGXA96WfUVLXqoHti6GzVomL4= -github.com/digitalocean/godo v1.97.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA= -github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.12+incompatible h1:lZlz0uzG+GH+c0plStMUdF/qk3ppmgnswpR5EbqzVGA= -github.com/docker/cli v20.10.12+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY= -github.com/docker/docker v23.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/cli v24.0.2+incompatible h1:QdqR7znue1mtkXIJ+ruQMGQhpw2JzMJLRXp6zpzF6tM= +github.com/docker/cli v24.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v24.0.2+incompatible h1:eATx+oLz9WdNVkQrr0qjQ8HvRJ4bOOxfzEo8R+dA3cg= +github.com/docker/docker v24.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -230,17 +169,9 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.11.0 h1:jtLewhRR2vMRNnq2ZZUoCjUlgut+Y0+sDDWPOfwOi1o= -github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.9.1 h1:PS7VIOgmSVhWUEeZwTe7z7zouA22Cr590PzXKbZHOVY= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= @@ -268,8 +199,9 @@ github.com/go-co-op/gocron v1.9.0/go.mod h1:DbJm9kdgr1sEvWpHCA7dFFs/PGHPMil9/97E github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gorp/gorp/v3 v3.0.2 h1:ULqJXIekoqMx29FI5ekXXFoH1dT2Vc8UhnRzBg+Emz4= github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= @@ -289,7 +221,6 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= @@ -298,19 +229,15 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= -github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= github.com/go-zoo/bone v1.3.0 h1:PY6sHq37FnQhj+4ZyqFIzJQHvrrGx0GEc3vTZZC/OsI= github.com/go-zoo/bone v1.3.0/go.mod h1:HI3Lhb7G3UQcAwEhOJ2WyNcsFtQX1WYHa0Hl4OBbhW8= -github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= -github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= @@ -325,21 +252,15 @@ github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -372,7 +293,6 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -390,8 +310,6 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -419,70 +337,41 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gophercloud/gophercloud v1.2.0 h1:1oXyj4g54KBg/kFtCdMM6jtxSzeIyg8wv4z1HoGPp1E= -github.com/gophercloud/gophercloud v1.2.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD0O/HjhShYuM6XE0i/lbE6J94kww= -github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= -github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= -github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= -github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= -github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= -github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -496,8 +385,6 @@ github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4= -github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -505,20 +392,12 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= -github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= -github.com/hashicorp/nomad/api v0.0.0-20230308192510-48e7d70fcd4b h1:EkuSTU8c/63q4LMayj8ilgg/4I5PXDFVcnqKfs9qcwI= -github.com/hashicorp/nomad/api v0.0.0-20230308192510-48e7d70fcd4b/go.mod h1:bKUb1ytds5KwUioHdvdq9jmrDqCThv95si0Ub7iNeBg= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= -github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hetznercloud/hcloud-go v1.41.0 h1:KJGFRRc68QiVu4PrEP5BmCQVveCP2CM26UGQUKGpIUs= -github.com/hetznercloud/hcloud-go v1.41.0/go.mod h1:NaHg47L6C77mngZhwBG652dTAztYrsZ2/iITJKhQkHA= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs= github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= @@ -530,14 +409,11 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/ionos-cloud/sdk-go/v6 v6.1.4 h1:BJHhFA8Q1SZC7VOXqKKr2BV2ysQ2/4hlk1e4hZte7GY= -github.com/ionos-cloud/sdk-go/v6 v6.1.4/go.mod h1:Ox3W0iiEz0GHnfY9e5LmAxwklsxguuNFEUSu0gVRTME= github.com/jaegertracing/jaeger v1.38.1 h1:IunKLJl9Imgpxh3ZL+SD+E7KHYAkaeiOnjay9YeUl3o= github.com/jaegertracing/jaeger v1.38.1/go.mod h1:T5RFOZgRQBXR9rpQq8HsiIg39gu0DAYGQbDzpKw9gU8= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= @@ -560,13 +436,10 @@ github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -578,7 +451,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= @@ -586,8 +458,6 @@ github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0P github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs= github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= -github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= -github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= @@ -596,21 +466,16 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= -github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/linode/linodego v1.14.1 h1:uGxQyy0BidoEpLGdvfi4cPgEW+0YUFsEGrLEhcTfjNc= -github.com/linode/linodego v1.14.1/go.mod h1:NJlzvlNtdMRRkXb0oN6UWzUkj6t+IBsyveHgZ5Ppjyk= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -629,19 +494,11 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -654,8 +511,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfr github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= -github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo= -github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= @@ -664,15 +519,11 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= -github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -682,11 +533,9 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -694,14 +543,12 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/mostynb/go-grpc-compression v1.1.17 h1:N9t6taOJN3mNTTi0wDf4e3lp/G/ON1TP67Pn0vTUA9I= github.com/mostynb/go-grpc-compression v1.1.17/go.mod h1:FUSBr0QjKqQgoDG/e0yiqlR6aqyXC39+g/hFLDfSsEY= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI= github.com/nats-io/jwt/v2 v2.3.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= @@ -718,8 +565,6 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= @@ -732,50 +577,35 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= -github.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusremotewriteexporter v0.75.0 h1:5ze0Ej18NN6kUv71VHKKwKYac2q4WvWxjBU12wacOaM= -github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.75.0 h1:S/3OyvZ32sdQuN/KiPDZV8dQDMQMyhvb8aoR9w6ja1I= github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.75.0 h1:XW4DBJP3+dgdclPVA7d9aetG/FBUmwSNQGWaWoZnyo0= github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.75.0/go.mod h1:IyFOweK1oDRlcm4k+hdobQjvP5z3L5+5G7NBrQj1Kx0= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl v0.75.0 h1:BqTK0DHxiJ2Jih/MCvt3qJJwi6SxyP8hLSpAElLVUHk= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl v0.75.0/go.mod h1:XCBv1BOhtwwGmOBaPLtYelyVCLYAw5PwzyQKxZpsEkM= -github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.75.0 h1:QU3vgTIsLc42+n43ZYDcF7Cf7e3b24xVCExbpvu4jnY= -github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.75.0/go.mod h1:5oMhwmHLeTMb8BxMsa1Ip+eabpw9m2f1Cuk3fe1q2FQ= -github.com/open-telemetry/opentelemetry-collector-contrib/pkg/resourcetotelemetry v0.75.0 h1:YUku2qImuCj85X7LNGjToa3X1hJUd3VIBGVVbikGv08= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.62.0 h1:Oy2PdppooZrcUiBqHAOHrKK+rk+/+wScXEPMVKdDkcc= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.62.0/go.mod h1:WgMWz7+zb5KKN6BDx8rL+88M73BxvjQiRsgK9yEavis= -github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.75.0 h1:u/wsa7XjcQQMvO6deLfaz5B49DiQO1HCcNNuQgZCeqQ= -github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.75.0/go.mod h1:UqV4ls10rc9EDPlQLET0C4ajcKbkwvAbKX2jpIY3tuE= -github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheusremotewrite v0.75.0 h1:ZXz5ORMt7kV0pcr3AsazcRPVOyjDqV/J8Se9G1Qorjk= github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor v0.75.0 h1:8hSpocXftWJjo84a8E6Wa+ixOdgug3acVKblZbTM6sM= github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor v0.75.0/go.mod h1:BLIRUMliVdYdh6pFSNYRbbmShyqPr1sHwK/qYJSayx0= -github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.75.0 h1:LS7te1hRJf8zDL29rWxurpJmz6gcUea+LsY5BSaVugc= -github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.75.0/go.mod h1:iPmZSxNlYy3Nd99BEL41QJwlGz+I9vZFteUCInhP5c0= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runc v1.1.0 h1:O9+X96OcDjkmmZyfaG996kV7yq8HsoU2h1XRRQcefG8= -github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= +github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/orb-community/diode v0.0.0-20230419222319-4ec19ba89e9f h1:69y3FTEsmMS5EUUzkDIp/atM3AEZpQwRz4dUif/4Gbk= github.com/orb-community/diode v0.0.0-20230419222319-4ec19ba89e9f/go.mod h1:QrGOf+olmuV2fkA2zB8xU62b8xd7F+9lonHZo0B+Eak= -github.com/ory/dockertest/v3 v3.8.1 h1:vU/8d1We4qIad2YM0kOwRVtnyue7ExvacPiw1yDm17g= -github.com/ory/dockertest/v3 v3.8.1/go.mod h1:wSRQ3wmkz+uSARYMk7kVJFDBGm8x5gSxIhI7NDc+BAQ= +github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= +github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/ory/keto/proto/ory/keto/acl/v1alpha1 v0.0.0-20210616104402-80e043246cf9 h1:gP86NkMkUlqMOTjFQ8lt8T1HbHtCJGGeeeh/6c+nla0= github.com/ory/keto/proto/ory/keto/acl/v1alpha1 v0.0.0-20210616104402-80e043246cf9/go.mod h1:8IoeBQqIRKWU5L6dTKQTlTwVhlUawpqSBJZWfLLN4FM= -github.com/ovh/go-ovh v1.3.0 h1:mvZaddk4E4kLcXhzb+cxBsMPYp2pHqiQpWYkInsuZPQ= -github.com/ovh/go-ovh v1.3.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= @@ -798,58 +628,44 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= -github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/prometheus/prometheus v0.43.0 h1:18iCSfrbAHbXvYFvR38U1Pt4uZmU9SmDcCpCrBKUiGg= github.com/prometheus/prometheus v0.43.0/go.mod h1:2BA14LgBeqlPuzObSEbh+Y+JwLH2GcqDlJKbF2sA6FM= github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rubenv/sql-migrate v1.1.1 h1:haR5Hn8hbW9/SpAICrXoZqXnywS7Q5WijwkQENPeNWY= @@ -859,44 +675,30 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14 h1:yFl3jyaSVLNYXlnNYM5z2pagEk1dYQhfr1p20T1NyKY= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU= -github.com/shoenig/test v0.6.2 h1:tdq+WGnznwE5xcOMXkqqXuudK75RkSGBazBGcP1lX6w= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= @@ -913,7 +715,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= @@ -922,26 +723,13 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tidwall/gjson v1.10.2 h1:APbLGOM0rrEkd8WBw9C24nllro4ajFuJu0Sc9hRz8Bo= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/tinylru v1.1.0 h1:XY6IUfzVTU9rpwdhKUF6nQdChgCdGjkMfLzbWyiau6I= -github.com/tidwall/wal v1.1.7 h1:emc1TRjIVsdKKSnpwGBAcsAGg0767SvUk8+ygx7Bb+4= -github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= -github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= -github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= @@ -957,8 +745,6 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -966,10 +752,7 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= @@ -999,7 +782,6 @@ go.opentelemetry.io/collector/featuregate v0.75.0 h1:543kdhXh7/dHTwpHsjv+lgIz73R go.opentelemetry.io/collector/featuregate v0.75.0/go.mod h1:pmVMr98Ps6QKyEHiVPN7o3Qd8K//M2NapfOv5BMWvA0= go.opentelemetry.io/collector/pdata v1.0.0-rc9 h1:K1GND9w4hOMVE4lLpGt+0KvjIBcbsR54ZsijEyUQFFI= go.opentelemetry.io/collector/pdata v1.0.0-rc9/go.mod h1:olBmmDzT077Jyag/kVDAaG9OFkzLF6zSm8mfufL4HW4= -go.opentelemetry.io/collector/processor/batchprocessor v0.75.0 h1:eMRde/kjWdgpBKFWRS7Z1sfv20IvapdDMyOAV/I1ZZ8= go.opentelemetry.io/collector/receiver v0.75.0 h1:ZgoShBSTprt7vExTLtXTmEH05qIHU3tORhBWyk0PuB4= go.opentelemetry.io/collector/receiver v0.75.0/go.mod h1:MADsPYeztg9cGUZIjmv5ayzntt69blxfmmZHlgdM1Aw= go.opentelemetry.io/collector/receiver/otlpreceiver v0.75.0 h1:VXOt3k/zB/R2SxFNJQTDWm/KifCMW60m8Q3O5E2TV2c= @@ -1010,7 +792,6 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0/go.mod h1:UMklln0+MRhZC4e3PwmN3pCtq4DyIadWw4yikh6bNrw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 h1:lE9EJyw3/JhrjWH/hEy9FptnalDQgj7vpbgC2KCCCxE= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0/go.mod h1:pcQ3MM3SWvrA71U4GDqv9UFDJ3HQsW7y5ZO3tDTlUdI= -go.opentelemetry.io/contrib/propagators/b3 v1.15.0 h1:bMaonPyFcAvZ4EVzkUNkfnUHP5Zi63CIDlA3dRsEg8Q= go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= go.opentelemetry.io/otel/exporters/prometheus v0.37.0 h1:NQc0epfL0xItsmGgSXgfbH2C1fq2VLXkZoDFsfRNHpc= @@ -1021,17 +802,13 @@ go.opentelemetry.io/otel/sdk/metric v0.37.0 h1:haYBBtZZxiI3ROwSmkZnI+d0+AVzBWevi go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= @@ -1051,7 +828,6 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1092,7 +868,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1101,14 +876,12 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1142,16 +915,13 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1164,9 +934,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1179,14 +948,13 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1211,7 +979,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1231,9 +998,7 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1251,34 +1016,26 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1290,14 +1047,13 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1310,7 +1066,6 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1355,14 +1110,12 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1385,8 +1138,6 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1446,7 +1197,6 @@ google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVix google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -1484,8 +1234,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1497,15 +1247,12 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1521,9 +1268,7 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/maestro/config/authentication_builder.go b/maestro/config/authentication_builder.go new file mode 100644 index 000000000..1a49f5b2e --- /dev/null +++ b/maestro/config/authentication_builder.go @@ -0,0 +1,38 @@ +package config + +import ( + "github.com/orb-community/orb/pkg/types" + "github.com/orb-community/orb/sinks/authentication_type/basicauth" +) + +const AuthenticationKey = "authentication" + +type AuthBuilderService interface { + GetExtensionsFromMetadata(config types.Metadata) (Extensions, string) +} + +func GetAuthService(authType string) AuthBuilderService { + switch authType { + case basicauth.AuthType: + return &BasicAuthBuilder{} + } + return nil +} + +type BasicAuthBuilder struct { +} + +func (b *BasicAuthBuilder) GetExtensionsFromMetadata(config types.Metadata) (Extensions, string) { + + authcfg := config.GetSubMetadata(AuthenticationKey) + username := authcfg["username"].(string) + password := authcfg["password"].(string) + return Extensions{ + BasicAuth: &BasicAuthenticationExtension{ + ClientAuth: &ClientAuth{ + Username: username, + Password: password, + }, + }, + }, "basicauth/exporter" +} diff --git a/maestro/config/config_builder.go b/maestro/config/config_builder.go index 4e2ad6209..7ec609f19 100644 --- a/maestro/config/config_builder.go +++ b/maestro/config/config_builder.go @@ -97,16 +97,6 @@ var k8sOtelCollector = ` "protocol": "TCP" } ], - "resources": { - "limits": { - "cpu": "100m", - "memory": "200Mi" - }, - "requests": { - "cpu": "100m", - "memory": "200Mi" - } - }, "volumeMounts": [ { "name": "varlog", @@ -317,16 +307,6 @@ var JsonDeployment = ` "protocol": "TCP" } ], - "resources": { - "limits": { - "cpu": "100m", - "memory": "200Mi" - }, - "requests": { - "cpu": "100m", - "memory": "200Mi" - } - }, "volumeMounts": [ { "name": "varlog", @@ -373,90 +353,72 @@ var JsonDeployment = ` } ` -func GetDeploymentJson(kafkaUrl, sinkId, sinkUrl, sinkUsername, sinkPassword string) (string, error) { +func GetDeploymentJson(kafkaUrl string, sink SinkData) (string, error) { // prepare manifest - manifest := strings.Replace(k8sOtelCollector, "SINK_ID", sinkId, -1) - config, err := ReturnConfigYamlFromSink(context.Background(), kafkaUrl, sinkId, sinkUrl, sinkUsername, sinkPassword) + manifest := strings.Replace(k8sOtelCollector, "SINK_ID", sink.SinkID, -1) + config, err := ReturnConfigYamlFromSink(context.Background(), kafkaUrl, sink) if err != nil { - return "", errors.Wrap(errors.New("failed to build YAML"), err) + return "", errors.Wrap(errors.New(fmt.Sprintf("failed to build YAML, sink: %s", sink.SinkID)), err) } manifest = strings.Replace(manifest, "SINK_CONFIG", config, -1) return manifest, nil } -func GetDeploymentApplyConfig(sinkId string) string { - manifest := strings.Replace(JsonDeployment, "SINK_ID", sinkId, -1) - return manifest -} - -func GetConfigMapApplyConfig(kafkaUrl, sinkId, sinkUrl, sinkUsername, sinkPassword string) (string, error) { - manifest := strings.Replace(JsonConfigMap, "SINK_ID", sinkId, -1) - config, err := ReturnConfigYamlFromSink(context.Background(), kafkaUrl, sinkId, sinkUrl, sinkUsername, sinkPassword) - if err != nil { - return "", errors.Wrap(errors.New("failed to build YAML"), err) +// ReturnConfigYamlFromSink this is the main method, which will generate the YAML file from the +func ReturnConfigYamlFromSink(_ context.Context, kafkaUrlConfig string, sink SinkData) (string, error) { + authType := sink.Config.GetSubMetadata(AuthenticationKey)["type"] + authTypeStr, ok := authType.(string) + if !ok { + return "", errors.New("failed to create config invalid authentication type") + } + authBuilder := GetAuthService(authTypeStr) + if authBuilder == nil { + return "", errors.New("invalid authentication type") + } + exporterBuilder := FromStrategy(sink.Backend) + if exporterBuilder == nil { + return "", errors.New("invalid backend") + } + extensions, extensionName := authBuilder.GetExtensionsFromMetadata(sink.Config) + exporters, exporterName := exporterBuilder.GetExportersFromMetadata(sink.Config, extensionName) + if exporterName == "" { + return "", errors.New("failed to build exporter") } - manifest = strings.Replace(manifest, "SINK_CONFIG", config, -1) - return manifest, nil -} - -func GetServiceApplyConfig(sinkId string) string { - manifest := strings.Replace(JsonService, "SINK_ID", sinkId, -1) - return manifest -} -// ReturnConfigYamlFromSink this is the main method, which will generate the YAML file from the -func ReturnConfigYamlFromSink(_ context.Context, kafkaUrlConfig, sinkId, sinkUrl, sinkUsername, sinkPassword string) (string, error) { + // Add prometheus extension for metrics + extensions.PProf = &PProfExtension{ + Endpoint: "0.0.0.0:1888", + } + serviceConfig := ServiceConfig{ + Extensions: []string{"pprof", extensionName}, + Pipelines: struct { + Metrics struct { + Receivers []string `json:"receivers" yaml:"receivers"` + Processors []string `json:"processors,omitempty" yaml:"processors,omitempty"` + Exporters []string `json:"exporters" yaml:"exporters"` + } `json:"metrics" yaml:"metrics"` + }{ + Metrics: struct { + Receivers []string `json:"receivers" yaml:"receivers"` + Processors []string `json:"processors,omitempty" yaml:"processors,omitempty"` + Exporters []string `json:"exporters" yaml:"exporters"` + }{ + Receivers: []string{"kafka"}, + Exporters: []string{exporterName}, + }, + }, + } config := OtelConfigFile{ Receivers: Receivers{ Kafka: KafkaReceiver{ Brokers: []string{kafkaUrlConfig}, - Topic: fmt.Sprintf("otlp_metrics-%s", sinkId), - ProtocolVersion: "2.0.0", // Leaving default of over 2.0.0 - }, - }, - Extensions: &Extensions{ - PProf: &PProfExtension{ - Endpoint: "0.0.0.0:1888", // Leaving default for now, will need to change with more processes - }, - BasicAuth: &BasicAuthenticationExtension{ - ClientAuth: &struct { - Username string `json:"username" yaml:"username"` - Password string `json:"password" yaml:"password"` - }{Username: sinkUsername, Password: sinkPassword}, - }, - }, - Exporters: Exporters{ - PrometheusRemoteWrite: &PrometheusRemoteWriteExporterConfig{ - Endpoint: sinkUrl, - Auth: struct { - Authenticator string `json:"authenticator" yaml:"authenticator"` - }{Authenticator: "basicauth/exporter"}, - }, - LoggingExporter: &LoggingExporterConfig{ - Verbosity: "detailed", - SamplingInitial: 5, - SamplingThereAfter: 50, - }, - }, - Service: ServiceConfig{ - Extensions: []string{"pprof", "basicauth/exporter"}, - Pipelines: struct { - Metrics struct { - Receivers []string `json:"receivers" yaml:"receivers"` - Processors []string `json:"processors,omitempty" yaml:"processors,omitempty"` - Exporters []string `json:"exporters" yaml:"exporters"` - } `json:"metrics" yaml:"metrics"` - }{ - Metrics: struct { - Receivers []string `json:"receivers" yaml:"receivers"` - Processors []string `json:"processors,omitempty" yaml:"processors,omitempty"` - Exporters []string `json:"exporters" yaml:"exporters"` - }{ - Receivers: []string{"kafka"}, - Exporters: []string{"prometheusremotewrite"}, - }, + Topic: fmt.Sprintf("otlp_metrics-%s", sink.SinkID), + ProtocolVersion: "2.0.0", }, }, + Extensions: &extensions, + Exporters: exporters, + Service: serviceConfig, } marshal, err := yaml.Marshal(&config) if err != nil { @@ -467,96 +429,3 @@ func ReturnConfigYamlFromSink(_ context.Context, kafkaUrlConfig, sinkId, sinkUrl s = strings.ReplaceAll(s, "\n", `\n`) return s, nil } - -type OtelConfigFile struct { - Receivers Receivers `json:"receivers" yaml:"receivers"` - Processors *Processors `json:"processors,omitempty" yaml:"processors,omitempty"` - Extensions *Extensions `json:"extensions,omitempty" yaml:"extensions,omitempty"` - Exporters Exporters `json:"exporters" yaml:"exporters"` - Service ServiceConfig `json:"service" yaml:"service"` -} - -// Receivers will receive only with Kafka for now -type Receivers struct { - Kafka KafkaReceiver `json:"kafka" yaml:"kafka"` -} - -type KafkaReceiver struct { - Brokers []string `json:"brokers" yaml:"brokers"` - Topic string `json:"topic" yaml:"topic"` - ProtocolVersion string `json:"protocol_version" yaml:"protocol_version"` -} - -type Processors struct { -} - -type Extensions struct { - HealthCheckExtConfig *HealthCheckExtension `json:"health_check,omitempty" yaml:"health_check,omitempty" :"health_check_ext_config"` - PProf *PProfExtension `json:"pprof,omitempty" yaml:"pprof,omitempty" :"p_prof"` - ZPages *ZPagesExtension `json:"zpages,omitempty" yaml:"zpages,omitempty" :"z_pages"` - // Exporters Authentication - BasicAuth *BasicAuthenticationExtension `json:"basicauth/exporter,omitempty" yaml:"basicauth/exporter,omitempty" :"basic_auth"` - //BearerAuth *BearerAuthExtension `json:"bearerauth/exporter,omitempty" yaml:"bearerauth/exporter,omitempty" :"bearer_auth"` -} - -type HealthCheckExtension struct { - Endpoint string `json:"endpoint" yaml:"endpoint"` - Path string `json:"path" yaml:"path"` - CollectorPipeline *CollectorPipelineExtension `json:"check_collector_pipeline,omitempty" yaml:"check_collector_pipeline,omitempty"` -} - -type CollectorPipelineExtension struct { - Enabled string `json:"enabled" yaml:"enabled"` - Interval string `json:"interval" yaml:"interval"` - FailureThreshold int32 `json:"exporter_failure_threshold" yaml:"exporter_failure_threshold"` -} - -type PProfExtension struct { - Endpoint string `json:"endpoint" yaml:"endpoint"` -} - -type ZPagesExtension struct { - Endpoint string `json:"endpoint" yaml:"endpoint"` -} - -type BasicAuthenticationExtension struct { - ClientAuth *struct { - Username string `json:"username" yaml:"username"` - Password string `json:"password" yaml:"password"` - } `json:"client_auth" yaml:"client_auth"` -} - -type BearerAuthExtension struct { - BearerAuth *struct { - Token string `json:"token" yaml:"token"` - } `json:"client_auth" yaml:"client_auth"` -} - -type Exporters struct { - PrometheusRemoteWrite *PrometheusRemoteWriteExporterConfig `json:"prometheusremotewrite,omitempty" yaml:"prometheusremotewrite,omitempty"` - LoggingExporter *LoggingExporterConfig `json:"logging,omitempty" yaml:"logging,omitempty"` -} - -type LoggingExporterConfig struct { - Verbosity string `json:"verbosity,omitempty" yaml:"verbosity,omitempty"` - SamplingInitial int `json:"sampling_initial,omitempty" yaml:"sampling_initial,omitempty"` - SamplingThereAfter int `json:"sampling_thereafter,omitempty" yaml:"sampling_thereafter,omitempty"` -} - -type PrometheusRemoteWriteExporterConfig struct { - Endpoint string `json:"endpoint" yaml:"endpoint"` - Auth struct { - Authenticator string `json:"authenticator" yaml:"authenticator"` - } -} - -type ServiceConfig struct { - Extensions []string `json:"extensions,omitempty" yaml:"extensions,omitempty"` - Pipelines struct { - Metrics struct { - Receivers []string `json:"receivers" yaml:"receivers"` - Processors []string `json:"processors,omitempty" yaml:"processors,omitempty"` - Exporters []string `json:"exporters" yaml:"exporters"` - } `json:"metrics" yaml:"metrics"` - } `json:"pipelines" yaml:"pipelines"` -} diff --git a/maestro/config/config_builder_test.go b/maestro/config/config_builder_test.go index 5af7030e7..9e7cd2d04 100644 --- a/maestro/config/config_builder_test.go +++ b/maestro/config/config_builder_test.go @@ -3,17 +3,16 @@ package config import ( "context" "fmt" + "github.com/orb-community/orb/pkg/types" "testing" + "time" ) func TestReturnConfigYamlFromSink(t *testing.T) { type args struct { in0 context.Context kafkaUrlConfig string - sinkId string - sinkUrl string - sinkUsername string - sinkPassword string + sink SinkData } tests := []struct { name string @@ -21,19 +20,94 @@ func TestReturnConfigYamlFromSink(t *testing.T) { want string wantErr bool }{ - {name: "simple test", args: args{ - in0: context.Background(), - kafkaUrlConfig: "kafka:9092", - sinkId: "sink-id-222", - sinkUrl: "https://mysinkurl:9922", - sinkUsername: "1234123", - sinkPassword: "CarnivorousVulgaris", - }, want: `---\nreceivers:\n kafka:\n brokers:\n - kafka:9092\n topic: otlp_metrics-sink-id-222\n protocol_version: 2.0.0\nextensions:\n pprof:\n endpoint: 0.0.0.0:1888\n basicauth/exporter:\n client_auth:\n username: 1234123\n password: CarnivorousVulgaris\nexporters:\n prometheusremotewrite:\n endpoint: https://mysinkurl:9922\n auth:\n authenticator: basicauth/exporter\n logging:\n verbosity: detailed\n sampling_initial: 5\n sampling_thereafter: 50\nservice:\n extensions:\n - pprof\n - basicauth/exporter\n pipelines:\n metrics:\n receivers:\n - kafka\n exporters:\n - prometheusremotewrite\n`, - wantErr: false}, + { + name: "prometheus, basicauth", + args: args{ + in0: context.Background(), + kafkaUrlConfig: "kafka:9092", + sink: SinkData{ + SinkID: "sink-id-11", + OwnerID: "11", + Backend: "prometheus", + Config: types.Metadata{ + "exporter": types.Metadata{ + "remote_host": "https://acme.com/prom/push", + }, + "authentication": types.Metadata{ + "type": "basicauth", + "username": "prom-user", + "password": "dbpass", + }, + }, + State: 0, + Msg: "", + LastRemoteWrite: time.Time{}, + }, + }, + want: `---\nreceivers:\n kafka:\n brokers:\n - kafka:9092\n topic: otlp_metrics-sink-id-11\n protocol_version: 2.0.0\nextensions:\n pprof:\n endpoint: 0.0.0.0:1888\n basicauth/exporter:\n client_auth:\n username: prom-user\n password: dbpass\nexporters:\n prometheusremotewrite:\n endpoint: https://acme.com/prom/push\n auth:\n authenticator: basicauth/exporter\nservice:\n extensions:\n - pprof\n - basicauth/exporter\n pipelines:\n metrics:\n receivers:\n - kafka\n exporters:\n - prometheusremotewrite\n`, + wantErr: false, + }, + { + name: "prometheus, basicauth, with headers", + args: args{ + in0: context.Background(), + kafkaUrlConfig: "kafka:9092", + sink: SinkData{ + SinkID: "sink-id-11", + OwnerID: "11", + Backend: "prometheus", + Config: types.Metadata{ + "exporter": types.Metadata{ + "remote_host": "https://acme.com/prom/push", + "headers": map[string]interface{}{ + "X-Scope-OrgID": "TENANT_1", + }, + }, + "authentication": types.Metadata{ + "type": "basicauth", + "username": "prom-user", + "password": "dbpass", + }, + }, + State: 0, + Msg: "", + LastRemoteWrite: time.Time{}, + }, + }, + want: `---\nreceivers:\n kafka:\n brokers:\n - kafka:9092\n topic: otlp_metrics-sink-id-11\n protocol_version: 2.0.0\nextensions:\n pprof:\n endpoint: 0.0.0.0:1888\n basicauth/exporter:\n client_auth:\n username: prom-user\n password: dbpass\nexporters:\n prometheusremotewrite:\n endpoint: https://acme.com/prom/push\n headers:\n X-Scope-OrgID: TENANT_1\n auth:\n authenticator: basicauth/exporter\nservice:\n extensions:\n - pprof\n - basicauth/exporter\n pipelines:\n metrics:\n receivers:\n - kafka\n exporters:\n - prometheusremotewrite\n`, + wantErr: false, + }, + { + name: "otlp, basicauth", + args: args{ + in0: context.Background(), + kafkaUrlConfig: "kafka:9092", + sink: SinkData{ + SinkID: "sink-id-22", + OwnerID: "22", + Backend: "otlphttp", + Config: types.Metadata{ + "exporter": types.Metadata{ + "endpoint": "https://acme.com/otlphttp/push", + }, + "authentication": types.Metadata{ + "type": "basicauth", + "username": "otlp-user", + "password": "dbpass", + }, + }, + State: 0, + Msg: "", + LastRemoteWrite: time.Time{}, + }, + }, + want: `---\nreceivers:\n kafka:\n brokers:\n - kafka:9092\n topic: otlp_metrics-sink-id-22\n protocol_version: 2.0.0\nextensions:\n pprof:\n endpoint: 0.0.0.0:1888\n basicauth/exporter:\n client_auth:\n username: otlp-user\n password: dbpass\nexporters:\n otlphttp:\n endpoint: https://acme.com/otlphttp/push\n auth:\n authenticator: basicauth/exporter\nservice:\n extensions:\n - pprof\n - basicauth/exporter\n pipelines:\n metrics:\n receivers:\n - kafka\n exporters:\n - otlphttp\n`, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := ReturnConfigYamlFromSink(tt.args.in0, tt.args.kafkaUrlConfig, tt.args.sinkId, tt.args.sinkUrl, tt.args.sinkUsername, tt.args.sinkPassword) + got, err := ReturnConfigYamlFromSink(tt.args.in0, tt.args.kafkaUrlConfig, tt.args.sink) if (err != nil) != tt.wantErr { t.Errorf("ReturnConfigYamlFromSink() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/maestro/config/exporter_builder.go b/maestro/config/exporter_builder.go new file mode 100644 index 000000000..a6f27af97 --- /dev/null +++ b/maestro/config/exporter_builder.go @@ -0,0 +1,73 @@ +package config + +import "github.com/orb-community/orb/pkg/types" + +type ExporterConfigService interface { + GetExportersFromMetadata(config types.Metadata, authenticationExtensionName string) (Exporters, string) +} + +func FromStrategy(backend string) ExporterConfigService { + switch backend { + case "prometheus": + return &PrometheusExporterConfig{} + case "otlphttp": + return &OTLPHTTPExporterBuilder{} + } + + return nil +} + +type PrometheusExporterConfig struct { +} + +func (p *PrometheusExporterConfig) GetExportersFromMetadata(config types.Metadata, authenticationExtensionName string) (Exporters, string) { + exporterSubMeta := config.GetSubMetadata("exporter") + if exporterSubMeta == nil { + return Exporters{}, "" + } + endpointCfg, ok := exporterSubMeta["remote_host"].(string) + if !ok { + return Exporters{}, "" + } + customHeaders, ok := exporterSubMeta["headers"] + if !ok || customHeaders == nil { + return Exporters{ + PrometheusRemoteWrite: &PrometheusRemoteWriteExporterConfig{ + Endpoint: endpointCfg, + Auth: Auth{Authenticator: authenticationExtensionName}, + }, + }, "prometheusremotewrite" + } + return Exporters{ + PrometheusRemoteWrite: &PrometheusRemoteWriteExporterConfig{ + Endpoint: endpointCfg, + Auth: Auth{Authenticator: authenticationExtensionName}, + Headers: customHeaders.(map[string]interface{}), + }, + }, "prometheusremotewrite" +} + +type OTLPHTTPExporterBuilder struct { +} + +func (O *OTLPHTTPExporterBuilder) GetExportersFromMetadata(config types.Metadata, authenticationExtensionName string) (Exporters, string) { + exporterSubMeta := config.GetSubMetadata("exporter") + endpointCfg := exporterSubMeta["endpoint"].(string) + customHeaders, ok := exporterSubMeta["headers"] + if !ok || customHeaders == nil { + return Exporters{ + OTLPExporter: &OTLPExporterConfig{ + Endpoint: endpointCfg, + Auth: Auth{Authenticator: authenticationExtensionName}, + }, + }, "otlphttp" + } else { + return Exporters{ + OTLPExporter: &OTLPExporterConfig{ + Endpoint: endpointCfg, + Auth: Auth{Authenticator: authenticationExtensionName}, + Headers: customHeaders.(map[string]interface{}), + }, + }, "otlphttp" + } +} diff --git a/maestro/config/types.go b/maestro/config/types.go index 74df590b4..637abb2c2 100644 --- a/maestro/config/types.go +++ b/maestro/config/types.go @@ -2,19 +2,16 @@ package config import ( "database/sql/driver" + "github.com/orb-community/orb/pkg/types" "time" ) type SinkData struct { SinkID string `json:"sink_id"` OwnerID string `json:"owner_id"` - Url string `json:"remote_host"` - User string `json:"username"` - Password string `json:"password"` - Token string `json:"token"` - OpenTelemetry string `json:"opentelemetry"` + Backend string `json:"backend"` + Config types.Metadata `json:"config"` State PrometheusState `json:"state,omitempty"` - Migrate string `json:"migrate,omitempty"` Msg string `json:"msg,omitempty"` LastRemoteWrite time.Time `json:"last_remote_write,omitempty"` } @@ -57,3 +54,112 @@ func (p *PrometheusState) SetFromString(value string) error { func (p PrometheusState) Value() (driver.Value, error) { return p.String(), nil } + +type OtelConfigFile struct { + Receivers Receivers `json:"receivers" yaml:"receivers"` + Processors *Processors `json:"processors,omitempty" yaml:"processors,omitempty"` + Extensions *Extensions `json:"extensions,omitempty" yaml:"extensions,omitempty"` + Exporters Exporters `json:"exporters" yaml:"exporters"` + Service ServiceConfig `json:"service" yaml:"service"` +} + +// Receivers will receive only with Kafka for now +type Receivers struct { + Kafka KafkaReceiver `json:"kafka" yaml:"kafka"` +} + +type KafkaReceiver struct { + Brokers []string `json:"brokers" yaml:"brokers"` + Topic string `json:"topic" yaml:"topic"` + ProtocolVersion string `json:"protocol_version" yaml:"protocol_version"` +} + +type Processors struct { +} + +type Extensions struct { + HealthCheckExtConfig *HealthCheckExtension `json:"health_check,omitempty" yaml:"health_check,omitempty" :"health_check_ext_config"` + PProf *PProfExtension `json:"pprof,omitempty" yaml:"pprof,omitempty" :"p_prof"` + ZPages *ZPagesExtension `json:"zpages,omitempty" yaml:"zpages,omitempty" :"z_pages"` + // Exporters Authentication + BasicAuth *BasicAuthenticationExtension `json:"basicauth/exporter,omitempty" yaml:"basicauth/exporter,omitempty" :"basic_auth"` + //BearerAuth *BearerAuthExtension `json:"bearerauth/exporter,omitempty" yaml:"bearerauth/exporter,omitempty" :"bearer_auth"` +} + +type HealthCheckExtension struct { + Endpoint string `json:"endpoint" yaml:"endpoint"` + Path string `json:"path" yaml:"path"` + CollectorPipeline *CollectorPipelineExtension `json:"check_collector_pipeline,omitempty" yaml:"check_collector_pipeline,omitempty"` +} + +type CollectorPipelineExtension struct { + Enabled string `json:"enabled" yaml:"enabled"` + Interval string `json:"interval" yaml:"interval"` + FailureThreshold int32 `json:"exporter_failure_threshold" yaml:"exporter_failure_threshold"` +} + +type PProfExtension struct { + Endpoint string `json:"endpoint" yaml:"endpoint"` +} + +type ZPagesExtension struct { + Endpoint string `json:"endpoint" yaml:"endpoint"` +} + +type ClientAuth struct { + Username string `json:"username" yaml:"username"` + Password string `json:"password" yaml:"password"` +} + +type BasicAuthenticationExtension struct { + ClientAuth *ClientAuth `json:"client_auth" yaml:"client_auth"` +} + +type BearerAuthExtension struct { + BearerAuth *struct { + Token string `json:"token" yaml:"token"` + } `json:"client_auth" yaml:"client_auth"` +} + +type Exporters struct { + PrometheusRemoteWrite *PrometheusRemoteWriteExporterConfig `json:"prometheusremotewrite,omitempty" yaml:"prometheusremotewrite,omitempty"` + OTLPExporter *OTLPExporterConfig `json:"otlphttp,omitempty" yaml:"otlphttp,omitempty"` + LoggingExporter *LoggingExporterConfig `json:"logging,omitempty" yaml:"logging,omitempty"` +} + +type LoggingExporterConfig struct { + Verbosity string `json:"verbosity,omitempty" yaml:"verbosity,omitempty"` + SamplingInitial int `json:"sampling_initial,omitempty" yaml:"sampling_initial,omitempty"` + SamplingThereAfter int `json:"sampling_thereafter,omitempty" yaml:"sampling_thereafter,omitempty"` +} + +type OTLPExporterConfig struct { + Endpoint string `json:"endpoint" yaml:"endpoint"` + Headers map[string]interface{} `json:"headers,omitempty" yaml:"headers,omitempty"` + Auth struct { + Authenticator string `json:"authenticator" yaml:"authenticator"` + } +} + +type Auth struct { + Authenticator string `json:"authenticator" yaml:"authenticator"` +} + +type PrometheusRemoteWriteExporterConfig struct { + Endpoint string `json:"endpoint" yaml:"endpoint"` + Headers map[string]interface{} `json:"headers,omitempty" yaml:"headers,omitempty"` + Auth struct { + Authenticator string `json:"authenticator" yaml:"authenticator"` + } +} + +type ServiceConfig struct { + Extensions []string `json:"extensions,omitempty" yaml:"extensions,omitempty"` + Pipelines struct { + Metrics struct { + Receivers []string `json:"receivers" yaml:"receivers"` + Processors []string `json:"processors,omitempty" yaml:"processors,omitempty"` + Exporters []string `json:"exporters" yaml:"exporters"` + } `json:"metrics" yaml:"metrics"` + } `json:"pipelines" yaml:"pipelines"` +} diff --git a/maestro/kubecontrol/kubecontrol.go b/maestro/kubecontrol/kubecontrol.go index ffea58af8..edf83ab7c 100644 --- a/maestro/kubecontrol/kubecontrol.go +++ b/maestro/kubecontrol/kubecontrol.go @@ -49,6 +49,9 @@ type Service interface { // UpdateOtelCollector - update an existing collector by id UpdateOtelCollector(ctx context.Context, ownerID, sinkID, deploymentEntry string) error + + // KillOtelCollector - kill an existing collector by id, terminating by the ownerID, sinkID without the file + KillOtelCollector(ctx context.Context, ownerID, sinkID string) error } func (svc *deployService) collectorDeploy(ctx context.Context, operation, ownerID, sinkId, manifest string) error { @@ -157,3 +160,23 @@ func (svc *deployService) DeleteOtelCollector(ctx context.Context, ownerID, sink } return nil } + +func (svc *deployService) KillOtelCollector(ctx context.Context, deploymentName string, sinkId string) error { + stdOutListenFunction := func(out *bufio.Scanner, err *bufio.Scanner) { + for out.Scan() { + svc.logger.Info("Deploy Info: " + out.Text()) + } + for err.Scan() { + svc.logger.Info("Deploy Error: " + err.Text()) + } + } + + // execute action + cmd := exec.Command("kubectl", "delete", "deploy", deploymentName, "-n", namespace) + _, _, err := execCmd(ctx, cmd, svc.logger, stdOutListenFunction) + if err == nil { + svc.logger.Info(fmt.Sprintf("successfully killed the otel-collector for sink-id: %s", sinkId)) + } + + return nil +} diff --git a/maestro/monitor/monitor.go b/maestro/monitor/monitor.go index 5809c7d3b..c9fa10ccc 100644 --- a/maestro/monitor/monitor.go +++ b/maestro/monitor/monitor.go @@ -71,7 +71,7 @@ func (svc *monitorService) Start(ctx context.Context, cancelFunc context.CancelF } func (svc *monitorService) getPodLogs(ctx context.Context, pod k8scorev1.Pod) ([]string, error) { - maxTailLines := int64(10) + maxTailLines := int64(1) sinceSeconds := int64(300) podLogOpts := k8scorev1.PodLogOptions{TailLines: &maxTailLines, SinceSeconds: &sinceSeconds} config, err := rest.InClusterConfig() @@ -105,7 +105,6 @@ func (svc *monitorService) getPodLogs(ctx context.Context, pod k8scorev1.Pod) ([ } str := buf.String() splitLogs := strings.Split(str, "\n") - svc.logger.Info("logs length", zap.Int("amount line logs", len(splitLogs))) return splitLogs, nil } @@ -141,6 +140,7 @@ func (svc *monitorService) getRunningPods(ctx context.Context) ([]k8scorev1.Pod, } func (svc *monitorService) monitorSinks(ctx context.Context) { + runningCollectors, err := svc.getRunningPods(ctx) if err != nil { svc.logger.Error("error getting running pods on namespace", zap.Error(err)) @@ -160,17 +160,21 @@ func (svc *monitorService) monitorSinks(ctx context.Context) { var sink *sinkspb.SinkRes for _, sinkRes := range sinksRes.Sinks { if strings.Contains(collector.Name, sinkRes.Id) { - svc.logger.Warn("collector found for sink", zap.String("collector name", collector.Name), zap.String("sink", sinkRes.Id)) sink = sinkRes break } } if sink == nil { svc.logger.Warn("collector not found for sink, depleting collector", zap.String("collector name", collector.Name)) - sinkId := collector.Name[5:51] + sinkId := collector.Name[5:41] deploymentEntry, err := svc.eventStore.GetDeploymentEntryFromSinkId(ctx, sinkId) if err != nil { svc.logger.Error("did not find collector entry for sink", zap.String("sink-id", sinkId)) + deploymentName := "otel-" + sinkId + err = svc.kubecontrol.KillOtelCollector(ctx, deploymentName, sinkId) + if err != nil { + svc.logger.Error("error removing otel collector, manual intervention required", zap.Error(err)) + } continue } err = svc.kubecontrol.DeleteOtelCollector(ctx, "", sinkId, deploymentEntry) @@ -186,52 +190,38 @@ func (svc *monitorService) monitorSinks(ctx context.Context) { } data.SinkID = sink.Id data.OwnerID = sink.OwnerID - // only analyze logs if current status is "active" or "warning" var logsErr error var status string - if sink.GetState() == "active" || sink.GetState() == "warning" { - logs, err := svc.getPodLogs(ctx, collector) - if err != nil { - svc.logger.Error("error on getting logs, skipping", zap.Error(err)) - continue - } - status, logsErr = svc.analyzeLogs(logs) - if status == "fail" { - svc.logger.Error("error during analyze logs", zap.Error(logsErr)) - continue - } + logs, err := svc.getPodLogs(ctx, collector) + if err != nil { + svc.logger.Error("error on getting logs, skipping", zap.Error(err)) + continue + } + status, logsErr = svc.analyzeLogs(logs) + if status == "fail" { + svc.logger.Error("error during analyze logs", zap.Error(logsErr)) + continue } - var lastActivity int64 - var activityErr error - // if log analysis return active or warning we should check if have activity on collector - if status == "active" || status == "warning" { - lastActivity, activityErr = svc.eventStore.GetActivity(sink.Id) - // if logs reported 'active' status - // here we should check if LastActivity is up-to-date, otherwise we need to set sink as idle - var idleLimit int64 = 0 - if activityErr != nil || lastActivity == 0 { - svc.logger.Error("error on getting last collector activity", zap.Error(activityErr)) + lastActivity, activityErr := svc.eventStore.GetActivity(sink.Id) + // if logs reported 'active' status + // here we should check if LastActivity is up-to-date, otherwise we need to set sink as idle + idleLimit := time.Now().Unix() - idleTimeSeconds // within 10 minutes + if idleLimit >= lastActivity { + //changing state on sinks + svc.eventStore.PublishSinkStateChange(sink, "idle", logsErr, err) + //changing state on redis sinker + data.State.SetFromString("idle") + svc.eventStore.UpdateSinkStateCache(ctx, data) + deploymentEntry, errDeploy := svc.eventStore.GetDeploymentEntryFromSinkId(ctx, sink.Id) + if errDeploy != nil { + svc.logger.Error("Remove collector: error on getting collector deployment from redis", zap.Error(activityErr)) continue - } else { - idleLimit = time.Now().Unix() - idleTimeSeconds // within 10 minutes } - if idleLimit >= lastActivity { - //changing state on sinks - svc.eventStore.PublishSinkStateChange(sink, "idle", logsErr, err) - //changing state on redis sinker - data.State.SetFromString("idle") - svc.eventStore.UpdateSinkStateCache(ctx, data) - deploymentEntry, errDeploy := svc.eventStore.GetDeploymentEntryFromSinkId(ctx, sink.Id) - if errDeploy != nil { - svc.logger.Error("Remove collector: error on getting collector deployment from redis", zap.Error(activityErr)) - continue - } - err = svc.kubecontrol.DeleteOtelCollector(ctx, sink.OwnerID, sink.Id, deploymentEntry) - if err != nil { - svc.logger.Error("error removing otel collector", zap.Error(err)) - } - continue + err = svc.kubecontrol.DeleteOtelCollector(ctx, sink.OwnerID, sink.Id, deploymentEntry) + if err != nil { + svc.logger.Error("error removing otel collector", zap.Error(err)) } + continue } //set the new sink status if changed during checks if sink.GetState() != status && status != "" { @@ -283,7 +273,7 @@ func (svc *monitorService) analyzeLogs(logEntry []string) (status string, err er if strings.Contains(logLine, "400 Bad Request") { errorMessage := "error: remote write returned HTTP status 400 Bad Request" return "warning", errors.New(errorMessage) - } + } // other generic errors if strings.Contains(logLine, "error") { errStringLog := strings.TrimRight(logLine, "error") diff --git a/maestro/redis/consumer/hashset.go b/maestro/redis/consumer/hashset.go index 25d04512c..3893c7fc4 100644 --- a/maestro/redis/consumer/hashset.go +++ b/maestro/redis/consumer/hashset.go @@ -35,11 +35,19 @@ func (es eventStore) GetDeploymentEntryFromSinkId(ctx context.Context, sinkId st // handleSinksDeleteCollector will delete Deployment Entry and force delete otel collector func (es eventStore) handleSinksDeleteCollector(ctx context.Context, event redis.SinksUpdateEvent) error { es.logger.Info("Received maestro DELETE event from sinks ID", zap.String("sinkID", event.SinkID), zap.String("owner", event.Owner)) + err := es.RemoveSinkActivity(ctx, event.SinkID) + if err != nil { + return err + } deploymentEntry, err := es.GetDeploymentEntryFromSinkId(ctx, event.SinkID) if err != nil { es.logger.Error("did not find collector entry for sink", zap.String("sink-id", event.SinkID)) return err } + err = es.sinkerKeyRedisClient.HDel(ctx, deploymentKey, event.SinkID).Err() + if err != nil { + return err + } err = es.kubecontrol.DeleteOtelCollector(ctx, event.Owner, event.SinkID, deploymentEntry) if err != nil { return err @@ -57,21 +65,18 @@ func (es eventStore) handleSinksCreateCollector(ctx context.Context, event redis if err != nil { es.logger.Error("could not fetch info for sink", zap.String("sink-id", event.SinkID), zap.Error(err)) } - var data config.SinkData - if err := json.Unmarshal(sinkData.Config, &data); err != nil { + + var metadata types.Metadata + if err := json.Unmarshal(sinkData.Config, &metadata); err != nil { return err } - sinkUrl := data.Url - var sinkUsername string - var sinkPassword string - if data.User != "" { - sinkUsername = data.User - sinkPassword = data.Password - } else { - sinkPassword = data.Token + data := config.SinkData{ + SinkID: sinkData.Id, + OwnerID: sinkData.OwnerID, + Backend: sinkData.Backend, + Config: metadata, } - - err2 := es.CreateDeploymentEntry(ctx, event.SinkID, sinkUrl, sinkUsername, sinkPassword) + err2 := es.CreateDeploymentEntry(ctx, data) if err2 != nil { return err2 } @@ -79,14 +84,14 @@ func (es eventStore) handleSinksCreateCollector(ctx context.Context, event redis return nil } -func (es eventStore) CreateDeploymentEntry(ctx context.Context, sinkId, sinkUrl, sinkUsername, sinkPassword string) error { - deploy, err := config.GetDeploymentJson(es.kafkaUrl, sinkId, sinkUrl, sinkUsername, sinkPassword) +func (es eventStore) CreateDeploymentEntry(ctx context.Context, sink config.SinkData) error { + deploy, err := config.GetDeploymentJson(es.kafkaUrl, sink) if err != nil { - es.logger.Error("error trying to get deployment json for sink ID", zap.String("sinkId", sinkId)) + es.logger.Error("error trying to get deployment json for sink ID", zap.String("sinkId", sink.SinkID), zap.Error(err)) return err } - es.sinkerKeyRedisClient.HSet(ctx, deploymentKey, sinkId, deploy) + es.sinkerKeyRedisClient.HSet(ctx, deploymentKey, sink.SinkID, deploy) return nil } @@ -100,35 +105,37 @@ func (es eventStore) handleSinksUpdateCollector(ctx context.Context, event redis if err != nil { es.logger.Error("could not fetch info for sink", zap.String("sink-id", event.SinkID), zap.Error(err)) } - var data config.SinkData - if err := json.Unmarshal(sinkData.Config, &data); err != nil { + var metadata types.Metadata + if err := json.Unmarshal(sinkData.Config, &metadata); err != nil { + return err + } + data := config.SinkData{ + SinkID: sinkData.Id, + OwnerID: sinkData.OwnerID, + Backend: sinkData.Backend, + Config: metadata, + } + _ = data.State.SetFromString(sinkData.State) + + deploy, err := config.GetDeploymentJson(es.kafkaUrl, data) + + if err != nil { + es.logger.Error("error trying to get deployment json for sink ID", zap.String("sinkId", event.SinkID), zap.Error(err)) return err } - sinkUrl := data.Url - sinkUsername := data.User - sinkPassword := data.Password - deploy, err := config.GetDeploymentJson(es.kafkaUrl, event.SinkID, sinkUrl, sinkUsername, sinkPassword) + err = es.sinkerKeyRedisClient.HSet(ctx, deploymentKey, event.SinkID, deploy).Err() if err != nil { - es.logger.Error("error trying to get deployment json for sink ID", zap.String("sinkId", event.SinkID)) + es.logger.Error("error trying to update deployment json for sink ID", zap.String("sinkId", event.SinkID), zap.Error(err)) return err } - es.sinkerKeyRedisClient.HSet(ctx, deploymentKey, event.SinkID, deploy) err = es.kubecontrol.UpdateOtelCollector(ctx, event.Owner, event.SinkID, deploy) if err != nil { return err } - // changing state on updated sink to unknown - sinkData.OwnerID = event.Owner - es.PublishSinkStateChange(sinkData, "unknown", err, err) - data.SinkID = sinkData.Id - data.OwnerID = sinkData.OwnerID - data.State.SetFromString("unknown") - es.UpdateSinkStateCache(ctx, data) return nil } func (es eventStore) UpdateSinkCache(ctx context.Context, data config.SinkData) (err error) { - data.State = config.Unknown keyPrefix := "sinker_key" skey := fmt.Sprintf("%s-%s:%s", keyPrefix, data.OwnerID, data.SinkID) bytes, err := json.Marshal(data) @@ -136,6 +143,7 @@ func (es eventStore) UpdateSinkCache(ctx context.Context, data config.SinkData) return err } if err = es.sinkerKeyRedisClient.Set(ctx, skey, bytes, 0).Err(); err != nil { + es.logger.Error("failed to update sink cache", zap.Error(err)) return err } return @@ -146,6 +154,7 @@ func (es eventStore) UpdateSinkStateCache(ctx context.Context, data config.SinkD skey := fmt.Sprintf("%s-%s:%s", keyPrefix, data.OwnerID, data.SinkID) bytes, err := json.Marshal(data) if err != nil { + es.logger.Error("error update sink cache state", zap.Error(err)) return err } if err = es.sinkerKeyRedisClient.Set(ctx, skey, bytes, 0).Err(); err != nil { diff --git a/maestro/redis/consumer/streams.go b/maestro/redis/consumer/streams.go index 24658cfbf..c5eb0634f 100644 --- a/maestro/redis/consumer/streams.go +++ b/maestro/redis/consumer/streams.go @@ -2,6 +2,7 @@ package consumer import ( "context" + "encoding/json" "time" "github.com/orb-community/orb/maestro/config" @@ -33,7 +34,7 @@ const ( ) type Subscriber interface { - CreateDeploymentEntry(ctx context.Context, sinkId, sinkUrl, sinkUsername, sinkPassword string) error + CreateDeploymentEntry(ctx context.Context, sink config.SinkData) error GetDeploymentEntryFromSinkId(ctx context.Context, sinkId string) (string, error) UpdateSinkCache(ctx context.Context, data config.SinkData) (err error) @@ -97,7 +98,7 @@ func (es eventStore) SubscribeSinkerEvents(ctx context.Context) error { go func() { err = es.handleSinkerCreateCollector(ctx, rte) //sinker request to create collector if err != nil { - es.logger.Error("Failed to handle sinks event", zap.Any("operation", event["operation"]), zap.Error(err)) + es.logger.Error("Failed to handle sinker event", zap.Any("operation", event["operation"]), zap.Error(err)) } else { es.streamRedisClient.XAck(ctx, streamSinker, groupMaestro, msg.ID) } @@ -191,8 +192,29 @@ func (es eventStore) handleSinkerCreateCollector(ctx context.Context, event maes es.logger.Info("Received maestro CREATE event from sinker, sink state", zap.String("state", event.State), zap.String("sinkID", event.SinkID), zap.String("ownerID", event.Owner)) deploymentEntry, err := es.GetDeploymentEntryFromSinkId(ctx, event.SinkID) if err != nil { - es.logger.Error("could not find deployment entry from sink-id", zap.String("sinkID", event.SinkID), zap.Error(err)) - return err + sink, err := es.sinksClient.RetrieveSink(ctx, &sinkspb.SinkByIDReq{ + SinkID: event.SinkID, + OwnerID: event.Owner, + }) + if err != nil { + es.logger.Error("could not find deployment entry from sink-id", zap.String("sinkID", event.SinkID), zap.Error(err)) + return err + } + var metadata types.Metadata + if err := json.Unmarshal(sink.Config, &metadata); err != nil { + return err + } + sinkData := config.SinkData{ + SinkID: sink.Id, + OwnerID: sink.OwnerID, + Backend: sink.Backend, + Config: metadata, + } + err = es.CreateDeploymentEntry(ctx, sinkData) + if err != nil { + es.logger.Error("could not create deployment entry from sink", zap.String("sinkID", event.SinkID), zap.Error(err)) + return err + } } err = es.kubecontrol.CreateOtelCollector(ctx, event.Owner, event.SinkID, deploymentEntry) if err != nil { diff --git a/maestro/service.go b/maestro/service.go index 40a438fcd..c30c61e20 100644 --- a/maestro/service.go +++ b/maestro/service.go @@ -12,6 +12,7 @@ import ( "context" "encoding/json" "github.com/orb-community/orb/maestro/monitor" + "github.com/orb-community/orb/pkg/types" "strings" "github.com/go-redis/redis/v8" @@ -81,15 +82,19 @@ func (svc *maestroService) Start(ctx context.Context, cancelFunction context.Can for _, sinkRes := range sinksRes.Sinks { sinkContext := context.WithValue(loadCtx, "sink-id", sinkRes.Id) - var data maestroconfig.SinkData - if err := json.Unmarshal(sinkRes.Config, &data); err != nil { + var metadata types.Metadata + if err := json.Unmarshal(sinkRes.Config, &metadata); err != nil { svc.logger.Warn("failed to unmarshal sink, skipping", zap.String("sink-id", sinkRes.Id)) continue } if val, _ := svc.eventStore.GetDeploymentEntryFromSinkId(ctx, sinkRes.Id); val != "" { svc.logger.Info("Skipping deploymentEntry because it is already created") } else { - err := svc.eventStore.CreateDeploymentEntry(sinkContext, sinkRes.Id, data.Url, data.User, data.Password) + var data maestroconfig.SinkData + data.SinkID = sinkRes.Id + data.Config = metadata + data.Backend = sinkRes.Backend + err := svc.eventStore.CreateDeploymentEntry(sinkContext, data) if err != nil { svc.logger.Warn("failed to create deploymentEntry for sink, skipping", zap.String("sink-id", sinkRes.Id)) continue diff --git a/migrate/migration/m2_encrypt_sinks_creds.go b/migrate/migration/m2_encrypt_sinks_creds.go index 44ad7a829..22020d095 100644 --- a/migrate/migration/m2_encrypt_sinks_creds.go +++ b/migrate/migration/m2_encrypt_sinks_creds.go @@ -6,6 +6,7 @@ import ( "github.com/orb-community/orb/pkg/db" "github.com/orb-community/orb/pkg/types" "github.com/orb-community/orb/sinks" + "github.com/orb-community/orb/sinks/authentication_type" "github.com/orb-community/orb/sinks/backend" "github.com/orb-community/orb/sinks/postgres" "go.uber.org/zap" @@ -14,7 +15,7 @@ import ( type M2SinksCredentials struct { logger *zap.Logger dbSinks postgres.Database - pwdSvc sinks.PasswordService + pwdSvc authentication_type.PasswordService } type querySink struct { @@ -96,7 +97,7 @@ func (m M2SinksCredentials) Down() (err error) { } func NewM2SinksCredentials(log *zap.Logger, dbSinks postgres.Database, config config.EncryptionKey) Plan { - pwdSvc := sinks.NewPasswordService(log, config.Key) + pwdSvc := authentication_type.NewPasswordService(log, config.Key) return &M2SinksCredentials{log, dbSinks, pwdSvc} } diff --git a/migrate/migration/m3_enable_otel_all_sinks.go b/migrate/migration/m3_enable_otel_all_sinks.go index a29079a25..257c2485b 100644 --- a/migrate/migration/m3_enable_otel_all_sinks.go +++ b/migrate/migration/m3_enable_otel_all_sinks.go @@ -7,13 +7,14 @@ import ( "github.com/orb-community/orb/pkg/errors" "github.com/orb-community/orb/pkg/types" "github.com/orb-community/orb/sinks" + "github.com/orb-community/orb/sinks/authentication_type" "go.uber.org/zap" ) type M3SinksOpenTelemetry struct { logger *zap.Logger dbSinks postgres.Database - pwdSvc sinks.PasswordService + pwdSvc authentication_type.PasswordService } func NewM3SinksOpenTelemetry(log *zap.Logger, dbSinks postgres.Database) Plan { diff --git a/orb_tests/chromedriver.log b/orb_tests/chromedriver.log deleted file mode 100644 index ca2792f73..000000000 --- a/orb_tests/chromedriver.log +++ /dev/null @@ -1,4 +0,0 @@ -Starting ChromeDriver 93.0.4577.15 (660fc11082ba57405eca2e8c49c3e1af756fbfae-refs/branch-heads/4577@{#203}) on port 9515 -Only local connections are allowed. -Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe. -ChromeDriver was started successfully. diff --git a/orb_tests/nightwatch.conf.js b/orb_tests/nightwatch.conf.js deleted file mode 100644 index bdc32edb9..000000000 --- a/orb_tests/nightwatch.conf.js +++ /dev/null @@ -1,52 +0,0 @@ -const chromedriver = require('chromedriver'); - -const testUrl = 'http://localhost:4200'; -const defaultTimeout = 15000; - -module.exports = { - src_folders : ['tests'], - page_objects_path : './page_objects', - skip_testcases_on_fail: false, - - webdriver: { - start_process: true, - }, - - test_settings: { - default: { - launch_url: testUrl, - globals: { - waitForConditionTimeout: defaultTimeout, - }, - webdriver: { - server_path: chromedriver.path, - port: 9515, - }, - desiredCapabilities: { - browserName: 'chrome', - } - }, - - headless: { - launch_url: testUrl, - globals: { - waitForConditionTimeout: defaultTimeout, - }, - webdriver: { - server_path: chromedriver.path, - port: 9515, - }, - desiredCapabilities: { - browserName: 'chrome', - chromeOptions: { - w3c: false, - args: ['--headless', '--no-sandbox', '--disable-dev-shm-usage'], - } - } - }, - - beta: { - launch_url: "https://beta.orb.live" - } - } -} diff --git a/orb_tests/package-lock.json b/orb_tests/package-lock.json deleted file mode 100644 index b503d718b..000000000 --- a/orb_tests/package-lock.json +++ /dev/null @@ -1,2736 +0,0 @@ -{ - "name": "orb_tests", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@testim/chrome-version": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.0.7.tgz", - "integrity": "sha512-8UT/J+xqCYfn3fKtOznAibsHpiuDshCb0fwgWxRazTT19Igp9ovoXMPhXyLD6m3CKQGTMHgqoxaFfMWaL40Rnw==", - "dev": true - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@types/node": { - "version": "16.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.1.tgz", - "integrity": "sha512-4/Z9DMPKFexZj/Gn3LylFgamNKHm4K3QDi0gz9B26Uk0c8izYf97B5fxfpspMNkWlFupblKM/nV8+NA9Ffvr+w==", - "dev": true, - "optional": true - }, - "@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "requires": { - "tslib": "^2.0.1" - } - }, - "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dev": true, - "requires": { - "follow-redirects": "^1.14.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chai-nightwatch": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/chai-nightwatch/-/chai-nightwatch-0.4.1.tgz", - "integrity": "sha512-s2put5cFhG8Hw+6Po3R8QZ0ctsDmcaIh7+l9Fu3RcLCfosfQffT3mcFSq2jmXEQk0pmwo/PuXvYMO87MRlyqxg==", - "dev": true, - "requires": { - "assertion-error": "1.0.0", - "deep-eql": "0.1.3" - }, - "dependencies": { - "assertion-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", - "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", - "dev": true - } - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chromedriver": { - "version": "93.0.1", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-93.0.1.tgz", - "integrity": "sha512-KDzbW34CvQLF5aTkm3b5VdlTrvdIt4wEpCzT2p4XJIQWQZEPco5pNce7Lu9UqZQGkhQ4mpZt4Ky6NKVyIS2N8A==", - "dev": true, - "requires": { - "@testim/chrome-version": "^1.0.7", - "axios": "^0.21.2", - "del": "^6.0.0", - "extract-zip": "^2.0.1", - "https-proxy-agent": "^5.0.0", - "proxy-from-env": "^1.1.0", - "tcp-port-used": "^1.0.1" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-spinners": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", - "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==", - "dev": true - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-uri-to-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", - "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", - "dev": true - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", - "dev": true, - "requires": { - "type-detect": "0.1.1" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "requires": { - "clone": "^1.0.2" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "degenerator": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-3.0.1.tgz", - "integrity": "sha512-LFsIFEeLPlKvAKXu7j3ssIG6RT0TbI7/GhsqrI0DnHASEQjXQ0LUSYcjJteGgRGmZbl1TnMSxpNQIAiJ7Du5TQ==", - "dev": true, - "requires": { - "ast-types": "^0.13.2", - "escodegen": "^1.8.1", - "esprima": "^4.0.0", - "vm2": "^3.9.3" - } - }, - "del": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", - "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", - "dev": true, - "requires": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dotenv": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz", - "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ejs": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", - "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", - "dev": true, - "requires": { - "jake": "^10.6.1" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true - }, - "es-abstract": { - "version": "1.18.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.6.tgz", - "integrity": "sha512-kAeIT4cku5eNLNuUKhlmtuk1/TRZvQoYccn6TO0cSVdf1kzB0T7+dYuVK9MWM7l+/53W2Q8M7N2c6MQvhXFcUQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.4", - "is-string": "^1.0.7", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "dependencies": { - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - } - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "file-uri-to-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", - "integrity": "sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==", - "dev": true - }, - "filelist": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", - "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } - }, - "follow-redirects": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "ftp": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", - "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", - "dev": true, - "requires": { - "readable-stream": "1.1.x", - "xregexp": "2.0.0" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "get-uri": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz", - "integrity": "sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "data-uri-to-buffer": "3", - "debug": "4", - "file-uri-to-path": "2", - "fs-extra": "^8.1.0", - "ftp": "^0.3.10" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "dev": true - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true - }, - "is2": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.7.tgz", - "integrity": "sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "jake": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", - "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", - "dev": true, - "requires": { - "async": "0.9.x", - "chalk": "^2.4.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - } - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash._arraycopy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", - "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=", - "dev": true - }, - "lodash._arrayeach": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", - "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=", - "dev": true - }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._baseclone": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", - "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", - "dev": true, - "requires": { - "lodash._arraycopy": "^3.0.0", - "lodash._arrayeach": "^3.0.0", - "lodash._baseassign": "^3.0.0", - "lodash._basefor": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basefor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", - "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=", - "dev": true - }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash.clone": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-3.0.3.tgz", - "integrity": "sha1-hGiMc9MrWpDKJWFpY/GJJSqZcEM=", - "dev": true, - "requires": { - "lodash._baseclone": "^3.0.0", - "lodash._bindcallback": "^3.0.0", - "lodash._isiterateecall": "^3.0.0" - } - }, - "lodash.defaultsdeep": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", - "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==", - "dev": true - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", - "dev": true - }, - "mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "dev": true, - "requires": { - "mime-db": "1.49.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", - "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "mkpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-1.0.0.tgz", - "integrity": "sha1-67Opd+evHGg65v2hK1Raa6bFhT0=", - "dev": true - }, - "mocha": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", - "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", - "dev": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "2.2.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.4", - "ms": "2.1.1", - "node-environment-flags": "1.0.5", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "dev": true - }, - "nightwatch": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-1.7.11.tgz", - "integrity": "sha512-yV795EBXZ/myeoCvBtjC/QwvIprxF7SKh0XCeFnpoOtWXDb0yv+ATLRipKGfp+avyGtagqq38ucA4Uh6WPcnhQ==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "chai-nightwatch": "^0.4.0", - "ci-info": "^2.0.0", - "didyoumean": "^1.2.1", - "dotenv": "7.0.0", - "ejs": "^3.1.6", - "envinfo": "^7.5.1", - "lodash.clone": "3.0.3", - "lodash.defaultsdeep": "^4.6.1", - "lodash.merge": "^4.6.2", - "minimatch": "3.0.4", - "minimist": "^1.2.5", - "mkpath": "1.0.0", - "mocha": "6.2.3", - "ora": "^4.0.3", - "proxy-agent": "^5.0.0", - "request": "^2.88.2", - "request-promise": "^4.2.5", - "semver": "^6.3.0", - "strip-ansi": "^6.0.0" - } - }, - "node-environment-flags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", - "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "ora": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-4.1.1.tgz", - "integrity": "sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.2.0", - "is-interactive": "^1.0.0", - "log-symbols": "^3.0.0", - "mute-stream": "0.0.8", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pac-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-5.0.0.tgz", - "integrity": "sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4", - "get-uri": "3", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "5", - "pac-resolver": "^5.0.0", - "raw-body": "^2.2.0", - "socks-proxy-agent": "5" - } - }, - "pac-resolver": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-5.0.0.tgz", - "integrity": "sha512-H+/A6KitiHNNW+bxBKREk2MCGSxljfqRX76NjummWEYIat7ldVXRU3dhRIE3iXZ0nvGBk6smv3nntxKkzRL8NA==", - "dev": true, - "requires": { - "degenerator": "^3.0.1", - "ip": "^1.1.5", - "netmask": "^2.0.1" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-5.0.0.tgz", - "integrity": "sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g==", - "dev": true, - "requires": { - "agent-base": "^6.0.0", - "debug": "4", - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^5.0.0", - "lru-cache": "^5.1.1", - "pac-proxy-agent": "^5.0.0", - "proxy-from-env": "^1.0.0", - "socks-proxy-agent": "^5.0.0" - } - }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "raw-body": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", - "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", - "dev": true, - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.3", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "request-promise": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz", - "integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==", - "dev": true, - "requires": { - "bluebird": "^3.5.0", - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", - "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true - }, - "socks": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", - "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", - "dev": true, - "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.1.0" - } - }, - "socks-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz", - "integrity": "sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==", - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "debug": "4", - "socks": "^2.3.3" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - } - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "tcp-port-used": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", - "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", - "dev": true, - "requires": { - "debug": "4.3.1", - "is2": "^2.0.6" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - } - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "dependencies": { - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - } - } - }, - "vm2": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.3.tgz", - "integrity": "sha512-smLS+18RjXYMl9joyJxMNI9l4w7biW8ilSDaVRvFBDwOH8P0BK1ognFQTpg0wyQ6wIKLTblHJvROW692L/E53Q==" - }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "xregexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", - "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=", - "dev": true - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - } - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - } - } -} diff --git a/orb_tests/package.json b/orb_tests/package.json deleted file mode 100644 index 117cbbdd2..000000000 --- a/orb_tests/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "orb_tests", - "version": "1.0.0", - "main": "index.js", - "scripts": { - "test": "nightwatch", - "test:beta": "nightwatch --env=beta", - "test:ci": "nightwatch --env=headless" - }, - "author": "", - "license": "ISC", - "devDependencies": { - "chromedriver": "^93.0.1", - "nightwatch": "^1.7.11" - }, - "directories": { - "test": "tests" - }, - "keywords": [], - "dependencies": { - "vm2": "^3.9.3" - }, - "description": "" -} diff --git a/orb_tests/page_objects/accountRegister.js b/orb_tests/page_objects/accountRegister.js deleted file mode 100644 index afb1f1a5c..000000000 --- a/orb_tests/page_objects/accountRegister.js +++ /dev/null @@ -1,39 +0,0 @@ -var registerCommands = { - orbRegister: function() { - return this.verify.visible('@register', "'Register' option is being displayed") - .click('@register') - //bug remove this duplicated - .click('@register') - .waitForElementVisible('@fullNameInput', "'Full name' field is visible") - .verify.containsText("[for='input-name']", "Full name:", "'Full name:' description is correctly written") - .setValue('@fullNameInput', 'tester') - .waitForElementVisible('@username', "'Email address' field is visible") - .verify.containsText("[for='input-email']", "Email address:", "'Email Address' description is correctly written") - .setValue('@username', 'tester@email.com') - .waitForElementVisible('@pwd', "'Password field is visible") - .verify.containsText("[for='input-password']", "Password:", "'Password' description is correctly written") - .setValue('@pwd', '12345678') - .waitForElementVisible('@confirmPassword', "'Repeat password' field is visible") - .verify.containsText("[for='input-re-password']", "Repeat password:", "'Repeat password' description is correctly written") - .setValue('@confirmPassword', '12345678') - .waitForElementVisible('@submit', "'Register' button is visible") - .verify.containsText('@submit', "REGISTER", "'REGISTER' text is correctly written") - .verify.attributeEquals('@submit','aria-disabled', 'false', "'Register' button is clickable") - .pause(2000) - .click('@submit') - .pause(2000) - - }, -} -module.exports = { - url: '/auth/register', - commands: [registerCommands], - elements: { - register: '.text-link', - fullNameInput:'input[id=input-name]', - username: 'input[id=input-email]', - pwd: 'input[id=input-password]', - confirmPassword: 'input[id=input-re-password]', - submit: '.appearance-filled' - - }} diff --git a/orb_tests/page_objects/agent_groups.js b/orb_tests/page_objects/agent_groups.js deleted file mode 100644 index bd4d04925..000000000 --- a/orb_tests/page_objects/agent_groups.js +++ /dev/null @@ -1,216 +0,0 @@ -var commands = { - - new: function() { - return this.navigate() - .waitForElementVisible('@newButton', 10000, "New Agent Groups button is visible") - .click('@newButton') - }, - - fillInput: function (selector, data) { - return this.setValue(selector, data) - }, - - - listView: function() { - return this.verify.containsText('@agentGroupList','Agent Groups List', "Agent Groups view is named 'Agent Groups List'") - .verify.containsText('@agentGroupAll', 'All Agent Groups', "Agent Groups Header is 'All Agent Groups'") - .verify.containsText('@agentPath', 'Fleet Management', "Agent Groups is inherited from Fleet Management") - .verify.visible('@new', "'New Agent Group' button is visible") - .verify.visible('.flex-column', "Agent Groups table view is visible") - .verify.visible("@table", "Agent Groups body table view is visible") - .verify.visible("@filter", "Filter type is visible") - .verify.visible("@search", "Search by filter is visible") - }, - - agentGroupCreationPage: function() { - return this.verify.containsText('.header', 'Agent Group Details', "Header is 'Agent Group Details'") - .verify.containsText('.header', 'This is how you will be able to easily identify your Agent Group', "Help text about name is correctly written") - .verify.containsText('.header', 'Agent Group Tags', "'Agent Group Tags' is being displayed") - .verify.containsText('.header', 'Set the tags that will be used to group Agents', "Help text about tags is correctly written") - .verify.containsText('.header', 'Review & Confirm', "'Review & Confirm' is being displayed") - .verify.containsText('.step-content', 'Agent Group Name*', "'Agent Group Name*' field is being displayed") - .verify.containsText('.step-content', 'Agent Group Description', "'Agent Group Description' field is being displayed") - .verify.attributeEquals('@next','aria-disabled', 'true', "'Next' button is not enabled") - .verify.attributeEquals('@back','aria-disabled', 'false', "'Back' button is enabled") - }, - - agentGroupEditPage: function() { - return this.verify.containsText('.xng-breadcrumb-trail', 'Edit Agent Group', "Header is 'Edit Agent Group'") - .verify.containsText('ngx-sink-add-component.ng-star-inserted > div:nth-child(1) > header:nth-child(1)', 'Edit Agent Groups', "Page description is 'Agent Groups'") - .verify.containsText('@editSinkHeader', 'Agent Group Details', "'Agent Group Details' is being displayed") - .verify.containsText('@editSinkHeader', 'This is how you will be able to easily identify your Agent Group', "Help text about name is correctly written") - .verify.containsText('@editSinkHeader', 'Agent Group Tags', "'Agent Group Tags' is being displayed") - .verify.containsText('@editSinkHeader', 'Set the tags that will be used to group Agents', "Help text about tags is correctly written") - .verify.containsText('@editSinkHeader', 'Review & Confirm', "'Review & Confirm' is being displayed") - .verify.attributeEquals('@sinkNext','aria-disabled', 'false', "Next button is enabled") - .verify.attributeEquals('@back','aria-disabled', 'false', "Cancel button is enabled") - }, - - agentGroupCreation: function(name, description, key, value, verify) { - return this.setValue('@newNameInput', name) - .verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - .setValue('@newDescriptionInput', description) - .click('@next') - .verify.attributeEquals('@next','aria-disabled', 'true', "'Next' button is not enabled") - .verify.attributeEquals('button.status-primary:nth-child(1)','aria-disabled', 'false', "'Back' button is enabled") - .verify.attributeEquals('@addTag','aria-disabled', 'true', "'Add tags' button is not enabled") - .setValue('@key', key) - .setValue('@value', value) - .verify.attributeEquals('@addTag','aria-disabled', 'false', "'Add tags' button is enabled") - .click('@addTag') - .verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - .verify.attributeEquals('button.status-primary:nth-child(1)','aria-disabled', 'false', "'Back' button is enabled") - .click('@next') - .verify.containsText('@agentGroupList','New Agent Group', 'Page header is "New Agent Group"') - .verify.attributeEquals('@back','aria-disabled', 'false', "'Back' button is enabled") - .verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - .click('@next') - .verify.containsText('span.title', verify, "Confirmation message is correctly displayed") - - - }, - - agentGroupsDelete: function() { - return this.verify.attributeEquals('button.orb-action-hover:nth-child(3)', 'aria-disabled', 'false', "'Remove' agent group button is enabled") - .click('button.orb-action-hover:nth-child(3)') - .verify.attributeEquals('@deleteAgentGroups','aria-disabled', 'true', "'Confirm agent group delete button is not enabled") - .verify.visible('@agentGroupsDeleteModal', "Delete agent groups modal is visible") - .verify.containsText('ngx-agent-group-delete-component.ng-star-inserted > nb-card:nth-child(1) > nb-card-header:nth-child(1)', 'Delete Agent Group Confirmation', "Header of help text about delete confirmation is correctly written") - .verify.containsText('@agentGroupsDeleteModal', 'Are you sure you want to delete this Agent Group? This may cause Datasets which use this Agent Group to become invalid. This action cannot be undone.*', "Help text about delete confirmation is correctly written") - .verify.containsText('@agentGroupsDeleteModal', '*To confirm, type the Agent Group name exactly as it appears', "End of help text about delete confirmation is correctly written") - .getAttribute('.input-full-width', 'placeholder', function(result) {this.setValue('.input-full-width', result.value) }) - .verify.attributeEquals('@deleteAgentGroups','aria-disabled', 'false', "'Confirm agent group delete button is enabled") - .click('@deleteAgentGroups') - .verify.containsText('span.title', 'Agent Group successfully deleted', "Delete confirmation message is being displayed") - //bug .verify.containsText('.empty-row', 'No data to display') - - }, - - agentGroupVisualization: function() { - return this.verify.attributeEquals('button.orb-action-hover:nth-child(1)', 'aria-disabled', 'false', "'Visualization' button is visible") - .click('button.orb-action-hover:nth-child(1)') - .verify.elementPresent('.cdk-overlay-backdrop', "'Visualization' modal is visible") - .verify.containsText('.nb-card-medium > nb-card-header:nth-child(1)', 'Agent Group Details', "'Visualization' header is correctly written") - }, - - - choose_last_element: function() { - return this.waitForElementVisible('[class="orb-action-hover detail-button appearance-ghost size-medium status-basic shape-rectangle icon-start icon-end nb-transition"]', "Agent Group is visible") - .findElements('[class="orb-action-hover detail-button appearance-ghost size-medium status-basic shape-rectangle icon-start icon-end nb-transition"]', function(result) { - var agentGroupsView = result.value - this.elementIdClick(agentGroupsView[agentGroupsView.length-1].ELEMENT) - - - }) - }, - - addTags: function(key, value) { - return this.setValue('@key', key) - .setValue('@value', value) - .verify.attributeEquals('@addTag','aria-disabled', 'false', "'Add tags' button is enabled") - .click('@addTag') - .verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - - }, - - //bug : need to insert a test for checking if is possible to create two identicals tags - - agentGroupsEdit: function(name, description, key, value, key2, value2, verify) { - return this.verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - .verify.attributeEquals('@back','aria-disabled', 'false', "'Back' button is enabled") - .clearValue('@newNameInput') - .setValue('@newNameInput', name) - .clearValue('@newDescriptionInput') - .setValue('@newDescriptionInput', description) - .click('@next') - .click('.eva-close-outline') - .verify.attributeEquals('@next','aria-disabled', 'true', "'Next' button is not enabled") - .verify.attributeEquals('button.status-primary:nth-child(1)','aria-disabled', 'false', "'Back' button is enabled") - .verify.attributeEquals('@addTag','aria-disabled', 'true', "'Add tags' button is not enabled") - .addTags(key, value) - .addTags(key2, value2) - .verify.attributeEquals('button.status-primary:nth-child(1)','aria-disabled', 'false', "'Back' button is enabled") - .click('@next') - .verify.containsText('@agentGroupList','Edit Agent Group', 'Page header is "Edit Agent Group"') - .verify.attributeEquals('@back','aria-disabled', 'false', "'Back' button is enabled") - .verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - .click('@next') - .verify.containsText('span.title', verify, "Confirmation message is correctly displayed") - }, - - - agentGroupCheck: function(name, description){ - return this.verify.containsText('div.row:nth-child(1) > div:nth-child(1) > p:nth-child(1)', 'Agent Group Name*', "View contain Agent Group Name Field") - .verify.containsText('div.row:nth-child(1) > div:nth-child(1) > p:nth-child(2)', name, "Name of Agent Group is correctly displayed") - .verify.containsText('div.row:nth-child(1) > div:nth-child(2) > p:nth-child(1)', 'Agent Group Description', "View contain Agent Group Description Field") - .verify.containsText('div.row:nth-child(1) > div:nth-child(2) > p:nth-child(2)', description, "Description of Agent Group is correctly displayed") - .verify.containsText('div.row:nth-child(2) > div:nth-child(1) > p:nth-child(1)', 'Date Created', "View contain Agent Group Date Created Field") - .verify.visible('div.row:nth-child(2) > div:nth-child(1) > p:nth-child(2)', "Agent Group Date Created is visible") - .verify.containsText('div.row:nth-child(2) > div:nth-child(2) > p:nth-child(1)', 'Matches Against', "View contain Agent Group Matches Against Field") - .verify.containsText('div.row:nth-child(2) > div:nth-child(2) > p:nth-child(2)', 'Agent(s)', "Matches of Agent Group is correctly displayed") - .verify.containsText('div.row:nth-child(3) > div:nth-child(1) > p:nth-child(1)', 'Tags*', "View contain Agent Group Tags Field") - .click('@close') - - }, - - - - countAgentGroups: function(browser) { - return this.getText('.page-count', function(result){ - //console.log('Value is:', result.value); - if (result.value == "0 total") { - browser.expect.elements('datatable-row-wrapper').count.to.equal(parseInt(result.value)) - - browser.verify.containsText('.sink-info-accent', 'There are no Agent Groups yet.', "Info message of Agent Groups count is correctly displayed") - browser.verify.containsText('.empty-row', 'No data to display', "View table info message is correctly displayed") - } else { - browser.expect.elements('datatable-row-wrapper').count.to.equal(parseInt(result.value)) - browser.verify.containsText('.justify-content-between > div:nth-child(1)', 'You have', "Beginning of info message is correctly displayed") - browser.verify.containsText('.justify-content-between > div:nth-child(1)', parseInt(result.value), "Number of Agents is correctly displayed") - browser.verify.containsText('.justify-content-between > div:nth-child(1)', 'Agent Groups.', "End of info message is correctly displayed") - } - }) - - } - -}; - -module.exports = { - url: '/pages/fleet/groups', - elements: { - newButton: '.appearance-ghost', - newHeading: 'header h4', - activeBreadcrumb: '.xng-breadcrumb-item:last-child .xng-breadcrumb-trail', - selectedStep: '.selected span', - completedStep: '.completed span', - stepLabel: '.step-label strong', - stepCaption: '.step-label p', - detailsLabels: '.nb-form-control-container div:not(.d-flex)', - newNameInput: '[formcontrolname="name"]', - newDescriptionInput: '[formcontrolname="description"]', - tagLabels: '.nb-form-control-container div div div', - keyInput: '[formcontrolname="key"]', - valueInput: '[formcontrolname="value"]', - addTagButton: 'button [icon="plus-outline"]', - tagChip: '.mat-chip', - tagChipDelete: '.mat-chip [icon="close-outline"]', - agentGroupList: 'xng-breadcrumb.orb-breadcrumb', - agentGroupAll: 'ngx-agent-group-list-component.ng-star-inserted > div:nth-child(1) > header:nth-child(1) > h4:nth-child(2)', - key: 'div.col-5:nth-child(1) > div:nth-child(2) > input:nth-child(1)', - value: 'div.d-flex:nth-child(3) > div:nth-child(2) > input:nth-child(1)', - addTag: 'button.status-basic', - next:'.next-button', - back: '.appearance-ghost', - deleteAgentGroups: '.orb-sink-delete-warning-button', - agentGroupsDeleteModal: 'ngx-agent-group-delete-component.ng-star-inserted > nb-card:nth-child(1)', - close: '.nb-close', - edit: '.sink-edit-button', - agentPath: '.xng-breadcrumb-link', - new:'.status-primary', - filter:'.select-button', - table:'.datatable-body', - search:'input.size-medium' - - }, - commands: [commands] -}; diff --git a/orb_tests/page_objects/agents.js b/orb_tests/page_objects/agents.js deleted file mode 100644 index be6d27b4a..000000000 --- a/orb_tests/page_objects/agents.js +++ /dev/null @@ -1,221 +0,0 @@ -var loginActions = { - AgentsPage: function () { - return this - .waitForElementVisible('@path', "Agents path is visible") - .verify.containsText('@agentPath', 'Fleet Management', "Agents is inherited from Fleet Management") - .verify.containsText('@view', 'Agents List', "Agent view is named 'Agents List'") - .verify.containsText('@header', "All Agents", "Agents Header is 'All Agents'") - .waitForElementVisible('.flex-column', "Agent Groups table view is visible") - .waitForElementVisible('@table', "Agent table view is visible") - .waitForElementVisible("@new", "New Agent button is visible") - .waitForElementVisible("@filter", "Filter type is visible") - .waitForElementVisible("@search", "Search by filter is visible") - - }, - - new: function() { - return this.navigate() - .waitForElementVisible('@newButton', 10000, "New Agent button is visible") - .click('@newButton') - }, - - - agentCreationPage: function() { - return this.waitForElementVisible('@pathNew', "Agents path is visible") - .verify.containsText('@agentPath', 'Fleet Management', "Agents is inherited from Fleet Management") - .verify.containsText('@pathNew', 'Agents List', "Agent view is named 'Agents List'") - .verify.containsText('@pathNew', "New Agent", "Agents Header is 'New Agent'") - .verify.containsText('@headerNew', 'New Agent', "Header is 'New Agent'") - .verify.containsText('@agentDetails', 'Agent Details', "Header is 'Agent Details'") - .verify.containsText('.header', 'This is how you will be able to easily identify your Agent', "Help text about name is correctly written") - .verify.containsText('.header', 'Orb Tags', "'Agent Tags' is being displayed") - .verify.containsText('.header', 'Set the tags that will be used to filter your Agent', "Help text about tags is correctly written") - .verify.containsText('.header', 'Review & Confirm', "'Review & Confirm' is being displayed") - .verify.containsText('.step-content', 'Agent Name*', "'Agent Name*' field is being displayed") - .verify.attributeEquals('@next','aria-disabled', 'true', "'Next' button is not enabled") - .verify.attributeEquals('@back','aria-disabled', 'false', "'Back' button is enabled") - }, - - - agentCreation: function(name, key, value, verify, closeOption='@close') { - return this.setValue('@newNameInput', name) - .verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - // .setValue('@newDescriptionInput', description) - .click('@next') - .verify.attributeEquals('@next','aria-disabled', 'true', "'Next' button is not enabled") - .verify.attributeEquals('button.status-primary:nth-child(1)','aria-disabled', 'false', "'Back' button is enabled") - .verify.attributeEquals('@addTag','aria-disabled', 'true', "'Add tags' button is not enabled") - .setValue('@key', key) - .setValue('@value', value) - .verify.attributeEquals('@addTag','aria-disabled', 'false', "'Add tags' button is enabled") - .click('@addTag') - .verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - .verify.attributeEquals('button.status-primary:nth-child(1)','aria-disabled', 'false', "'Back' button is enabled") - .click('@next') - .verify.containsText('@pathNew', "New Agent", "Agents Header is 'New Agent'") - .verify.attributeEquals('@back','aria-disabled', 'false', "'Back' button is enabled") - .verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - .click('@next') - .verify.visible('[class="cdk-overlay-pane"]', "Agent Credentials modal is visible") - .verify.containsText("@agentCredentialsHeader", "Agent Credentials", "Agent Credentials modal's header is 'Agent Credentials'") - .verify.containsText("@agentCredencialsBody", "Make sure to copy the Agent Key now. You won’t be able to see it again!", "Agent Credentials help text is 'Make sure to copy the Agent Key now. You won’t be able to see it again!'") - .verify.containsText("@agentCredencialsBody", "Agent Key", "Agent key field's name is 'Agent Key'") - .verify.containsText("@agentCredencialsBody", "Provisioning Command", "Provisioning Command field's name is 'Provisioning Command'") - .verify.visible("@agentKey", "Agent Key is being displayed") - .verify.visible("@agentProvisioningCommand", "Agent Provisioning Command is being displayed") - .click('@copyKey') - .click('@copyProvisioningCommand') - // options: '@closeCredentialsModal' or '@close' - .click(closeOption) - .verify.containsText('span.title', verify, "Confirmation message is correctly displayed") - - -}, - -agentsVisualization: function() { - return this.verify.attributeEquals('button.orb-action-hover:nth-child(1)', 'aria-disabled', 'false', "'Visualization' button is visible") - .click('button.orb-action-hover:nth-child(1)') - .verify.elementPresent('.cdk-overlay-backdrop', "'Visualization' modal is visible") - .verify.containsText('.nb-card-medium > nb-card-header:nth-child(1)', 'Agent Details', "'Visualization' header is correctly written") - .verify.containsText('@visualizationAgentModal', "Agent Name*", "Agent name field is visible on visualization modal") - .verify.containsText('@visualizationAgentModal', "Channel ID", "Agent Channel ID field is visible on visualization modal") - .verify.containsText('@visualizationAgentModal', "Created on", "Agent Created on field is visible on visualization modal") - .verify.containsText('@visualizationAgentModal', "Status", "Agent Status field is visible on visualization modal") - .verify.containsText('@visualizationAgentModal', "Tags*", "Agent Tags field is visible on visualization modal") -}, - - -agentsEdit: function(name, key, value, key2, value2, verify) { - return this.verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - .verify.attributeEquals('@back','aria-disabled', 'false', "'Back' button is enabled") - .clearValue('@newNameInput') - .setValue('@newNameInput', name) - - .click('@next') - .click('.eva-close-outline') - .verify.attributeEquals('@next','aria-disabled', 'true', "'Next' button is not enabled") - .verify.attributeEquals('button.status-primary:nth-child(1)','aria-disabled', 'false', "'Back' button is enabled") - .verify.attributeEquals('@addTag','aria-disabled', 'true', "'Add tags' button is not enabled") - .addTags(key, value) - .addTags(key2, value2) - .verify.attributeEquals('button.status-primary:nth-child(1)','aria-disabled', 'false', "'Back' button is enabled") - .click('@next') - .verify.containsText('@agentsList','Edit Agent', 'Page header is "Edit Agent"') - .verify.attributeEquals('@back','aria-disabled', 'false', "'Back' button is enabled") - .verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - .click('@next') - .verify.containsText('span.title', verify, "Confirmation message is correctly displayed") -}, - -addTags: function(key, value) { - return this.setValue('@key', key) - .setValue('@value', value) - .verify.attributeEquals('@addTag','aria-disabled', 'false', "'Add tags' button is enabled") - .click('@addTag') - .verify.attributeEquals('@next','aria-disabled', 'false', "'Next' button is enabled") - -}, - -choose_last_element: function() { - return this.waitForElementVisible('[class="orb-action-hover detail-button appearance-ghost size-medium status-basic shape-rectangle icon-start icon-end nb-transition"]', "Agent is visible") - .findElements('[class="orb-action-hover detail-button appearance-ghost size-medium status-basic shape-rectangle icon-start icon-end nb-transition"]', function(result) { - var agentView = result.value - this.elementIdClick(agentView[agentView.length-1].ELEMENT) - - -}) -}, - -agentCheck: function(name, tag){ - return this.verify.containsText('div.row:nth-child(1) > div:nth-child(1) > p:nth-child(1)', 'Agent Name*', "View contain Agent Group Name Field") - .verify.containsText('div.row:nth-child(1) > div:nth-child(1) > p:nth-child(2)', name, "Name of Agent is correctly displayed") - .verify.containsText('div.row:nth-child(2) > div:nth-child(1) > p:nth-child(1)', 'Tags*', "View contain Agent Tags Field") - // bug insert a way to verify tags - // .verify.containsText('[class="mat-chip-list-wrapper"]', tag, "Tag is correctly displayed") - .click('@close') - -}, - -agentsDelete: function() { - return this.verify.attributeEquals('button.orb-action-hover:nth-child(3)', 'aria-disabled', 'false', "'Remove' agent button is enabled") - .click('button.orb-action-hover:nth-child(3)') - .verify.attributeEquals('@deleteAgent','aria-disabled', 'true', "'Confirm agent delete button is not enabled") - .verify.visible('@deleteAgentModal', "Delete agent modal is visible") - .verify.containsText('ngx-agent-delete-component.ng-star-inserted > nb-card:nth-child(1) > nb-card-header:nth-child(1)', 'Delete Agent Confirmation', "Header of help text about delete confirmation is correctly written") - .verify.containsText('@deleteAgentModal', 'Are you sure you want to delete this Agent? This action cannot be undone.*', "Help text about delete confirmation is correctly written") - .verify.containsText('@deleteAgentModal', '*To confirm, type your Agent name exactly as it appears', "End of help text about delete confirmation is correctly written") - .getAttribute('.input-full-width', 'placeholder', function(result) {this.setValue('.input-full-width', result.value) }) - .verify.attributeEquals('@deleteAgent','aria-disabled', 'false', "'Confirm agent delete button is enabled") - .click('@deleteAgent') - .verify.containsText('span.title', 'Agent successfully deleted', "Delete confirmation message is being displayed") - //bug .verify.containsText('.empty-row', 'No data to display') - -}, - -countAgent: function(browser) { - return this.getText('.page-count', function(result){ - //console.log('Value is:', result.value); - if (result.value == "0 total") { - browser.expect.elements('datatable-row-wrapper').count.to.equal(parseInt(result.value)) - - browser.verify.containsText('.sink-info-accent', 'There are no Agent yet.', "Info message of Agent count is correctly displayed") - browser.verify.containsText('.empty-row', 'No data to display', "View table info message is correctly displayed") - } else { - browser.expect.elements('datatable-row-wrapper').count.to.equal(parseInt(result.value)) - browser.verify.containsText('.justify-content-between > div:nth-child(1)', 'You have', "Beginning of info message is correctly displayed") - browser.verify.containsText('.justify-content-between > div:nth-child(1)', parseInt(result.value), "Number of Agents is correctly displayed") - // bug need to insert regions count test - browser.verify.containsText('.justify-content-between > div:nth-child(1)', 'agents deployed', "End of info message is correctly displayed") - } - }) - -} - } - - module.exports = { - url: '/pages/fleet/agents', - commands: [loginActions], - elements: { - path: 'xng-breadcrumb.orb-breadcrumb', - pathNew: '.xng-breadcrumb-root', - agentPath: '.xng-breadcrumb-link', - view: '.xng-breadcrumb-trail', - headerNew: 'ngx-agent-add-component.ng-star-inserted > div:nth-child(1) > header:nth-child(1) > h4:nth-child(2)', - header: 'ngx-agent-list-component.ng-star-inserted > div:nth-child(1) > header:nth-child(1) > h4:nth-child(2)', - table:'.datatable-body', - new:'.status-primary', - filter:'.select-button', - search:'input.size-medium', - agentsListed: '.datatable-row-wrapper', - info: '.sink-info-accent', - emptyRow: '.empty-row', - countMessage: '.justify-content-between > div:nth-child(1)', - count:'.page-count', - newButton: '.appearance-ghost', - agentDetails: 'div.step:nth-child(1) > div:nth-child(2) > div:nth-child(1) > strong:nth-child(1)', - next:'.next-button', - back: '.appearance-ghost', - close: '.nb-close', - closeCredentialsModal: 'ngx-agent-key-component.ng-star-inserted > nb-card:nth-child(1) > nb-card-footer:nth-child(3) > button:nth-child(1)', - newNameInput: '[formcontrolname="name"]', - key: 'div.col-5:nth-child(1) > div:nth-child(2) > input:nth-child(1)', - value: 'div.d-flex:nth-child(3) > div:nth-child(2) > input:nth-child(1)', - addTag: 'button.status-basic', - agentCredencialsBody: 'ngx-agent-key-component.ng-star-inserted > nb-card:nth-child(1) > nb-card-body:nth-child(2)', - agentCredentialsHeader: 'ngx-agent-key-component.ng-star-inserted > nb-card:nth-child(1) > nb-card-header:nth-child(1)', - agentKey: 'ngx-agent-key-component.ng-star-inserted > nb-card:nth-child(1) > nb-card-body:nth-child(2) > pre:nth-child(3)', - agentProvisioningCommand: 'ngx-agent-key-component.ng-star-inserted > nb-card:nth-child(1) > nb-card-body:nth-child(2) > pre:nth-child(5) > code:nth-child(2)', - copyKey: 'ngx-agent-key-component.ng-star-inserted > nb-card:nth-child(1) > nb-card-body:nth-child(2) > pre:nth-child(5) > button:nth-child(1)', - copyProvisioningCommand: 'ngx-agent-key-component.ng-star-inserted > nb-card:nth-child(1) > nb-card-body:nth-child(2) > pre:nth-child(5) > button:nth-child(1)', - deleteAgent: '.orb-sink-delete-warning-button', - deleteAgentModal: 'ngx-agent-delete-component.ng-star-inserted', - edit: '.sink-edit-button', - visualizationAgentModal: '.nb-card-medium > nb-card-body:nth-child(2)', - newNameInput: '[formcontrolname="name"]', - newDescriptionInput: '[formcontrolname="description"]', - agentsList: 'xng-breadcrumb.orb-breadcrumb', - - - - } - } diff --git a/orb_tests/page_objects/login.js b/orb_tests/page_objects/login.js deleted file mode 100644 index d5192914a..000000000 --- a/orb_tests/page_objects/login.js +++ /dev/null @@ -1,33 +0,0 @@ -var loginActions = { - with: function (email, pass) { - return this - .navigate() - .waitForElementVisible('@form', 10000, "Login form is visible") - .setValue('@emailInput', email) - .setValue('@pwdInput', pass) - .click('@loginButton'); - }, - - expectAlert: function (message) { - return this - .waitForElementVisible('@alertMessage', 10000) - .assert.containsText('@alertMessage', message); - }, -} - -module.exports = { - url: '/auth/login', - commands: [loginActions], - elements: { - form: '.pane form', - emailInput: 'input[name=email]', - pwdInput: 'input[name=password]', - loginButton: 'form button', - alertMessage: '.alert-message', - requiredAlert: 'p.status-danger', - orbLogo: '#orb-pane-div img', - orbCaption: '#orb-pane-div p', - forgotPwdLink: 'a.forgot-password', - registerLink: '[aria-label="Register"] a' - } -} diff --git a/orb_tests/page_objects/register.js b/orb_tests/page_objects/register.js deleted file mode 100644 index 4ebba3863..000000000 --- a/orb_tests/page_objects/register.js +++ /dev/null @@ -1,28 +0,0 @@ -var registerActions = { - with: function (userData) { - return this - .navigate() - .waitForElementVisible('@form', 10000) - .setValue('@emailInput', email) - .setValue('@pwdInput', pass) - .click('@loginButton'); - }, - - expectAlert: function (message) { - return this - .waitForElementVisible('@alertMessage', 10000) - .assert.containsText('@alertMessage', message); - }, -} - -module.exports = { - url: '/auth/register', - commands: [registerActions], - elements: { - form: '.pane form', - emailInput: 'input[name=email]', - pwdInput: 'input[name=password]', - loginButton: 'form button', - alertMessage: '.alert-message', - } -} diff --git a/orb_tests/page_objects/sinks.js b/orb_tests/page_objects/sinks.js deleted file mode 100644 index 7413362c4..000000000 --- a/orb_tests/page_objects/sinks.js +++ /dev/null @@ -1,167 +0,0 @@ -var commands = { - sinkManagementPage: function() { - return this.waitForElementVisible('@allSinksPage') - .verify.containsText('@allSinksPage', 'Sink Management', "Page name is 'Sink Management'") - .verify.visible('ngx-sink-list-component.ng-star-inserted', "Sink list is visible") - - }, - - sinkCreation: function(name_label, description, remote_host, username, password, key, value, verify) { - return this.verify.attributeEquals('@sinkNext','aria-disabled', 'true', "'Next' button is not enabled") - .waitForElementVisible('@sinkNameLabel', "Sink Label named field is being displayed") - .setValue('@sinkNameLabel', name_label) - .verify.attributeEquals('@sinkNext','aria-disabled', 'false', "'Next' button is enabled") - .waitForElementVisible('@sinkDescription', "Sink Description field is being displayed") - .setValue('@sinkDescription', description) - .verify.attributeEquals('@sinkNext','aria-disabled', 'false', "'Next' button is enabled") - .click('@sinkNext') - .verify.attributeEquals('@sinkNext','aria-disabled', 'true', "'Next' button is not enabled") - .waitForElementVisible('@sinkRemoteHost', "Sink Remote Host field is being displayed") - .setValue('@sinkRemoteHost', remote_host) - .verify.attributeEquals('@sinkNext','aria-disabled', 'true', "'Next' button is not enabled") - .waitForElementVisible('@sinkUsername', "Sink username field is being displayed") - .setValue('@sinkUsername', username) - .verify.attributeEquals('@sinkNext','aria-disabled', 'true', "'Next' button is not enabled") - .waitForElementVisible('@sinkPassword', "Sink password field is being displayed") - .setValue('@sinkPassword', password) - .verify.attributeEquals('@sinkNext','aria-disabled', 'false', "'Next' button is enabled") - .click('@sinkNext') - .verify.attributeEquals('@sinkNext','aria-disabled', 'true', "'Next' button is not enabled") - .waitForElementVisible('@key', "Sink key field is being displayed") - .setValue('@key', key) - .verify.attributeEquals('@sinkNext','aria-disabled', 'true', "'Next' button is not enabled") - .waitForElementVisible('@value', "Sink value field is being displayed") - .setValue('@value', value) - .verify.attributeEquals('@sinkNext','aria-disabled', 'true', "'Next' button is not enabled") - .waitForElementVisible('@addTag', "Add tags button is being displayed") - .click('@addTag') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', verify, "Confirmation message is being correctly displayed") - }, - - sinkVisualization: function() { - return this.verify.not.elementPresent('.cdk-overlay-backdrop', "Sink visualization modal is not being displayed") - .verify.attributeEquals('button.orb-action-hover:nth-child(1)', 'aria-disabled', 'false', "Visualization button is enabled") - .click('button.orb-action-hover:nth-child(1)') - .verify.elementPresent('.cdk-overlay-backdrop', "Sink visualization modal is being displayed") - .verify.containsText('.nb-card-medium > nb-card-header:nth-child(1)', 'Sink Details', "Header of visualization page is correctly written") - }, - - sinkEdit: function() { - return this.verify.not.elementPresent('.cdk-overlay-backdrop', "Sink edit page is not being displayed") - .verify.attributeEquals('button.orb-action-hover:nth-child(2)', 'aria-disabled', 'false', "Edit sink button is enabled") - .click('button.orb-action-hover:nth-child(2)') - .sinkEditPage() - - }, - - sinkEditPage: function() { - return this.verify.containsText('.xng-breadcrumb-trail', 'Edit Sink', "Page is named 'Edit Sink'") - .verify.containsText('ngx-sink-add-component.ng-star-inserted > div:nth-child(1) > header:nth-child(1)', 'Edit Sink', "Option is named 'Edit Sink'") - .verify.containsText('@editSinkHeader', 'Sink Details', "Sink Details is being displayed") - .verify.containsText('@editSinkHeader', 'Provide a name and description for the Sink', "Help message for name is correctly written") - .verify.containsText('@editSinkHeader', 'Sink Destination', "Sink Destination is being correctly displayed") - .verify.containsText('@editSinkHeader', 'Configure your Sink settings', "Help message for settings is correctly written") - .verify.containsText('@editSinkHeader', 'Sink Tags', "Sink Tags is being displayed") - .verify.containsText('@editSinkHeader', 'Enter tags for this Sink', "Help message for tags is correctly written") - .verify.containsText('@editSinkForm', 'Name Label', "Name Label is being displayed") - .verify.containsText('@editSinkForm', 'Sink Description', "Sink Description is being displayed") - .verify.containsText('@editSinkForm', 'Sink Type', "Sink Type is being displayed") - .verify.attributeEquals('@sinkNext','aria-disabled', 'false', "'Next' button is enabled") - - }, - - sinkEditAttribute: function(attribute, value) { - return this.verify.attributeEquals('@sinkNext','aria-disabled', 'false', "'Next' button is enabled") - .waitForElementVisible(attribute, "Element edition is enabled") - .setValue(attribute, value) - .verify.attributeEquals('@sinkNext','aria-disabled', 'false', "'Next' button is enabled") - - }, - - sinkEditTags: function(key, value, key_value, value_value) { - return this.waitForElementVisible(key, "Key is being displayed") - .waitForElementVisible(value, "Value is being displayed") - .setValue(key, key_value) - .setValue(value, value_value) - .waitForElementVisible('@addTag', "'Add Tags' button is visible") - .click('@addTag') - .verify.attributeEquals('@sinkNext','aria-disabled', 'false', "'Next' button is enabled") - - }, - - - sinkDelete: function() { - return this.verify.attributeEquals('button.orb-action-hover:nth-child(3)', 'aria-disabled', 'false', "'Delete' button is enabled") - .click('button.orb-action-hover:nth-child(3)') - .verify.attributeEquals('@deleteSink','aria-disabled', 'true', "'Confirm Delete' button is not enabled") - .verify.visible('@sinkDeleteModal', "Delete modal is visible") - .verify.containsText('ngx-sink-delete-component.ng-star-inserted > nb-card:nth-child(1) > nb-card-header:nth-child(1)', 'Delete Sink Confirmation', "Header of delete modal is correctly written") - .verify.containsText('@sinkDeleteModal', 'Are you sure you want to delete this Sink? This may cause Datasets which use this Sink to become invalid. This action cannot be undone.', "Help message for delete is correctly written") - .verify.containsText('@sinkDeleteModal', 'To confirm, type your Sink name exactly as it appears', "Confirm message is correctly wirtten") - .getAttribute('.input-full-width', 'placeholder', function(result) {this.setValue('.input-full-width', result.value) }) - .verify.attributeEquals('@deleteSink','aria-disabled', 'false', "'Confirm Delete' button is enabled") - .click('@deleteSink') - .verify.containsText('span.title', 'Sink successfully deleted', "Confirmation message is being correctly displayed") - //bug insert count - .verify.containsText('.empty-row', 'No data to display', "List sink is correctly reload") - - }, - - sinkCheckEdition: function(value) { - return this.sinkVisualization() - .verify.containsText('ngx-sink-details-component.ng-star-inserted',value, "Element is correctly edited") - .click('.nb-close') - .sinkManagementPage() - }, - - countSinks: function(browser) { - return this.getText('.page-count', function(result){ - //console.log('Value is:', result.value); - if (result.value == "0 total") { - browser.expect.elements('datatable-row-wrapper').count.to.equal(parseInt(result.value)) - - browser.verify.containsText('.sink-info-accent', 'There are no Sinks yet.', "Info message of Sinks count is correctly displayed") - browser.verify.containsText('.empty-row', 'No data to display', "View table info message is correctly displayed") - } else { - browser.expect.elements('datatable-row-wrapper').count.to.equal(parseInt(result.value)) - browser.verify.containsText('.justify-content-between > div:nth-child(1)', 'sinks total', "Beginning of info message is correctly displayed") - browser.verify.containsText('.justify-content-between > div:nth-child(1)', parseInt(result.value), "Number of Sinks is correctly displayed") - browser.verify.containsText('.justify-content-between > div:nth-child(1)', 'have errors.', "End of info message is correctly displayed") - } - })} - -}; - -module.exports = { - url: '/pages/sinks', - elements: { - username: 'input[id=input-email]', - pwd: 'input[id=input-password]', - submit: '.appearance-filled', - loginBody: 'div.login_wrapper', - allSinksPage : '.xng-breadcrumb-trail', - sinkNameLabel:'input[data-orb-qa-id=name]', - sinkDescription: 'input[data-orb-qa-id=description]', - sinkNext: 'button[data-orb-qa-id=next]', - sinkRemoteHost: 'input[data-orb-qa-id=remote_host]', - sinkUsername: 'input[data-orb-qa-id=username]', - sinkPassword: 'input[data-orb-qa-id=password]', - key: 'input[data-orb-qa-id=key]', - value: 'input[data-orb-qa-id=value]', - addTag: 'button[data-orb-qa-id=addTag]', - spanTitle: 'span.title', - register: '.text-link', - fullNameInput:'input[id=input-name]', - confirmPassword: 'input[id=input-re-password]', - deleteSink: '.orb-sink-delete-warning-button', - cancel: 'button[data-orb-qa-id=cancel]', - back: 'button[data-orb-qa-id=back]', - previous: 'button[data-orb-qa-id=previous]', - editSinkHeader: '.header', - editSinkForm: 'form.ng-pristine', - sinkDeleteModal: 'ngx-sink-delete-component.ng-star-inserted > nb-card:nth-child(1)' - }, - commands: [commands] -}; diff --git a/orb_tests/page_objects/topbar.js b/orb_tests/page_objects/topbar.js deleted file mode 100644 index 014afa63d..000000000 --- a/orb_tests/page_objects/topbar.js +++ /dev/null @@ -1,15 +0,0 @@ -var userActions = { - expectLoggedUser: function (email) { - return this.waitForElementVisible('@userInfo', 10000, "User info is visible") - //bug on orb - // .waitForElementVisible('@userInfo', 10000, "User info is visible") - .assert.containsText('@userInfo', email, "User is displayed on topbar"); - } -} - -module.exports = { - commands: [userActions], - elements: { - userInfo: '.user-name', - } -} diff --git a/orb_tests/tests/agents/1goToAgentsPage.js b/orb_tests/tests/agents/1goToAgentsPage.js deleted file mode 100644 index bbdfaf8a5..000000000 --- a/orb_tests/tests/agents/1goToAgentsPage.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - '@disabled': false, - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - -'Go to Agent page from Orb Home': function(browser){ - const agentsPage = browser.launch_url + '/pages/fleet/agents' - browser - .verify.visible('li.menu-item:nth-child(2)', "Fleet Management is visible on ORB menu") - .verify.containsText('[title="Fleet Management"]', 'Fleet Management', "Fleet management is correctly writen") - .click('li.menu-item:nth-child(2)') - .waitForElementVisible('xpath', '/html/body/ngx-app/ngx-pages/ngx-one-column-layout/nb-layout/div/div/div/nb-sidebar/div/div/nb-menu/ul/li[2]/ul/li[2]/a', "Agents is visible on ORB menu") - .click('xpath','/html/body/ngx-app/ngx-pages/ngx-one-column-layout/nb-layout/div/div/div/nb-sidebar/div/div/nb-menu/ul/li[2]/ul/li[1]/a') - .verify.urlEquals(agentsPage) - browser.page.agents().AgentsPage() - }} diff --git a/orb_tests/tests/agents/2agent_page.js b/orb_tests/tests/agents/2agent_page.js deleted file mode 100644 index b8b85e865..000000000 --- a/orb_tests/tests/agents/2agent_page.js +++ /dev/null @@ -1,54 +0,0 @@ -module.exports = { - '@disabled': false, - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - - 'agents page info': function(browser) { - - const agents = browser.page.agents(); - - agents - .navigate() - .AgentsPage() - }, - - 'Count of agents': function(browser){ - const agents = browser.page.agents(); - - agents - //bug - need to remove this time - .navigate() - .pause(1000) - .getText('.page-count', function(result){ - //console.log('Value is:', result.value); - if (result.value == "0 total") { - browser.expect.elements('datatable-row-wrapper').count.to.equal(parseInt(result.value)) - - browser.verify.containsText('.sink-info-accent', 'There are no Agents yet.', "Info message of Agents count is correctly displayed") - browser.verify.containsText('.empty-row', 'No data to display', "View table info message is correctly displayed") - } else { - browser.expect.elements('datatable-row-wrapper').count.to.equal(parseInt(result.value)) - browser.verify.containsText('.justify-content-between > div:nth-child(1)', 'You have', "Beginning of info message is correctly displayed") - browser.verify.containsText('.justify-content-between > div:nth-child(1)', parseInt(result.value), "Number of Agents is correctly displayed") - browser.verify.containsText('.justify-content-between > div:nth-child(1)', 'agents deployed in', "End of info message is correctly displayed") - } - }) - - // .pause(2000) - } - } - \ No newline at end of file diff --git a/orb_tests/tests/agents/3newAgent.js b/orb_tests/tests/agents/3newAgent.js deleted file mode 100644 index 8d31e2bb5..000000000 --- a/orb_tests/tests/agents/3newAgent.js +++ /dev/null @@ -1,41 +0,0 @@ -module.exports = { - '@disabled': false, - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - - 'Agent Creation using close button' : function(browser){ - var agents = browser.page.agents() - agents - .new() - .agentCreationPage() - .agentCreation('newAgent', 'key', 'value', 'Agent successfully created', '@closeCredentialsModal') - - - }, - - 'Agent Creation using "x" button' : function(browser){ - var agents = browser.page.agents() - agents - .new() - .agentCreationPage() - .agentCreation('new2Agent', 'key', 'value', 'Agent successfully created', '@close') - - - } - - - } diff --git a/orb_tests/tests/agents/4agentsVisualization.js b/orb_tests/tests/agents/4agentsVisualization.js deleted file mode 100644 index 67dee9262..000000000 --- a/orb_tests/tests/agents/4agentsVisualization.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - -'Agent Group Visualization': function(browser){ - var agents = browser.page.agents() - agents - .navigate() - .agentsVisualization() - .click('@close') - .AgentsPage() - .agentsVisualization() - .click('@edit') - .click('@back') - -}} diff --git a/orb_tests/tests/agents/5agentEdit.js b/orb_tests/tests/agents/5agentEdit.js deleted file mode 100644 index 6d1abbb74..000000000 --- a/orb_tests/tests/agents/5agentEdit.js +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - -'Agent Group Edit': function(browser){ - var agents = browser.page.agents() - agents - .navigate() - - .agentsVisualization() - .click('@edit') - .click('@back') - .AgentsPage() - .agentsVisualization() - .click('@edit') - .agentsEdit('agentEdited', 'region', 'br', 'region2', 'usa', 'Agent successfully updated') - .choose_last_element() - .agentCheck('agentEdited', 'region: br') - .choose_last_element() - .agentCheck('agentEdited', 'region2: usa') -}} diff --git a/orb_tests/tests/agents/9agentsDelete.js b/orb_tests/tests/agents/9agentsDelete.js deleted file mode 100644 index fd6355139..000000000 --- a/orb_tests/tests/agents/9agentsDelete.js +++ /dev/null @@ -1,33 +0,0 @@ -module.exports = { - '@disabled': false, - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - - 'Agent Delete' : function(browser){ - var agents = browser.page.agents() - agents - .navigate() - .AgentsPage() - // bug need to remove this pause - .pause(2000) - .countAgent(browser) - .agentsDelete() - .AgentsPage() - .agentsDelete() - .countAgent(browser) - - }} diff --git a/orb_tests/tests/agents_groups/1goToAgentGroupPage.js b/orb_tests/tests/agents_groups/1goToAgentGroupPage.js deleted file mode 100644 index 27a2c3731..000000000 --- a/orb_tests/tests/agents_groups/1goToAgentGroupPage.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - -'Go to Agent Groups page from Orb Home': function(browser){ - const agentGroupsPage = browser.launch_url + '/pages/fleet/groups'; - browser - .verify.visible('li.menu-item:nth-child(2)', "Fleet Management is visible on ORB menu") - .verify.containsText('[title="Fleet Management"]', 'Fleet Management', "Fleet management is correctly writen") - .click('li.menu-item:nth-child(2)') - .waitForElementVisible('xpath', '/html/body/ngx-app/ngx-pages/ngx-one-column-layout/nb-layout/div/div/div/nb-sidebar/div/div/nb-menu/ul/li[2]/ul/li[2]/a', "Agent Groups is visible on ORB menu") - .click('xpath','/html/body/ngx-app/ngx-pages/ngx-one-column-layout/nb-layout/div/div/div/nb-sidebar/div/div/nb-menu/ul/li[2]/ul/li[2]/a') - .verify.urlEquals(agentGroupsPage) - }} diff --git a/orb_tests/tests/agents_groups/2list-view.js b/orb_tests/tests/agents_groups/2list-view.js deleted file mode 100644 index 131bb9625..000000000 --- a/orb_tests/tests/agents_groups/2list-view.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'Agent Group Page' : function(browser) { - var agent_groups = browser.page.agent_groups() - agent_groups - .navigate() - .listView() - }, - - - -} - - diff --git a/orb_tests/tests/agents_groups/3newAgentWithDescription.js b/orb_tests/tests/agents_groups/3newAgentWithDescription.js deleted file mode 100644 index cca64c5ed..000000000 --- a/orb_tests/tests/agents_groups/3newAgentWithDescription.js +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'Agent Group Creation - with description' : function(browser){ - var agent_groups = browser.page.agent_groups() - agent_groups - .navigate() - .new() - //bug need to insert count agent groups - .agentGroupCreation('nam3', "some_description", "key", "value", "Agent Group successfully created") - //bug - incomplete reload. need to remove this navigate - //bug - delay. need to remove this paude - .navigate() - .pause(1000) - .agentGroupVisualization() - .agentGroupCheck('nam3', "some_description", "key", "value") - .countAgentGroups(browser) - - - }} diff --git a/orb_tests/tests/agents_groups/4newAgentGroupsWithoutDescription.js b/orb_tests/tests/agents_groups/4newAgentGroupsWithoutDescription.js deleted file mode 100644 index 52bfeb630..000000000 --- a/orb_tests/tests/agents_groups/4newAgentGroupsWithoutDescription.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - -'Agent Group Creation - without description' : function(browser){ - var agent_groups = browser.page.agent_groups() - agent_groups - .navigate() - .listView() - .new() - .agentGroupCreationPage() - .agentGroupCreation('name', "", "key", "value", "Agent Group successfully created") - //bug - incomplete reload. need to remove this navigate - //bug - delay. need to remove this paude - .navigate() - .pause(1000) - .countAgentGroups(browser) -}} diff --git a/orb_tests/tests/agents_groups/5agentGroupVisualization.js b/orb_tests/tests/agents_groups/5agentGroupVisualization.js deleted file mode 100644 index 65b1d62cb..000000000 --- a/orb_tests/tests/agents_groups/5agentGroupVisualization.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - -'Agent Group Visualization': function(browser){ - var agent_groups = browser.page.agent_groups() - agent_groups - .navigate() - .agentGroupVisualization() - .click('@close') - .listView() - .agentGroupVisualization() - .click('@edit') - .click('@back') - -}} diff --git a/orb_tests/tests/agents_groups/6agentGroupEdit.js b/orb_tests/tests/agents_groups/6agentGroupEdit.js deleted file mode 100644 index f901d90b0..000000000 --- a/orb_tests/tests/agents_groups/6agentGroupEdit.js +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - -'Agent Group Edit': function(browser){ - var agent_groups = browser.page.agent_groups() - agent_groups - .navigate() - - .agentGroupVisualization() - .click('@edit') - .click('@back') - .listView() - .agentGroupVisualization() - .click('@edit') - .agentGroupsEdit('agentGroup_Edited', 'edited_desc', 'editedKey', 'editedValue', 'secondKey', 'secondValue', 'Agent Group successfully updated') - .choose_last_element() - .agentGroupCheck('agentGroup_Edited', 'edited_desc', 'editedKey', 'editedValue') - .choose_last_element() - .agentGroupCheck('agentGroup_Edited', 'edited_desc', 'secondKey', 'secondValue') -}} diff --git a/orb_tests/tests/agents_groups/7removeAgentGroup.js b/orb_tests/tests/agents_groups/7removeAgentGroup.js deleted file mode 100644 index 6763b6bb9..000000000 --- a/orb_tests/tests/agents_groups/7removeAgentGroup.js +++ /dev/null @@ -1,36 +0,0 @@ - - module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'Agent groups delete' : function(browser){ - - var agent_groups = browser.page.agent_groups() - //bug - incomplete reload. need to remove this navigate - //bug - delay. need to remove this paude - agent_groups - .navigate() - .pause(1000) - .countAgentGroups(browser) - .agentGroupsDelete() - .listView() - .agentGroupsDelete() - .countAgentGroups(browser) - //bug need to insert loop - - - }} diff --git a/orb_tests/tests/agents_groups/new_refactor.js b/orb_tests/tests/agents_groups/new_refactor.js deleted file mode 100644 index 39e041ea5..000000000 --- a/orb_tests/tests/agents_groups/new_refactor.js +++ /dev/null @@ -1,80 +0,0 @@ -module.exports = { - '@disabled': true, - - - - beforeEach: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'New Agent Group' : function(browser) { - const agentGroup = browser.page.agent_groups(); - const data = { - step1: { - name: 'group1', - description: 'group1 description', - }, - step2: [ - { - key: 'region', - value: 'br', - }, - { - key: 'backend', - value: 'visor', - }, - { - key: 'pop', - value: 'pop03', - } - ] - }; - - agentGroup.new() - .waitForElementVisible('@newHeading', 10000) - .verify.containsText('@activeBreadcrumb', 'New Agent Group') - .verify.containsText('@newHeading', 'New Agent Group') - .verify.containsText({ selector: '@stepLabel', index: 0 }, 'Agent Group Details') - .verify.containsText({ selector: '@stepCaption', index: 0 }, 'This is how you will be able to easily identify your Agent Group') - .verify.containsText({ selector: '@stepLabel', index: 1 }, 'Agent Group Tags') - .verify.containsText({ selector: '@stepCaption', index: 1 }, 'Set the tags that will be used to group Agents') - .verify.containsText({ selector: '@stepLabel', index: 2 }, 'Review & Confirm') - .verify.not.enabled('@next') - .verify.enabled('@back') - .verify.containsText({ selector: '@detailsLabels', index: 0 }, 'Agent Group Name*') - .setValue('@newNameInput', data.step1.name) - .verify.enabled('@next') - .verify.containsText({ selector: '@detailsLabels', index: 1 }, 'Agent Group Description') - .setValue('@newDescriptionInput', data.step1.description) - .click('@next') - - .verify.containsText({selector: '@tagLabels', index: 0}, 'Key*') - .verify.containsText({selector: '@tagLabels', index: 2}, 'Value*') - .verify.not.enabled('@next') - .verify.enabled('button.status-primary:nth-child(1)') - - data.step2.forEach((tag, i) => { - agentGroup.setValue('@keyInput', tag.key) - .setValue('@valueInput', tag.value) - .click('@addTagButton') - .waitForElementVisible({ selector: '@tagChip', index: 0 }, 10000) - .verify.containsText({ selector: '@tagChip', index: 0 }, `${tag.key}: ${tag.value}`) - }); - - agentGroup.verify.enabled('@next') - .click('@next') - - - } -}; diff --git a/orb_tests/tests/login/1invalid_regex_email.js b/orb_tests/tests/login/1invalid_regex_email.js deleted file mode 100644 index 2dbc34567..000000000 --- a/orb_tests/tests/login/1invalid_regex_email.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - '@disabled': false, - - 'Login with invalid regex for email': (browser) => { - const login = browser.page.login(); - - login - .with('tester@email', '12345678') - .click('@pwdInput') - .waitForElementVisible('@requiredAlert', 10000, "Help message about invalid regex for email is visible") - .assert.containsText('@requiredAlert', 'Email should be the real one!', "Help message contains text 'Email should be the real one!'") - .assert.not.enabled('@loginButton', "Login button is not enabled"); - } -} diff --git a/orb_tests/tests/login/2layout.js b/orb_tests/tests/login/2layout.js deleted file mode 100644 index 7613040a8..000000000 --- a/orb_tests/tests/login/2layout.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - '@disabled': false, - - 'layout components should be consistent with spec': (browser) => { - const forgotPwdLink = browser.launch_url + '/auth/request-password'; - const registerLink = browser.launch_url + '/auth/register'; - - const login = browser.page.login(); - - login - .navigate() - .waitForElementVisible('@orbLogo', 10000, "Orb logo is being displayed") - .assert.containsText('@orbCaption', 'An Open-Source dynamic edge observability platform', "Message 'An Open-Source dynamic edge observability platform' is being displayed") - .assert.containsText('@forgotPwdLink', 'Forgot Password?', "'Forgot Password' option is being displayed") - .assert.attributeEquals('@forgotPwdLink', 'href', forgotPwdLink, "'Forgot Password' is clickable") - .assert.containsText('@registerLink', 'Register', "'Register' option is being displayed") - .assert.attributeEquals('@registerLink', 'href', registerLink, "'Register' is clickable") - .assert.not.enabled('@loginButton', "Login button is not enabled"); - } -} diff --git a/orb_tests/tests/login/3password_length.js b/orb_tests/tests/login/3password_length.js deleted file mode 100644 index 9f6f5b551..000000000 --- a/orb_tests/tests/login/3password_length.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - '@disabled': false, - - 'Login with an invalid password legth ': (browser) => { - const login = browser.page.login(); - - login - .with('wrong@email.com', '1234567') - .click('@emailInput') - .waitForElementVisible('@requiredAlert', 10000, "Help message about password length is visible") - .assert.containsText('@requiredAlert', 'Password should contain from 8 to 50 characters', "Help messagem about password lenght contains text 'Password should contain from 8 to 50 characters'") - .assert.not.enabled('@loginButton', "Login button is not enabled"); - } -} diff --git a/orb_tests/tests/login/4password_required.js b/orb_tests/tests/login/4password_required.js deleted file mode 100644 index fec3dccc5..000000000 --- a/orb_tests/tests/login/4password_required.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - '@disabled': false, - - 'Login without password': (browser) => { - const login = browser.page.login(); - - login - .with('tester@email.com', '') - .click('@emailInput') - .waitForElementVisible('@requiredAlert', 10000, "Password request help message is visible") - .assert.containsText('@requiredAlert', 'Password is required!', "Help message is 'Password is required!'") - .assert.not.enabled('@loginButton', "Login button is not enabled"); - } -} diff --git a/orb_tests/tests/login/5successful.js b/orb_tests/tests/login/5successful.js deleted file mode 100644 index 8ad6c0abe..000000000 --- a/orb_tests/tests/login/5successful.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - '@disabled': false, - - 'Login with registered email and correct password': function (browser) { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - } -} diff --git a/orb_tests/tests/login/6without_email.js b/orb_tests/tests/login/6without_email.js deleted file mode 100644 index 80c34aaaf..000000000 --- a/orb_tests/tests/login/6without_email.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - '@disabled': false, - - 'Login without email': (browser) => { - const login = browser.page.login(); - - login - .with('', '12345678') - .click('@pwdInput') - .waitForElementVisible('@requiredAlert', 10000, "Email request help message is visible") - .assert.containsText("@requiredAlert", "Email is required!", "Help message is 'Email is required'") - .assert.not.enabled('@loginButton', "Login button is not enabled"); - } -} diff --git a/orb_tests/tests/login/7wrong_combination.js b/orb_tests/tests/login/7wrong_combination.js deleted file mode 100644 index 801b4e238..000000000 --- a/orb_tests/tests/login/7wrong_combination.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = { - '@disabled': false, - - 'Login with wrong combination of email and password': (browser) => { - const login = browser.page.login(); - - login - .with('wrong@email.com', 'any-pass') - .expectAlert('Login/Email combination is not correct, please try again.', "Alert message is visible and contains text 'Login/Email combination is not correct, please try again.'"); - }, - - - 'Login with wrong email and registered password': (browser) => { - const login = browser.page.login(); - - login - .with('testerr@email.com', '12345678') - .expectAlert('Login/Email combination is not correct, please try again.', "Alert message is visible and contains text 'Login/Email combination is not correct, please try again.'"); - }, - - - 'Login with registered email and wrong password': (browser) => { - const login = browser.page.login(); - - login - .with('tester@email.com', '123456789') - .expectAlert('Login/Email combination is not correct, please try again.', "Alert message is visible and contains text 'Login/Email combination is not correct, please try again.'"); - } -} diff --git a/orb_tests/tests/register/creatAccount.js b/orb_tests/tests/register/creatAccount.js deleted file mode 100644 index 6a4564a2f..000000000 --- a/orb_tests/tests/register/creatAccount.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { -'@disabled': false, - -'Create an account' : function(browser) { - var login = browser.page.login() - var accountRegister = browser.page.accountRegister() - const registerLink = browser.launch_url + '/pages'; - - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.navigate() - accountRegister.orbRegister() - .verify.urlEquals(registerLink) - - - }} diff --git a/orb_tests/tests/sinks/1goToSinkPage.js b/orb_tests/tests/sinks/1goToSinkPage.js deleted file mode 100644 index 37ca97087..000000000 --- a/orb_tests/tests/sinks/1goToSinkPage.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - '@disabled': false, - - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'Go to sink page' : (browser) => { - const sinks = browser.page.sinks(); - - sinks - .assert.visible('li.menu-item:nth-child(4)', "Sink Management is visible on ORB menu") - .click('li.menu-item:nth-child(4)') - .sinkManagementPage() - - - -}} diff --git a/orb_tests/tests/sinks/2newSink.js b/orb_tests/tests/sinks/2newSink.js deleted file mode 100644 index f34e69281..000000000 --- a/orb_tests/tests/sinks/2newSink.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = { - '@disabled': false, - - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'Sink Creation' : (browser) => { - const sinks = browser.page.sinks(); - - sinks - .navigate() - //.countSinks() - .verify.visible('.appearance-ghost') - .verify.attributeEquals('.appearance-ghost','aria-disabled', 'false') - .click('.appearance-ghost') - .sinkCreation('some_name', 'some_description', 'remote_host', 'tester', 'password', 'key', 'value', 'Sink successfully created') - .sinkManagementPage() - //bug need to remove this pause - .pause(2000) - .countSinks(browser) - - -}} diff --git a/orb_tests/tests/sinks/3duplicateSink.js b/orb_tests/tests/sinks/3duplicateSink.js deleted file mode 100644 index 0e973facf..000000000 --- a/orb_tests/tests/sinks/3duplicateSink.js +++ /dev/null @@ -1,38 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'Duplicate sink creation' : (browser) => { - const sinks = browser.page.sinks(); - - sinks - .navigate() - .sinkManagementPage() - .verify.visible('.appearance-ghost', "'New Sink' button is visible") - .verify.attributeEquals('.appearance-ghost','aria-disabled', 'false', "'New Sink' button is enabled") - .click('.appearance-ghost') - .sinkCreation('some_name', 'some_description', 'remote_host', 'tester', 'password', 'key', 'value', 'Failed to create Sink') - .waitForElementVisible('@previous') - .click('@previous') - .waitForElementVisible('@back') - .click('@back') - .waitForElementVisible('@cancel') - .click('@cancel') - .waitForElementVisible('@cancel') - .click('@cancel') - .sinkManagementPage() - -}} diff --git a/orb_tests/tests/sinks/4sinkVisualization.js b/orb_tests/tests/sinks/4sinkVisualization.js deleted file mode 100644 index 96942d5cb..000000000 --- a/orb_tests/tests/sinks/4sinkVisualization.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - - 'Sink Visualization': (browser) => { - const sinks = browser.page.sinks(); - - - sinks - .navigate() - .sinkVisualization() - .click('.nb-close') - .sinkVisualization() - .click('.sink-edit-button') - .sinkEditPage() - .click('@cancel') - -}} diff --git a/orb_tests/tests/sinks/5sinkVisualizationAndEditDesc.js b/orb_tests/tests/sinks/5sinkVisualizationAndEditDesc.js deleted file mode 100644 index 660df107c..000000000 --- a/orb_tests/tests/sinks/5sinkVisualizationAndEditDesc.js +++ /dev/null @@ -1,38 +0,0 @@ -module.exports = { - '@disabled': false, - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - - 'Sink Visualization and Edit Description' : (browser) => { - const sinks = browser.page.sinks(); - - sinks - .navigate() - .sinkVisualization() - .click('.nb-close') - .sinkVisualization() - .click('.sink-edit-button') - .sinkEditPage() - .sinkEditAttribute('@sinkDescription','_new') - .click('@sinkNext') - .click('@sinkNext') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', 'Sink successfully updated', "Confirmation message is being correctly displayed") - .sinkCheckEdition('_new') - -}} diff --git a/orb_tests/tests/sinks/6sinkVisuAndEditRH.js b/orb_tests/tests/sinks/6sinkVisuAndEditRH.js deleted file mode 100644 index bf7e3cf48..000000000 --- a/orb_tests/tests/sinks/6sinkVisuAndEditRH.js +++ /dev/null @@ -1,39 +0,0 @@ -module.exports = { - '@disabled': false, - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - - - 'Sink Visualization and Edit Remote Host': (browser) => { - const sinks = browser.page.sinks(); - - sinks - .navigate() - .sinkVisualization() - .click('.nb-close') - .sinkVisualization() - .click('.sink-edit-button') - .sinkEditPage() - .click('@sinkNext') - .sinkEditAttribute('@sinkRemoteHost', '_new_rm') - .click('@sinkNext') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', 'Sink successfully updated', "Confirmation message is being correctly displayed") - .sinkCheckEdition('_new_rm') - -}} diff --git a/orb_tests/tests/sinks/7sinkVisualizationAndEditPass.js b/orb_tests/tests/sinks/7sinkVisualizationAndEditPass.js deleted file mode 100644 index f79195b8d..000000000 --- a/orb_tests/tests/sinks/7sinkVisualizationAndEditPass.js +++ /dev/null @@ -1,39 +0,0 @@ -module.exports = { - '@disabled': false, - - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - - - 'Sink Visualization and Edit Password': (browser) => { - const sinks = browser.page.sinks(); - - sinks - .navigate() - .sinkVisualization() - .click('.nb-close') - .sinkVisualization() - .click('.sink-edit-button') - .sinkEditPage() - .click('@sinkNext') - .sinkEditAttribute('@sinkPassword', '_new_pass') - .click('@sinkNext') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', 'Sink successfully updated', "Confirmation message is being correctly displayed") - -}} diff --git a/orb_tests/tests/sinks/8sinkVisuAndEditTags.js b/orb_tests/tests/sinks/8sinkVisuAndEditTags.js deleted file mode 100644 index 66c3319b9..000000000 --- a/orb_tests/tests/sinks/8sinkVisuAndEditTags.js +++ /dev/null @@ -1,42 +0,0 @@ -module.exports = { - '@disabled': false, - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - - - 'Sink Visualization and Edit Tags' : (browser) => { - const sinks = browser.page.sinks(); - - sinks - .navigate() - .sinkVisualization() - .click('.nb-close') - .sinkVisualization() - .click('.sink-edit-button') - .sinkEditPage() - .click('@sinkNext') - .click('@sinkNext') - .click('.ml-1') - .verify.attributeEquals('@submit','aria-disabled', 'true') - .sinkEditTags('@key', '@value', 'new_key', 'new_value') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', 'Sink successfully updated', "Confirmation message is being correctly displayed") - .sinkCheckEdition('new_key') - .sinkCheckEdition('new_value') - -}} diff --git a/orb_tests/tests/sinks/90sinkVisuAndEditUsername.js b/orb_tests/tests/sinks/90sinkVisuAndEditUsername.js deleted file mode 100644 index f9259d469..000000000 --- a/orb_tests/tests/sinks/90sinkVisuAndEditUsername.js +++ /dev/null @@ -1,39 +0,0 @@ -module.exports = { - '@disabled': false, - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - - 'Sink Visualization and Edit Username': (browser) => { - const sinks = browser.page.sinks(); - - sinks - .navigate() - .sinkVisualization() - .click('.nb-close') - .sinkVisualization() - .click('.sink-edit-button') - .sinkEditPage() - .click('@sinkNext') - .sinkEditAttribute('@sinkUsername', '_new_usr') - .click('@sinkNext') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', 'Sink successfully updated', "Confirmation message is being correctly displayed") - //BUG - //.sinkCheckEdition('_new_usr') - -}} diff --git a/orb_tests/tests/sinks/91sinkEditDescription.js b/orb_tests/tests/sinks/91sinkEditDescription.js deleted file mode 100644 index c1bf118ad..000000000 --- a/orb_tests/tests/sinks/91sinkEditDescription.js +++ /dev/null @@ -1,33 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - - 'Sink Edit Description': (browser) => { - const sinks = browser.page.sinks(); - - sinks - .navigate() - .sinkEdit() - .sinkEditAttribute('@sinkDescription','_n3w') - .click('@sinkNext') - .click('@sinkNext') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', 'Sink successfully updated', "Confirmation message is being correctly displayed") - .sinkCheckEdition('_n3w') - -}} diff --git a/orb_tests/tests/sinks/92sinkEditRH.js b/orb_tests/tests/sinks/92sinkEditRH.js deleted file mode 100644 index 2d07c81bc..000000000 --- a/orb_tests/tests/sinks/92sinkEditRH.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'Sink Edit Remote Host': (browser) => { - const sinks = browser.page.sinks(); - - - - sinks - .navigate() - .sinkEdit() - .click('@sinkNext') - .sinkEditAttribute('@sinkRemoteHost', '_n3w_rm') - .click('@sinkNext') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', 'Sink successfully updated', "Confirmation message is being correctly displayed") - .sinkCheckEdition('_n3w_rm') - -}} diff --git a/orb_tests/tests/sinks/93sinkEditUsername.js b/orb_tests/tests/sinks/93sinkEditUsername.js deleted file mode 100644 index 4de8a0b61..000000000 --- a/orb_tests/tests/sinks/93sinkEditUsername.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'Sink Edit Username' : (browser) => { - const sinks = browser.page.sinks(); - - - sinks - .navigate() - .sinkEdit() - .click('@sinkNext') - .sinkEditAttribute('@sinkUsername', '_n3w_usr') - .click('@sinkNext') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', 'Sink successfully updated', "Confirmation message is being correctly displayed") - // BUG - //.sinkCheckEdition('_n3w_usr') - -}} diff --git a/orb_tests/tests/sinks/94sinkEditPassword.js b/orb_tests/tests/sinks/94sinkEditPassword.js deleted file mode 100644 index d747a9a82..000000000 --- a/orb_tests/tests/sinks/94sinkEditPassword.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'Sink Edit Password': (browser) => { - const sinks = browser.page.sinks(); - - - sinks - .navigate() - .sinkEdit() - .click('@sinkNext') - .sinkEditAttribute('@sinkPassword', '_n3w_pass') - .click('@sinkNext') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', 'Sink successfully updated', "Confirmation message is being correctly displayed") - -}} diff --git a/orb_tests/tests/sinks/95sinkEditTags.js b/orb_tests/tests/sinks/95sinkEditTags.js deleted file mode 100644 index a32b67d59..000000000 --- a/orb_tests/tests/sinks/95sinkEditTags.js +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = { - '@disabled': false, - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'Sink Edit Tags' : (browser) => { - const sinks = browser.page.sinks(); - - sinks - .navigate() - .sinkEdit() - .click('@sinkNext') - .click('@sinkNext') - .click('.ml-1') - .verify.attributeEquals('@submit','aria-disabled', 'true') - .sinkEditTags('@key', '@value', '_n3w_key', '_n3w_value') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', 'Sink successfully updated', "Confirmation message is being correctly displayed") - .sinkCheckEdition('_n3w_key') - .sinkCheckEdition('_n3w_value') -}} diff --git a/orb_tests/tests/sinks/96sinkEditAll.js b/orb_tests/tests/sinks/96sinkEditAll.js deleted file mode 100644 index 9caddeca2..000000000 --- a/orb_tests/tests/sinks/96sinkEditAll.js +++ /dev/null @@ -1,44 +0,0 @@ -module.exports = { - '@disabled': false, - - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - 'Sink Edit Description, Remote Host, Username, Password and Keys' : (browser) => { - const sinks = browser.page.sinks(); - - sinks - .navigate() - .sinkEdit() - .sinkEditAttribute('@sinkDescription','_n33w') - .click('@sinkNext') - .sinkEditAttribute('@sinkRemoteHost', '_n33w_rm') - .sinkEditAttribute('@sinkUsername', '_n33w_usr') - .sinkEditAttribute('@sinkPassword', '_n33w_pass') - .click('@sinkNext') - .click('.ml-1') - .verify.attributeEquals('@submit','aria-disabled', 'true', "'Subimit' button is not enabled") - .sinkEditTags('@key', '@value', '_n33w_key', '_n33w_value') - .click('@sinkNext') - .click('@sinkNext') - .verify.containsText('span.title', 'Sink successfully updated', "Confirmation message is being correctly displayed") - .sinkCheckEdition('_n33w') - .sinkCheckEdition('_n33w_rm') - // BUG - //.sinkCheckEdition('_n33w_usr') - .sinkCheckEdition('_n33w_key') - .sinkCheckEdition('_n33w_value') - -}} diff --git a/orb_tests/tests/sinks/97removeSink.js b/orb_tests/tests/sinks/97removeSink.js deleted file mode 100644 index 0db8fb122..000000000 --- a/orb_tests/tests/sinks/97removeSink.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = { - '@disabled': false, - - before: (browser) => { - const login = browser.page.login(); - const topbar = browser.page.topbar(); - const email = 'tester@email.com'; - const pwd = '12345678'; - const maximizeWindowCallback = () => { - console.log('Window maximized'); - }; - browser.maximizeWindow(maximizeWindowCallback); - - login.with(email, pwd); - topbar.expectLoggedUser(email); - }, - - - 'Sink Delete' : (browser) => { - const sinks = browser.page.sinks(); - - sinks - .navigate() - // bug need to remove this pause - .pause(1000) - .countSinks(browser) - .sinkDelete() - .sinkManagementPage() - // bug need to remove this pause - .pause(1000) - .countSinks(browser) - .sinkManagementPage() - -}} diff --git a/pkg/config/config.go b/pkg/config/config.go index e6cf1aaf4..56c0eb8ca 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -6,6 +6,7 @@ package config import ( "fmt" + "time" "github.com/spf13/viper" ) @@ -39,6 +40,11 @@ type CacheConfig struct { Pass string `mapstructure:"pass"` DB string `mapstructure:"db"` } + +type InMemoryCacheConfig struct { + DefaultExpiration time.Duration `mapstructure:"default_expiration"` +} + type EsConfig struct { URL string `mapstructure:"url"` Pass string `mapstructure:"pass"` @@ -232,3 +238,13 @@ func LoadGRPCConfig(prefix string, svc string) GRPCConfig { cfg.Unmarshal(&aC) return aC } + +func LoadInMemoryCacheConfig(prefix string) InMemoryCacheConfig { + cfg := viper.New() + cfg.SetEnvPrefix(fmt.Sprintf("%s_cache", prefix)) + cfg.SetDefault("default_expiration", 2*time.Minute) + cfg.AutomaticEnv() + var icC InMemoryCacheConfig + cfg.Unmarshal(&icC) + return icC +} diff --git a/pkg/errors/types.go b/pkg/errors/types.go index 73186e2bb..f09a283c4 100644 --- a/pkg/errors/types.go +++ b/pkg/errors/types.go @@ -21,6 +21,51 @@ var ( // ErrMalformedEntity indicates a malformed entity specification ErrMalformedEntity = New("malformed entity specification") + // ErrEntityNameNotFound indicates that the entity name was not found + ErrEntityNameNotFound = New("malformed entity specificiation. name not found") + + // ErrBackendNotFound indicates that the backend field was not found + ErrBackendNotFound = New("malformed entity specification. backend field is expected") + + // ErrInvalidBackend indicates a malformed entity specification on backend field + ErrInvalidBackend = New("malformed entity specification. backend field is invalid") + + // ErrConfigFieldNotFound indicates that configuration field was not found + ErrConfigFieldNotFound = New("malformed entity specification. configuration field is expected") + + // ErrExporterFieldNotFound indicates that exporter field was not found + ErrExporterFieldNotFound = New("malformed entity specification. exporter field is expected on configuration field") + + // ErrAuthFieldNotFound indicates that authentication field was not found on configuration field + ErrAuthFieldNotFound = New("malformed entity specification. authentication fields are expected on configuration field") + + // ErrAuthTypeNotFound indicates that authentication type field was not found on the authentication field + ErrAuthTypeNotFound = New("malformed entity specification: authentication type field is expected on configuration field") + + // ErrInvalidAuthType indicates invalid authentication type + ErrInvalidAuthType = New("malformed entity specification. type key on authentication field is invalid") + + // ErrPasswordNotFound indicates that password key was not found + ErrPasswordNotFound = New("malformed entity specification. password key is expected on authentication field") + + // ErrEndPointNotFound indicates that endpoint field was not found on exporter field for otlp backend + ErrEndpointNotFound = New("malformed entity specification. endpoint field is expected on exporter field") + + // ErrInvalidEndpoint indicates that endpoint field is not valid + ErrInvalidEndpoint = New("malformed entity specification. endpoint field is invalid") + + // ErrInvalidPasswordType indicates invalid password key on authentication field + ErrInvalidPasswordType = New("malformed entity specification. password key on authentication field is invalid") + + // ErrInvalidUsernameType indicates invalid username key on authentication field + ErrInvalidUsernameType = New("malformed entity specification. username key on authentication field is invalid") + + // ErrRemoteHostNotFound indicates that remote host field was not found + ErrRemoteHostNotFound = New("malformed entity specification. remote host is expected on exporter field") + + // ErrInvalidRemoteHost indicates that remote host field is invalid + ErrInvalidRemoteHost = New("malformed entity specification. remote host type is invalid") + // ErrNotFound indicates a non-existent entity request. ErrNotFound = New("non-existent entity") diff --git a/pkg/types/maps.go b/pkg/types/maps.go index 0d71918ea..1e91f5230 100644 --- a/pkg/types/maps.go +++ b/pkg/types/maps.go @@ -6,6 +6,7 @@ package types import ( "encoding/json" + "github.com/orb-community/orb/pkg/errors" ) @@ -35,6 +36,30 @@ func (s *Metadata) Scan(src interface{}) error { return errors.New("type assertion failed") } +func FromMap(m map[string]interface{}) Metadata { + meta := make(Metadata, len(m)) + for k, v := range m { + meta[k] = v + } + return meta +} + +// GetSubMetadata gets the first substructure with the keyname or nil +func (s *Metadata) GetSubMetadata(key string) Metadata { + v := (*s)[key] + if v == nil { + return nil + } + switch v.(type) { + case map[string]interface{}: + return FromMap(v.(map[string]interface{})) + case Metadata: + return v.(Metadata) + } + + return nil +} + func (s *Metadata) RestrictKeys(predicate func(string) bool) { for key, _ := range *s { if predicate(key) { diff --git a/policies/redis/consumer/streams.go b/policies/redis/consumer/streams.go index 3b725b345..b9e54292f 100644 --- a/policies/redis/consumer/streams.go +++ b/policies/redis/consumer/streams.go @@ -131,7 +131,7 @@ func decodeAgentGroupRemove(event map[string]interface{}) removeAgentGroupEvent func decodeSinkRemove(event map[string]interface{}) removeSinkEvent { return removeSinkEvent{ sinkID: read(event, "sink_id", ""), - ownerID: read(event, "owner_id", ""), + ownerID: read(event, "owner", ""), } } diff --git a/python-test/README.md b/python-test/README.md index 4e25c4e8f..ae1535c98 100644 --- a/python-test/README.md +++ b/python-test/README.md @@ -96,6 +96,10 @@ Then fill in the correct values: - Bool - If true, uses orb_address as base to api and mqtt address using orb.live pattern. If false, requires you to add the corresponding addresses. - Default value: `true` +- **backend_type**: + - Str + - Sink backend type + - Default value: `prometheus` - **orb_cloud_api_address**: - Required if `use_orb_live_address_pattern` is false - URL of the Orb deployment API. Obs: You MUST include the protocol. diff --git a/python-test/features/benchmark.feature b/python-test/features/benchmark.feature index 7643afcee..9876e85ff 100644 --- a/python-test/features/benchmark.feature +++ b/python-test/features/benchmark.feature @@ -10,7 +10,7 @@ Feature: Integrated Benchmark Tests And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists When mixed policies are applied to the group Then this agent's heartbeat shows that policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within seconds diff --git a/python-test/features/integration.feature b/python-test/features/integration.feature index 2c01e329a..20ffd577a 100644 --- a/python-test/features/integration.feature +++ b/python-test/features/integration.feature @@ -6,7 +6,7 @@ Feature: Integration tests Scenario: General smoke test to validate private agent image Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When a new agent is created with 1 orb tag(s) And the agent container is started on an available port And the agent status is online @@ -42,7 +42,7 @@ Scenario: Create dataset with name conflict Given the Orb user has a registered account And the Orb user logs in And 1 Agent Group(s) is created with 2 orb tag(s) - And that a sink already exists + And that a sink with default configuration type already exists And 1 simple policies are applied to the group When a new dataset is requested to be created with the same name as an existent one Then the error message on response is failed to create dataset @@ -53,7 +53,7 @@ Scenario: Edit dataset using an already existent name (conflict) Given the Orb user has a registered account And the Orb user logs in And 1 Agent Group(s) is created with 2 orb tag(s) - And that a sink already exists + And that a sink with default configuration type already exists And 1 simple policies are applied to the group And 1 new dataset is created using the policy, last group and 1 sink When editing a dataset using a name already in use @@ -68,7 +68,7 @@ Scenario: Apply multiple advanced policies to an agent And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists When 14 mixed policies are applied to the group Then this agent's heartbeat shows that 14 policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds @@ -83,7 +83,7 @@ Scenario: Apply two simple policies to an agent And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists When 2 simple policies are applied to the group Then this agent's heartbeat shows that 2 policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds @@ -100,7 +100,7 @@ Scenario: apply one policy using multiple datasets to the same group And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists When 2 simple policies are applied to the group by 3 datasets each Then this agent's heartbeat shows that 2 policies are applied and all has status running And 3 datasets are linked with each policy on agent's heartbeat within 180 seconds @@ -117,7 +117,7 @@ Scenario: Remove group to which agent is linked And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running When 1 group(s) to which the agent is linked is removed @@ -137,7 +137,7 @@ Scenario: Remove policy from agent And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running When one of applied policies is removed @@ -158,7 +158,7 @@ Scenario: Remove dataset from agent with just one dataset linked And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 1 simple policies are applied to the group And this agent's heartbeat shows that 1 policies are applied and all has status running When a dataset linked to this agent is removed @@ -177,7 +177,7 @@ Scenario: Remove dataset from agent with more than one dataset linked And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 3 simple policies are applied to the group And this agent's heartbeat shows that 3 policies are applied and all has status running When a dataset linked to this agent is removed @@ -206,7 +206,7 @@ Scenario: Provision agent with tag matching existing group linked to a valid dat Given the Orb user has a registered account And the Orb user logs in And 1 Agent Group(s) is created with 3 orb tag(s) - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group When a new agent is created with orb tags matching 1 existing group And the agent container is started on an available port @@ -223,7 +223,7 @@ Scenario: Provision agent with tag matching existing group with multiple policie Given the Orb user has a registered account And the Orb user logs in And 1 Agent Group(s) is created with 2 orb tag(s) - And that a sink already exists + And that a sink with default configuration type already exists And 14 mixed policies are applied to the group When a new agent is created with orb tags matching 1 existing group And the agent container is started on an available port @@ -242,7 +242,7 @@ Scenario: Provision agent with tag matching existing edited group with multiple And the Orb user logs in And 1 Agent Group(s) is created with 3 orb tag(s) And the name, tags, description of last Agent Group is edited using: name=edited_before_policy/ tags=2 orb tag(s)/ description=edited - And that a sink already exists + And that a sink with default configuration type already exists And 14 mixed policies are applied to the group When a new agent is created with orb tags matching 1 existing group And the agent container is started on an available port @@ -260,7 +260,7 @@ Scenario: Provision agent with tag matching existing group with multiple policie Given the Orb user has a registered account And the Orb user logs in And 1 Agent Group(s) is created with 3 orb tag(s) - And that a sink already exists + And that a sink with default configuration type already exists And 20 mixed policies are applied to the group And the name, tags, description of last Agent Group is edited using: name=edited_after_policy/ tags=2 orb tag(s)/ description=edited When a new agent is created with orb tags matching 1 existing group @@ -281,7 +281,7 @@ Scenario: Sink idle after 5 minutes without metrics flow And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds @@ -320,7 +320,7 @@ Scenario: Unapplying policies that failed by editing agent orb tags to unsubscri And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 3 simple policies are applied to the group And that a policy using: handler=dns, description='policy_dns', bpf_filter_expression=ufp pot 53 already exists And 1 new dataset is created using the policy, last group and 1 sink @@ -340,7 +340,7 @@ Scenario: Unapplying policies that failed by editing group tags to unsubscribe a And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 3 simple policies are applied to the group And that a policy using: handler=dns, description='policy_dns', bpf_filter_expression=ufp pot 53 already exists And 1 new dataset is created using the policy, last group and 1 sink @@ -360,7 +360,7 @@ Scenario: Unapplying policies that failed by removing group And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 3 simple policies are applied to the group And that a policy using: handler=dns, description='policy_dns', bpf_filter_expression=ufp pot 53 already exists And 1 new dataset is created using the policy, last group and 1 sink @@ -488,7 +488,7 @@ Scenario: Agent subscription to multiple group with policies after editing orb a And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running And 1 Agent Group(s) is created with 1 orb tag(s) @@ -509,7 +509,7 @@ Scenario: Agent subscription to group with policies after editing orb agent's ta And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running And 1 Agent Group(s) is created with 1 orb tag(s) @@ -531,7 +531,7 @@ Scenario: Remove one of the groups that applies the same policy on the agent And pktvisor state is running And referred agent is subscribed to 2 groups And this agent's heartbeat shows that 2 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And a new policy is created using: handler=dns, description='policy_dns_2_groups' And 2 new dataset is created using the policy, an existing group and 1 sink And this agent's heartbeat shows that 1 policies are applied and 1 has status running @@ -552,7 +552,7 @@ Scenario: Remove one of the datasets that applies the same policy on the agent And pktvisor state is running And referred agent is subscribed to 2 groups And this agent's heartbeat shows that 2 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And a new policy is created using: handler=dns, description='policy_dns_2_groups' And 2 new dataset is created using the policy, an existing group and 1 sink And this agent's heartbeat shows that 1 policies are applied and 1 has status running @@ -569,7 +569,7 @@ Scenario: Insert tags in agents created without tags and apply policies to group And a new agent is created with 0 orb tag(s) And the agent container is started on an available port And the agent status is online - And that a sink already exists + And that a sink with default configuration type already exists When edit the orb tags on agent and use 2 orb tag(s) And 1 Agent Group(s) is created with same tag as the agent and without description And 1 simple policies are applied to the group @@ -588,7 +588,7 @@ Scenario: Edit agent name and apply policies to then And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent And 1 agent must be matching on response field matching_agents of the last group created - And that a sink already exists + And that a sink with default configuration type already exists When edit the agent name And 1 simple policies are applied to the group Then this agent's heartbeat shows that 1 policies are applied and all has status running @@ -602,7 +602,7 @@ Scenario: Editing tags of an Agent Group with policies (unsubscription - provisi And that an agent with 1 orb tag(s) already exists and is online And 1 Agent Group(s) is created with same tag as the agent and without description And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group When the name, tags, description of last Agent Group is edited using: name=new_name/ tags=2 orb tag(s)/ description=None Then 0 agent must be matching on response field matching_agents of the last group created @@ -617,7 +617,7 @@ Scenario: Editing tags of an Agent Group with policies (subscription - provision And that an agent with 2 orb tag(s) already exists and is online And pktvisor state is running And 1 Agent Group(s) is created with 1 orb tag(s) and without description - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group When the name, tags, description of last Agent Group is edited using: name=new_name/ tags=matching the agent/ description=None Then 1 agent must be matching on response field matching_agents of the last group created @@ -631,7 +631,7 @@ Scenario: Editing tags of an Agent Group with policies (provision agent after ed Given the Orb user has a registered account And the Orb user logs in And 1 Agent Group(s) is created with 1 orb tag(s) and without description - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And a new agent is created with orb tags matching 1 existing group And 1 agent must be matching on response field matching_agents of the last group created @@ -647,7 +647,7 @@ Scenario: Editing tags of an Agent Group with policies (subscription - provision Given the Orb user has a registered account And the Orb user logs in And 1 Agent Group(s) is created with 1 orb tag(s) and without description - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group When the name, tags, description of last Agent Group is edited using: name=new_name/ tags=2 orb tag(s)/ description=None And a new agent is created with orb tags matching 1 existing group @@ -667,7 +667,7 @@ Scenario: Editing tags of an Agent and Agent Group with policies (unsubscription And that an agent with 1 orb tag(s) already exists and is online And pktvisor state is running And 1 Agent Group(s) is created with same tag as the agent and without description - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group When the name, tags, description of last Agent Group is edited using: name=new_name/ tags=2 orb tag(s)/ description=None And edit the orb tags on agent and use 1 orb tag(s) @@ -685,7 +685,7 @@ Scenario: Editing tags of an Agent and Agent Group with policies (subscription - And that an agent with 3 orb tag(s) already exists and is online And pktvisor state is running And 1 Agent Group(s) is created with 1 orb tag(s) and without description - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group When edit the orb tags on agent and use 2 orb tag(s) And the name, tags, description of last Agent Group is edited using: name=new_name/ tags=matching the agent/ description=None @@ -701,7 +701,7 @@ Scenario: Editing tags of an Agent and Agent Group with policies (provision agen Given the Orb user has a registered account And the Orb user logs in And 1 Agent Group(s) is created with 1 orb tag(s) and without description - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group When the name, tags, description of last Agent Group is edited using: name=new_name/ tags=2 orb tag(s)/ description=None And a new agent is created with 1 orb tag(s) @@ -717,7 +717,7 @@ Scenario: Editing tags of an Agent and Agent Group with policies (subscription - Given the Orb user has a registered account And the Orb user logs in And 1 Agent Group(s) is created with 1 orb tag(s) and without description - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group When the name, tags, description of last Agent Group is edited using: name=new_name/ tags=1 orb tag/ description=None And a new agent is created with 2 orb tag(s) @@ -736,7 +736,7 @@ Scenario: Editing tags of an Agent and Agent Group with policies (subscription - Scenario: Edit an advanced policy with handler dns changing the handler to net Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And that an agent with 1 orb tag(s) already exists and is online And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -757,7 +757,7 @@ Scenario: Edit an advanced policy with handler dns changing the handler to net Scenario: Edit an advanced policy with handler dns changing the handler to dhcp Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And that an agent with 1 orb tag(s) already exists and is online And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -776,7 +776,7 @@ Scenario: Edit an advanced policy with handler dns changing the handler to dhcp Scenario: Edit a simple policy with handler dhcp changing the handler to net Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And that an agent with 1 orb tag(s) already exists and is online And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -794,7 +794,7 @@ Scenario: Edit a simple policy with handler dhcp changing the handler to net Scenario: Edit a simple policy with handler net changing the handler to dns and inserting advanced parameters Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And that an agent with 1 orb tag(s) already exists and is online And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -876,7 +876,7 @@ Scenario: Remotely restart agents with policies applied And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running When remotely restart the agent @@ -896,7 +896,7 @@ Scenario: Remotely restart agents without policies applied And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists When remotely restart the agent And the container logs that were output after reset the agent contain the message "resetting backend" within 30 seconds And the container logs that were output after reset the agent contain the message "pktvisor process stopped" within 30 seconds @@ -916,7 +916,7 @@ Scenario: Create duplicated policy And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists When 1 simple policies are applied to the group And 1 duplicated policies is applied to the group Then this agent's heartbeat shows that 2 policies are applied and all has status running @@ -934,7 +934,7 @@ Scenario: Remove agent (check dataset) And the agent container is started on an available port And the agent status is online And referred agent is subscribed to 1 group - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group When this agent is removed Then 0 agent must be matching on response field matching_agents of the last group created @@ -952,7 +952,7 @@ Scenario: Edit sink active and use invalid remote host And the Orb user logs in And 1 Agent Group(s) is created with 3 orb tag(s) And the name, tags, description of last Agent Group is edited using: name=edited_before_policy/ tags=2 orb tag(s)/ description=edited - And that a sink already exists + And that a sink with default configuration type already exists And 10 simple policies are applied to the group When a new agent is created with orb tags matching 1 existing group And the agent container is started on an available port @@ -973,7 +973,7 @@ Scenario: Edit sink active and use invalid username And the Orb user logs in And 1 Agent Group(s) is created with 3 orb tag(s) And the name, tags, description of last Agent Group is edited using: name=edited_before_policy/ tags=2 orb tag(s)/ description=edited - And that a sink already exists + And that a sink with default configuration type already exists And 10 simple policies are applied to the group When a new agent is created with orb tags matching 1 existing group And the agent container is started on an available port @@ -994,7 +994,7 @@ Scenario: Edit sink active and use invalid password And the Orb user logs in And 1 Agent Group(s) is created with 3 orb tag(s) And the name, tags, description of last Agent Group is edited using: name=edited_before_policy/ tags=2 orb tag(s)/ description=edited - And that a sink already exists + And that a sink with default configuration type already exists And 10 simple policies are applied to the group When a new agent is created with orb tags matching 1 existing group And the agent container is started on an available port @@ -1061,7 +1061,7 @@ Scenario: Check policies status when agent backend stop running And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 5 mixed policies are applied to the group And this agent's heartbeat shows that 5 policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds @@ -1090,7 +1090,7 @@ Scenario: Check auto reset after pktvisor stop running And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 10 mixed policies are applied to the group Then this agent's heartbeat shows that 10 policies are applied and all has status running When agent backend (pktvisor) stops running @@ -1110,7 +1110,7 @@ Scenario: Check new policies applied after pktvisor stop running And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 10 mixed policies are applied to the group And this agent's heartbeat shows that 10 policies are applied and all has status running And agent backend (pktvisor) stops running @@ -1132,7 +1132,7 @@ Scenario: Partial Update: sink status after updating only sink name And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds @@ -1151,7 +1151,7 @@ Scenario: Partial Update: sink status after updating only sink description And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds @@ -1170,7 +1170,7 @@ Scenario: Partial Update: sink status after updating only sink tags And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds @@ -1207,7 +1207,7 @@ Scenario: Partial Update: sink status after updating only sink name and descript And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds @@ -1244,7 +1244,7 @@ Scenario: Partial Update: sink status after updating only sink name and tags And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds @@ -1263,7 +1263,7 @@ Scenario: Partial Update: sink status after updating only sink description and t And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds @@ -1318,7 +1318,7 @@ Scenario: Partial Update: sink status after updating only sink name, description And pktvisor state is running And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent - And that a sink already exists + And that a sink with default configuration type already exists And 2 simple policies are applied to the group And this agent's heartbeat shows that 2 policies are applied and all has status running And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds @@ -1382,3 +1382,31 @@ Scenario: Partial Update: sink status after updating only sink description, tags When the description, tags and config of this sink is updated Then the description, tags and config updates to the new value and other fields remains the same And referred sink must have active state on response within 360 seconds + +@smoke @sink_yaml +Scenario: Using sink with yaml configuration + Given the Orb user has a registered account + And the Orb user logs in + And that an agent with 1 orb tag(s) already exists and is online + And pktvisor state is running + And referred agent is subscribed to 1 group + And this agent's heartbeat shows that 1 groups are matching the agent + And that a sink with yaml configuration type already exists + When 10 mixed policies are applied to the group + Then this agent's heartbeat shows that 10 policies are applied and all has status running + And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds + And 10 dataset(s) have validity valid and 0 have validity invalid in 30 seconds + +@smoke +Scenario: Using sink with json configuration specified + Given the Orb user has a registered account + And the Orb user logs in + And that an agent with 1 orb tag(s) already exists and is online + And pktvisor state is running + And referred agent is subscribed to 1 group + And this agent's heartbeat shows that 1 groups are matching the agent + And that a sink with json configuration type already exists + When 10 mixed policies are applied to the group + Then this agent's heartbeat shows that 10 policies are applied and all has status running + And the container logs contain the message "policy applied successfully" referred to each policy within 30 seconds + And 10 dataset(s) have validity valid and 0 have validity invalid in 30 seconds \ No newline at end of file diff --git a/python-test/features/integration_config_file.feature b/python-test/features/integration_config_file.feature index 8904c1b98..793868885 100644 --- a/python-test/features/integration_config_file.feature +++ b/python-test/features/integration_config_file.feature @@ -6,7 +6,7 @@ Feature: Integration tests using agent provided via config file Scenario: General smoke test to validate private agent image - using configuration files Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:pcap, settings: {"iface":"default"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -27,7 +27,7 @@ Scenario: General smoke test to validate private agent image - using configurati Scenario: provisioning agent without specify pktvisor binary path and path to config file (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 1 orb tag(s) (lower case) And 3 simple policies flow are applied to the group And a new agent is created with 0 orb tag(s) @@ -46,7 +46,7 @@ Scenario: provisioning agent without specify pktvisor binary path and path to co Scenario: provisioning agent without specify pktvisor binary path (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 1 orb tag(s) (lower case) And 3 simple policies flow are applied to the group And a new agent is created with 0 orb tag(s) @@ -65,7 +65,7 @@ Scenario: provisioning agent without specify pktvisor binary path (config file - Scenario: provisioning agent without specify pktvisor path to config file (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 1 orb tag(s) (lower case) And 3 simple policies flow are applied to the group And a new agent is created with 0 orb tag(s) @@ -83,7 +83,7 @@ Scenario: provisioning agent without specify pktvisor path to config file (confi Scenario: provisioning agent without specify pktvisor binary path and path to config file (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a new agent is created with 2 orb tag(s) When an agent(input_type:flow, settings: {"bind":"0.0.0.0", "port":"available_port"}) is provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: True. Paste only file: True. Use specif backend for pktvisor {"binary":"None", "config_file":"None"}] And pktvisor state is running @@ -104,7 +104,7 @@ Scenario: provisioning agent without specify pktvisor binary path and path to co Scenario: provisioning agent without specify pktvisor binary path (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a new agent is created with 2 orb tag(s) When an agent(input_type:flow, settings: {"bind":"0.0.0.0", "port":"available_port"}) is provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: True. Paste only file: True. Use specif backend for pktvisor {"config_file":"None"}] And pktvisor state is running @@ -125,7 +125,7 @@ Scenario: provisioning agent without specify pktvisor binary path (config file - Scenario: provisioning agent without specify pktvisor path to config file (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a new agent is created with 2 orb tag(s) When an agent(input_type:flow, settings: {"bind":"0.0.0.0", "port":"available_port"}) is provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: True. Paste only file: True. Use specif backend for pktvisor {"binary":"None"}] And pktvisor state is running @@ -147,7 +147,7 @@ Scenario: provisioning agent without specify pktvisor path to config file (confi Scenario: tap_selector - any - matching 0 of all tags from an agent Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:pcap, settings: {"iface":"default"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -163,7 +163,7 @@ Scenario: tap_selector - any - matching 0 of all tags from an agent Scenario: tap_selector - any - matching 1 of all tags from an agent Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:pcap, settings: {"iface":"default"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -181,7 +181,7 @@ Scenario: tap_selector - any - matching 1 of all tags from an agent Scenario: tap_selector - any - matching 1 of all tags (plus 1 random tag) from an agent Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:pcap, settings: {"iface":"default"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -199,7 +199,7 @@ Scenario: tap_selector - any - matching 1 of all tags (plus 1 random tag) from a Scenario: tap_selector - all - matching 0 of all tags from an agent Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:pcap, settings: {"iface":"default"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -215,7 +215,7 @@ Scenario: tap_selector - all - matching 0 of all tags from an agent Scenario: tap_selector - all - matching 1 of all tags from an agent Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:pcap, settings: {"iface":"default"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -234,7 +234,7 @@ Scenario: tap_selector - all - matching 1 of all tags from an agent Scenario: tap_selector - all - matching all tags from an agent Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:pcap, settings: {"iface":"default"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -254,7 +254,7 @@ Scenario: tap_selector - all - matching all tags from an agent Scenario: agent pcap with only agent tags subscription to a group with policies created after provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:pcap, settings: {"iface":"default"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -273,7 +273,7 @@ Scenario: agent pcap with only agent tags subscription to a group with policies Scenario: agent pcap with only agent tags subscription to a group with policies created before provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 1 orb tag(s) (lower case) And 3 simple policies pcap are applied to the group And a new agent is created with 0 orb tag(s) @@ -293,7 +293,7 @@ Scenario: agent pcap with only agent tags subscription to a group with policies Scenario: agent pcap with mixed tags subscription to a group with policies created after provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:pcap, settings: {"iface":"default"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And edit the orb tags on agent and use 2 orb tag(s) @@ -313,7 +313,7 @@ Scenario: agent pcap with mixed tags subscription to a group with policies creat Scenario: agent pcap with mixed tags subscription to a group with policies created before provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 2 orb tag(s) (lower case) And 3 simple policies pcap are applied to the group And a new agent is created with 2 orb tag(s) @@ -332,7 +332,7 @@ Scenario: agent pcap with mixed tags subscription to a group with policies creat Scenario: agent pcap with only agent tags subscription to a group with policies created after provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a new agent is created with 0 orb tag(s) When an agent(input_type:pcap, settings: {"iface":"default"}) is provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -352,7 +352,7 @@ Scenario: agent pcap with only agent tags subscription to a group with policies Scenario: agent pcap with only agent tags subscription to a group with policies created before provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 1 orb tag(s) (lower case) And 3 simple policies pcap are applied to the group And a new agent is created with 0 orb tag(s) @@ -371,7 +371,7 @@ Scenario: agent pcap with only agent tags subscription to a group with policies Scenario: agent pcap with mixed tags subscription to a group with policies created after provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a new agent is created with 2 orb tag(s) When an agent(input_type:pcap, settings: {"iface":"default"}) is provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running @@ -392,7 +392,7 @@ Scenario: agent pcap with mixed tags subscription to a group with policies creat Scenario: agent pcap with mixed tags subscription to a group with policies created before provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 2 orb tag(s) (lower case) And 3 simple policies pcap are applied to the group And a new agent is created with 2 orb tag(s) @@ -414,7 +414,7 @@ Scenario: agent pcap with mixed tags subscription to a group with policies creat Scenario: agent flow with only agent tags subscription to a group with policies created after provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:flow, settings: {"bind":"0.0.0.0", "port":"available_port"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: True. Paste only file: False] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -432,7 +432,7 @@ Scenario: agent flow with only agent tags subscription to a group with policies Scenario: agent flow with only agent tags subscription to a group with policies created before provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 1 orb tag(s) (lower case) And 3 simple policies flow are applied to the group And a new agent is created with 0 orb tag(s) @@ -451,7 +451,7 @@ Scenario: agent flow with only agent tags subscription to a group with policies Scenario: agent flow with mixed tags subscription to a group with policies created after provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:flow, settings: {"bind":"0.0.0.0", "port":"available_port"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running And edit the orb tags on agent and use 2 orb tag(s) @@ -470,7 +470,7 @@ Scenario: agent flow with mixed tags subscription to a group with policies creat Scenario: agent flow with mixed tags subscription to a group with policies created before provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 2 orb tag(s) (lower case) And 3 simple policies flow are applied to the group And a new agent is created with 2 orb tag(s) @@ -488,7 +488,7 @@ Scenario: agent flow with mixed tags subscription to a group with policies creat Scenario: agent flow with only agent tags subscription to a group with policies created after provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a new agent is created with 0 orb tag(s) When an agent(input_type:flow, settings: {"bind":"0.0.0.0", "port":"available_port"}) is provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running @@ -507,7 +507,7 @@ Scenario: agent flow with only agent tags subscription to a group with policies Scenario: agent flow with only agent tags subscription to a group with policies created before provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 1 orb tag(s) (lower case) And 3 simple policies flow are applied to the group And a new agent is created with 0 orb tag(s) @@ -526,7 +526,7 @@ Scenario: agent flow with only agent tags subscription to a group with policies Scenario: agent flow with mixed tags subscription to a group with policies created after provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a new agent is created with 2 orb tag(s) When an agent(input_type:flow, settings: {"bind":"0.0.0.0", "port":"available_port"}) is provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running @@ -546,7 +546,7 @@ Scenario: agent flow with mixed tags subscription to a group with policies creat Scenario: agent flow with mixed tags subscription to a group with policies created before provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 2 orb tag(s) (lower case) And 3 simple policies flow are applied to the group And a new agent is created with 2 orb tag(s) @@ -569,7 +569,7 @@ Scenario: agent flow with mixed tags subscription to a group with policies creat Scenario: agent dnstap with only agent tags subscription to a group with policies created after provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:dnstap, settings: {"tcp":"0.0.0.0:available_port", "only_hosts":"0.0.0.0/32"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -588,7 +588,7 @@ Scenario: agent dnstap with only agent tags subscription to a group with policie Scenario: agent dnstap with only agent tags subscription to a group with policies created before provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 1 orb tag(s) (lower case) And 3 simple policies dnstap are applied to the group And a new agent is created with 0 orb tag(s) @@ -608,7 +608,7 @@ Scenario: agent dnstap with only agent tags subscription to a group with policie Scenario: agent dnstap with mixed tags subscription to a group with policies created after provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:dnstap, settings: {"tcp":"0.0.0.0:available_port", "only_hosts":"0.0.0.0/32"}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running And edit the orb tags on agent and use 2 orb tag(s) @@ -628,7 +628,7 @@ Scenario: agent dnstap with mixed tags subscription to a group with policies cre Scenario: agent dnstap with mixed tags subscription to a group with policies created before provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 2 orb tag(s) (lower case) And 3 simple policies dnstap are applied to the group And a new agent is created with 2 orb tag(s) @@ -647,7 +647,7 @@ Scenario: agent dnstap with mixed tags subscription to a group with policies cre Scenario: agent dnstap with only agent tags subscription to a group with policies created after provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a new agent is created with 0 orb tag(s) When an agent(input_type:dnstap, settings: {"tcp":"0.0.0.0:available_port", "only_hosts":"0.0.0.0/32"}) is provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running @@ -667,7 +667,7 @@ Scenario: agent dnstap with only agent tags subscription to a group with policie Scenario: agent dnstap with only agent tags subscription to a group with policies created before provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 1 orb tag(s) (lower case) And 3 simple policies dnstap are applied to the group And a new agent is created with 0 orb tag(s) @@ -686,7 +686,7 @@ Scenario: agent dnstap with only agent tags subscription to a group with policie Scenario: agent dnstap with mixed tags subscription to a group with policies created after provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a new agent is created with 2 orb tag(s) When an agent(input_type:dnstap, settings: {"tcp":"0.0.0.0:available_port", "only_hosts":"0.0.0.0/32"}) is provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running @@ -707,7 +707,7 @@ Scenario: agent dnstap with mixed tags subscription to a group with policies cre Scenario: agent dnstap with mixed tags subscription to a group with policies created before provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 2 orb tag(s) (lower case) And 3 simple policies dnstap are applied to the group And a new agent is created with 2 orb tag(s) @@ -730,7 +730,7 @@ Scenario: agent dnstap with mixed tags subscription to a group with policies cre Scenario: agent netprobe with only agent tags subscription to a group with policies created after provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:netprobe, settings: {"test_type":"ping", "packets_per_test":3, "interval_msec":3000, "timeout_msec":1500, "packets_interval_msec":50, "packet_payload_size":56, "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -749,7 +749,7 @@ Scenario: agent netprobe with only agent tags subscription to a group with polic Scenario: agent netprobe with only agent tags subscription to a group with policies created before provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 1 orb tag(s) (lower case) And 3 advanced policies netprobe are applied to the group And a new agent is created with 0 orb tag(s) @@ -769,7 +769,7 @@ Scenario: agent netprobe with only agent tags subscription to a group with polic Scenario: agent netprobe with mixed tags subscription to a group with policies created after provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:netprobe, settings: {"test_type":"ping", "packets_per_test":3, "interval_msec":3000, "timeout_msec":1500, "packets_interval_msec":50, "packet_payload_size":56, "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is self-provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running And edit the orb tags on agent and use 2 orb tag(s) @@ -789,7 +789,7 @@ Scenario: agent netprobe with mixed tags subscription to a group with policies c Scenario: agent netprobe with mixed tags subscription to a group with policies created before provision the agent (config file - auto_provision=true) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 2 orb tag(s) (lower case) And 3 simple policies netprobe are applied to the group And a new agent is created with 2 orb tag(s) @@ -809,7 +809,7 @@ Scenario: agent netprobe with mixed tags subscription to a group with policies c Scenario: agent netprobe with only agent tags subscription to a group with policies created after provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a new agent is created with 0 orb tag(s) When an agent(input_type:netprobe, settings: {"test_type":"ping", "packets_per_test":3, "interval_msec":3000, "timeout_msec":1500, "packets_interval_msec":50, "packet_payload_size":56, "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: True. Paste only file: True] And pktvisor state is running @@ -830,7 +830,7 @@ Scenario: agent netprobe with only agent tags subscription to a group with polic Scenario: agent netprobe with only agent tags subscription to a group with policies created before provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 1 orb tag(s) (lower case) And 3 simple policies netprobe are applied to the group And a new agent is created with 0 orb tag(s) @@ -850,7 +850,7 @@ Scenario: agent netprobe with only agent tags subscription to a group with polic Scenario: agent netprobe with mixed tags subscription to a group with policies created after provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a new agent is created with 2 orb tag(s) When an agent(input_type:netprobe, settings: {"test_type":"ping", "packets_per_test":3, "interval_msec":3000, "timeout_msec":1500, "packets_interval_msec":50, "packet_payload_size":56, "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is provisioned via a configuration file on port available with 3 agent tags and has status online. [Overwrite default: False. Paste only file: False] And pktvisor state is running @@ -872,7 +872,7 @@ Scenario: agent netprobe with mixed tags subscription to a group with policies c Scenario: agent netprobe with mixed tags subscription to a group with policies created before provision the agent (config file - auto_provision=false) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And 1 Agent Group(s) is created with 2 orb tag(s) (lower case) And 3 simple policies netprobe are applied to the group And a new agent is created with 2 orb tag(s) diff --git a/python-test/features/integration_ui.feature b/python-test/features/integration_ui.feature index 290af6108..205556ed4 100644 --- a/python-test/features/integration_ui.feature +++ b/python-test/features/integration_ui.feature @@ -8,7 +8,7 @@ Feature: Integration UI tests And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent And a new policy is created using: handler=dns, description='policy_dns' - And that a sink already exists + And that a sink with default configuration type already exists When a dataset is created through the UI Then the policy must have status running on agent view page (Active Policies/Datasets) And policy and dataset are clickable and redirect user to referred pages @@ -44,7 +44,7 @@ Feature: Integration UI tests And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent And a new policy is created using: handler=dns, description='policy_dns' - And that a sink already exists + And that a sink with default configuration type already exists And a dataset is created through the UI And the policy must have status running on agent view page (Active Policies/Datasets) And policy and dataset are clickable and redirect user to referred pages @@ -60,7 +60,7 @@ Feature: Integration UI tests And referred agent is subscribed to 1 group And this agent's heartbeat shows that 1 groups are matching the agent And a new policy is created using: handler=dns, description='policy_dns' - And that a sink already exists + And that a sink with default configuration type already exists And a dataset is created through the UI And a new sink is created When the dataset is edited and one more sink is inserted and name is changed diff --git a/python-test/features/metrics.feature b/python-test/features/metrics.feature index 56497dd38..002c89f0a 100644 --- a/python-test/features/metrics.feature +++ b/python-test/features/metrics.feature @@ -7,7 +7,7 @@ Feature: Integration tests validating metric groups Scenario: netprobe handler with default metric groups configuration Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:netprobe, settings: {"test_type":"ping", "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -25,7 +25,7 @@ Scenario: netprobe handler with default metric groups configuration Scenario: netprobe handler with all metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:netprobe, settings: {"test_type":"ping", "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -43,7 +43,7 @@ Scenario: netprobe handler with all metric groups enabled Scenario: netprobe handler with all metric groups disabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:netprobe, settings: {"test_type":"ping", "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -60,7 +60,7 @@ Scenario: netprobe handler with all metric groups disabled Scenario: netprobe handler with only counters metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:netprobe, settings: {"test_type":"ping", "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -78,7 +78,7 @@ Scenario: netprobe handler with only counters metric groups enabled Scenario: netprobe handler with only quantiles metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:netprobe, settings: {"test_type":"ping", "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -96,7 +96,7 @@ Scenario: netprobe handler with only quantiles metric groups enabled Scenario: netprobe handler with only histograms metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:netprobe, settings: {"test_type":"ping", "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -114,7 +114,7 @@ Scenario: netprobe handler with only histograms metric groups enabled Scenario: netprobe handler with counters and histograms metric groups enabled and quantiles disabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:netprobe, settings: {"test_type":"ping", "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -132,7 +132,7 @@ Scenario: netprobe handler with counters and histograms metric groups enabled an Scenario: netprobe handler with counters and quantiles metric groups enabled and histograms disabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:netprobe, settings: {"test_type":"ping", "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -150,7 +150,7 @@ Scenario: netprobe handler with counters and quantiles metric groups enabled and Scenario: netprobe handler with histograms and quantiles metric groups enabled and counters disabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists When an agent(input_type:netprobe, settings: {"test_type":"ping", "targets": {"www.google.com": {"target": "www.google.com"}, "orb_community": {"target": "orb.community"}}}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running And 1 Agent Group(s) is created with all tags contained in the agent @@ -169,7 +169,7 @@ Scenario: netprobe handler with histograms and quantiles metric groups enabled a Scenario: flow handler type netflow with default metric groups configuration Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -190,7 +190,7 @@ Scenario: flow handler type netflow with default metric groups configuration Scenario: flow handler type netflow with all metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -211,7 +211,7 @@ Scenario: flow handler type netflow with all metric groups enabled Scenario: flow handler type netflow with all metric groups disabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -231,7 +231,7 @@ Scenario: flow handler type netflow with all metric groups disabled Scenario: flow handler type netflow with only cardinality metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -252,7 +252,7 @@ Scenario: flow handler type netflow with only cardinality metric groups enabled Scenario: flow handler type netflow with only counters metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -273,7 +273,7 @@ Scenario: flow handler type netflow with only counters metric groups enabled Scenario: flow handler type netflow with only counters and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -294,7 +294,7 @@ Scenario: flow handler type netflow with only counters and by_bytes metric group Scenario: flow handler type netflow with only counters and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -315,7 +315,7 @@ Scenario: flow handler type netflow with only counters and by_packets metric gro Scenario: flow handler type netflow with only by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -335,7 +335,7 @@ Scenario: flow handler type netflow with only by_packets metric groups enabled Scenario: flow handler type netflow with only by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -355,7 +355,7 @@ Scenario: flow handler type netflow with only by_bytes metric groups enabled Scenario: flow handler type netflow with only top_geo metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -375,7 +375,7 @@ Scenario: flow handler type netflow with only top_geo metric groups enabled Scenario: flow handler type netflow with only top_geo and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -395,7 +395,7 @@ Scenario: flow handler type netflow with only top_geo and by_bytes metric groups Scenario: flow handler type netflow with only top_geo and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -416,7 +416,7 @@ Scenario: flow handler type netflow with only top_geo and by_packets metric grou Scenario: flow handler type netflow with only conversations metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -436,7 +436,7 @@ Scenario: flow handler type netflow with only conversations metric groups enable Scenario: flow handler type netflow with only conversations and cardinality metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -456,7 +456,7 @@ Scenario: flow handler type netflow with only conversations and cardinality metr Scenario: flow handler type netflow with only conversations and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -477,7 +477,7 @@ Scenario: flow handler type netflow with only conversations and by_bytes metric Scenario: flow handler type netflow with only conversations and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -498,7 +498,7 @@ Scenario: flow handler type netflow with only conversations and by_packets metri Scenario: flow handler type netflow with only top_ports metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -518,7 +518,7 @@ Scenario: flow handler type netflow with only top_ports metric groups enabled Scenario: flow handler type netflow with only top_ports and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -539,7 +539,7 @@ Scenario: flow handler type netflow with only top_ports and by_packets metric gr Scenario: flow handler type netflow with only top_ports and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -560,7 +560,7 @@ Scenario: flow handler type netflow with only top_ports and by_bytes metric grou Scenario: flow handler type netflow with only top_ips_ports metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -580,7 +580,7 @@ Scenario: flow handler type netflow with only top_ips_ports metric groups enable Scenario: flow handler type netflow with only top_ips_ports and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -601,7 +601,7 @@ Scenario: flow handler type netflow with only top_ips_ports and by_bytes metric Scenario: flow handler type netflow with only top_ips_ports and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -622,7 +622,7 @@ Scenario: flow handler type netflow with only top_ips_ports and by_packets metri Scenario: flow handler type netflow with only top_interfaces metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -642,7 +642,7 @@ Scenario: flow handler type netflow with only top_interfaces metric groups enabl Scenario: flow handler type netflow with only top_interfaces and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -663,7 +663,7 @@ Scenario: flow handler type netflow with only top_interfaces and by_bytes metric Scenario: flow handler type netflow with only top_interfaces and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -683,7 +683,7 @@ Scenario: flow handler type netflow with only top_interfaces and by_packets metr Scenario: flow handler type netflow with only top_ips metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -703,7 +703,7 @@ Scenario: flow handler type netflow with only top_ips metric groups enabled Scenario: flow handler type netflow with only top_ips and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -724,7 +724,7 @@ Scenario: flow handler type netflow with only top_ips and by_packets metric grou Scenario: flow handler type netflow with only top_ips and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -745,7 +745,7 @@ Scenario: flow handler type netflow with only top_ips and by_bytes metric groups Scenario: flow handler type netflow with only top_tos metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -765,7 +765,7 @@ Scenario: flow handler type netflow with only top_tos metric groups enabled Scenario: flow handler type netflow with only top_tos and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -786,7 +786,7 @@ Scenario: flow handler type netflow with only top_tos and by_bytes metric groups Scenario: flow handler type netflow with only top_tos and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type netflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"netflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -809,7 +809,7 @@ Scenario: flow handler type netflow with only top_tos and by_packets metric grou Scenario: flow handler type sflow with default metric groups configuration Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -830,7 +830,7 @@ Scenario: flow handler type sflow with default metric groups configuration Scenario: flow handler type sflow with all metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -851,7 +851,7 @@ Scenario: flow handler type sflow with all metric groups enabled Scenario: flow handler type sflow with all metric groups disabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -871,7 +871,7 @@ Scenario: flow handler type sflow with all metric groups disabled Scenario: flow handler type sflow with only cardinality metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -892,7 +892,7 @@ Scenario: flow handler type sflow with only cardinality metric groups enabled Scenario: flow handler type sflow with only counters metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -913,7 +913,7 @@ Scenario: flow handler type sflow with only counters metric groups enabled Scenario: flow handler type sflow with only counters and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -934,7 +934,7 @@ Scenario: flow handler type sflow with only counters and by_bytes metric groups Scenario: flow handler type sflow with only counters and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -955,7 +955,7 @@ Scenario: flow handler type sflow with only counters and by_packets metric group Scenario: flow handler type sflow with only by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -975,7 +975,7 @@ Scenario: flow handler type sflow with only by_packets metric groups enabled Scenario: flow handler type sflow with only by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -995,7 +995,7 @@ Scenario: flow handler type sflow with only by_bytes metric groups enabled Scenario: flow handler type sflow with only top_geo metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1014,7 +1014,7 @@ Scenario: flow handler type sflow with only top_geo metric groups enabled Scenario: flow handler type sflow with only top_geo and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1035,7 +1035,7 @@ Scenario: flow handler type sflow with only top_geo and by_bytes metric groups e Scenario: flow handler type sflow with only top_geo and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1056,7 +1056,7 @@ Scenario: flow handler type sflow with only top_geo and by_packets metric groups Scenario: flow handler type sflow with only conversations metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1076,7 +1076,7 @@ Scenario: flow handler type sflow with only conversations metric groups enabled Scenario: flow handler type sflow with only conversations and cardinality metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1096,7 +1096,7 @@ Scenario: flow handler type sflow with only conversations and cardinality metric Scenario: flow handler type sflow with only conversations and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1117,7 +1117,7 @@ Scenario: flow handler type sflow with only conversations and by_bytes metric gr Scenario: flow handler type sflow with only conversations and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1138,7 +1138,7 @@ Scenario: flow handler type sflow with only conversations and by_packets metric Scenario: flow handler type sflow with only top_ports metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1157,7 +1157,7 @@ Scenario: flow handler type sflow with only top_ports metric groups enabled Scenario: flow handler type sflow with only top_ports and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1178,7 +1178,7 @@ Scenario: flow handler type sflow with only top_ports and by_bytes metric groups Scenario: flow handler type sflow with only top_ports and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1199,7 +1199,7 @@ Scenario: flow handler type sflow with only top_ports and by_packets metric grou Scenario: flow handler type sflow with only top_ips_ports metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1219,7 +1219,7 @@ Scenario: flow handler type sflow with only top_ips_ports metric groups enabled Scenario: flow handler type sflow with only top_ips_ports and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1240,7 +1240,7 @@ Scenario: flow handler type sflow with only top_ips_ports and by_bytes metric gr Scenario: flow handler type sflow with only top_ips_ports and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1260,7 +1260,7 @@ Scenario: flow handler type sflow with only top_ips_ports and by_packets metric Scenario: flow handler type sflow with only top_interfaces metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1279,7 +1279,7 @@ Scenario: flow handler type sflow with only top_interfaces metric groups enabled Scenario: flow handler type sflow with only top_interfaces and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1300,7 +1300,7 @@ Scenario: flow handler type sflow with only top_interfaces and by_bytes metric g Scenario: flow handler type sflow with only top_interfaces and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1321,7 +1321,7 @@ Scenario: flow handler type sflow with only top_interfaces and by_packets metric Scenario: flow handler type sflow with only top_ips metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1340,7 +1340,7 @@ Scenario: flow handler type sflow with only top_ips metric groups enabled Scenario: flow handler type sflow with only top_ips and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1361,7 +1361,7 @@ Scenario: flow handler type sflow with only top_ips and by_bytes metric groups e Scenario: flow handler type sflow with only top_ips and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1382,7 +1382,7 @@ Scenario: flow handler type sflow with only top_ips and by_packets metric groups Scenario: flow handler type sflow with only top_tos metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1402,7 +1402,7 @@ Scenario: flow handler type sflow with only top_tos metric groups enabled Scenario: flow handler type sflow with only top_tos and by_bytes metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1423,7 +1423,7 @@ Scenario: flow handler type sflow with only top_tos and by_bytes metric groups e Scenario: flow handler type sflow with only top_tos and by_packets metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: 192.168.100.2/24 And a virtual switch is configured and is up with type sflow and target \"192.168.100.2:available\" When an agent(input_type:flow, settings: {"bind":"192.168.100.2", "port":"switch", "flow_type":"sflow"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] @@ -1446,7 +1446,7 @@ Scenario: flow handler type sflow with only top_tos and by_packets metric groups Scenario: pcap handler with default metric groups configuration Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1466,7 +1466,7 @@ Scenario: pcap handler with default metric groups configuration Scenario: pcap handler with all metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1486,7 +1486,7 @@ Scenario: pcap handler with all metric groups enabled Scenario: pcap handler with all metric groups disabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1508,7 +1508,7 @@ Scenario: pcap handler with all metric groups disabled Scenario: bgp handler with default metric groups configuration Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1528,7 +1528,7 @@ Scenario: bgp handler with default metric groups configuration Scenario: bgp handler with all metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1548,7 +1548,7 @@ Scenario: bgp handler with all metric groups enabled Scenario: bgp handler with all metric groups disabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1570,7 +1570,7 @@ Scenario: bgp handler with all metric groups disabled Scenario: dhcp handler with default metric groups configuration Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1590,7 +1590,7 @@ Scenario: dhcp handler with default metric groups configuration Scenario: dhcp handler with all metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1610,7 +1610,7 @@ Scenario: dhcp handler with all metric groups enabled Scenario: dhcp handler with all metric groups disabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1632,7 +1632,7 @@ Scenario: dhcp handler with all metric groups disabled Scenario: net handler with default metric groups configuration Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1652,7 +1652,7 @@ Scenario: net handler with default metric groups configuration Scenario: net handler with all metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1672,7 +1672,7 @@ Scenario: net handler with all metric groups enabled Scenario: net handler with all metric groups disabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1691,7 +1691,7 @@ Scenario: net handler with all metric groups disabled Scenario: net handler with only cardinality metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1711,7 +1711,7 @@ Scenario: net handler with only cardinality metric groups enabled Scenario: net handler with only counters metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1731,7 +1731,7 @@ Scenario: net handler with only counters metric groups enabled Scenario: net handler with only top_geo metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1751,7 +1751,7 @@ Scenario: net handler with only top_geo metric groups enabled Scenario: net handler with only top_ips metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1773,7 +1773,7 @@ Scenario: net handler with only top_ips metric groups enabled Scenario: dns handler with default metric groups configuration Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1793,7 +1793,7 @@ Scenario: dns handler with default metric groups configuration Scenario: dns handler with all metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1813,7 +1813,7 @@ Scenario: dns handler with all metric groups enabled Scenario: dns handler with all metric groups disabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1832,7 +1832,7 @@ Scenario: dns handler with all metric groups disabled Scenario: dns handler with only top_ecs metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1852,7 +1852,7 @@ Scenario: dns handler with only top_ecs metric groups enabled Scenario: dns handler with only top_qnames_details metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1872,7 +1872,7 @@ Scenario: dns handler with only top_qnames_details metric groups enabled Scenario: dns handler with only cardinality metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1892,7 +1892,7 @@ Scenario: dns handler with only cardinality metric groups enabled Scenario: dns handler with only counters metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1912,7 +1912,7 @@ Scenario: dns handler with only counters metric groups enabled Scenario: dns handler with only dns_transaction metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1932,7 +1932,7 @@ Scenario: dns handler with only dns_transaction metric groups enabled Scenario: dns handler with only top_qnames metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1952,7 +1952,7 @@ Scenario: dns handler with only top_qnames metric groups enabled Scenario: dns handler with only top_ports metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1972,7 +1972,7 @@ Scenario: dns handler with only top_ports metric groups enabled Scenario: dns handler with only histograms metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -1991,7 +1991,7 @@ Scenario: dns handler with only histograms metric groups enabled Scenario: dns handler with only histograms and dns_transaction metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2011,7 +2011,7 @@ Scenario: dns handler with only histograms and dns_transaction metric groups ena Scenario: dns handler with only quantiles metric groups enabled Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked", "host_spec":"fe80::a00:27ff:fed4:10bb/48,192.168.0.0/24,75.75.75.75/32"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2032,7 +2032,7 @@ Scenario: dns handler with only quantiles metric groups enabled Scenario: dns handler with default metric groups configuration (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2052,7 +2052,7 @@ Scenario: dns handler with default metric groups configuration (v2) Scenario: dns handler with all metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2072,7 +2072,7 @@ Scenario: dns handler with all metric groups enabled (v2) Scenario: dns handler with all metric groups disabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2091,7 +2091,7 @@ Scenario: dns handler with all metric groups disabled (v2) Scenario: dns handler with only top_ecs metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2111,7 +2111,7 @@ Scenario: dns handler with only top_ecs metric groups enabled (v2) Scenario: dns handler with only top_ports metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2131,7 +2131,7 @@ Scenario: dns handler with only top_ports metric groups enabled (v2) Scenario: dns handler with only top_size metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2151,7 +2151,7 @@ Scenario: dns handler with only top_size metric groups enabled (v2) Scenario: dns handler with only xact_times metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2171,7 +2171,7 @@ Scenario: dns handler with only xact_times metric groups enabled (v2) Scenario: dns handler with only cardinality metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2191,7 +2191,7 @@ Scenario: dns handler with only cardinality metric groups enabled (v2) Scenario: dns handler with only counters metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2211,7 +2211,7 @@ Scenario: dns handler with only counters metric groups enabled (v2) Scenario: dns handler with only top_qnames metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2231,7 +2231,7 @@ Scenario: dns handler with only top_qnames metric groups enabled (v2) Scenario: dns handler with only quantiles metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2251,7 +2251,7 @@ Scenario: dns handler with only quantiles metric groups enabled (v2) Scenario: dns handler with only top_qtypes metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2271,7 +2271,7 @@ Scenario: dns handler with only top_qtypes metric groups enabled (v2) Scenario: dns handler with only top_rcodes metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2293,7 +2293,7 @@ Scenario: dns handler with only top_rcodes metric groups enabled (v2) Scenario: net handler with default metric groups configuration (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2313,7 +2313,7 @@ Scenario: net handler with default metric groups configuration (v2) Scenario: net handler with all metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2333,7 +2333,7 @@ Scenario: net handler with all metric groups enabled (v2) Scenario: net handler with all metric groups disabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2352,7 +2352,7 @@ Scenario: net handler with all metric groups disabled (v2) Scenario: net handler with only cardinality metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2372,7 +2372,7 @@ Scenario: net handler with only cardinality metric groups enabled (v2) Scenario: net handler with only counters metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2392,7 +2392,7 @@ Scenario: net handler with only counters metric groups enabled (v2) Scenario: net handler with only top_geo metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2412,7 +2412,7 @@ Scenario: net handler with only top_geo metric groups enabled (v2) Scenario: net handler with only top_ips metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running @@ -2432,7 +2432,7 @@ Scenario: net handler with only top_ips metric groups enabled (v2) Scenario: net handler with only quantiles metric groups enabled (v2) Given the Orb user has a registered account And the Orb user logs in - And that a sink already exists + And that a sink with default configuration type already exists And a mocked interface is configured with mtu: 65000 and ip: None When an agent(input_type:pcap, settings: {"iface":"mocked"}) is self-provisioned via a configuration file on port available with 1 agent tags and has status online. [Overwrite default: False. Paste only file: True] And pktvisor state is running diff --git a/python-test/features/sinks.feature b/python-test/features/sinks.feature index c32adbee4..fed3678f8 100644 --- a/python-test/features/sinks.feature +++ b/python-test/features/sinks.feature @@ -1,15 +1,31 @@ @sinks @AUTORETRY Feature: sink creation - @smoke - Scenario: Create Sink using Prometheus - Given that the user has the prometheus/grafana credentials - And the Orb user has a registered account - And the Orb user logs in - When a new sink is created - Then referred sink must have unknown state on response within 30 seconds +@smoke +Scenario: Create Sink using Prometheus + Given that the user has the prometheus/grafana credentials + And the Orb user has a registered account + And the Orb user logs in + When a new sink is created + Then referred sink must have unknown state on response within 30 seconds +@smoke +Scenario: Create Sink using Prometheus and yaml configuration type + Given that the user has the prometheus/grafana credentials + And the Orb user has a registered account + And the Orb user logs in + When a new sink is created using yaml configuration type + Then referred sink must have unknown state on response within 30 seconds + +@smoke +Scenario: Create Sink using Prometheus and json configuration type specified + Given that the user has the prometheus/grafana credentials + And the Orb user has a registered account + And the Orb user logs in + When a new sink is created using json configuration type + Then referred sink must have unknown state on response within 30 seconds + @smoke @sanity Scenario: Create sink with name conflict Given that the user has the prometheus/grafana credentials diff --git a/python-test/features/steps/control_dataset_ui.py b/python-test/features/steps/control_dataset_ui.py index d424b7018..eaa72e373 100644 --- a/python-test/features/steps/control_dataset_ui.py +++ b/python-test/features/steps/control_dataset_ui.py @@ -60,8 +60,7 @@ def create_dataset_through_ui(context): break assert_that(context.dataset, is_not(None), "Unable to find dataset on orb backend") amount_of_datasets_after = wait_until_expected_amount_of_datasets_in_policy_view_page(context.driver, - amount_of_datasets_before + 1, - wait_time=1) + amount_of_datasets_before + 1) assert_that(amount_of_datasets_after, equal_to(amount_of_datasets_before + 1), f"Incorrect number of datasets.") diff --git a/python-test/features/steps/control_plane_agents.py b/python-test/features/steps/control_plane_agents.py index 37e44064f..4f7d4c1ec 100644 --- a/python-test/features/steps/control_plane_agents.py +++ b/python-test/features/steps/control_plane_agents.py @@ -752,7 +752,7 @@ def create_agent(token, name, tags, expected_status_code=201): :param expected_status_code: status code to be returned on response :returns: (dict) a dictionary containing the created agent data """ - response, response_json = wait_until_agent_being_created(token, name, tags, expected_status_code, wait_time=1) + response, response_json = wait_until_agent_being_created(token, name, tags, expected_status_code) assert_that(response.status_code, equal_to(expected_status_code), 'Request to create agent failed with status=' + str(response.status_code) + ":" + str(response_json)) diff --git a/python-test/features/steps/control_plane_sink.py b/python-test/features/steps/control_plane_sink.py index 932e5e30b..a3264e25f 100644 --- a/python-test/features/steps/control_plane_sink.py +++ b/python-test/features/steps/control_plane_sink.py @@ -1,14 +1,17 @@ from behave import given, when, then, step from test_config import TestConfig -from utils import random_string, filter_list_by_parameter_start_with, threading_wait_until, validate_json +from utils import random_string, filter_list_by_parameter_start_with, threading_wait_until, validate_json, \ + remove_empty_from_json from hamcrest import * import requests import threading +import yaml configs = TestConfig.configs() sink_name_prefix = "test_sink_label_name_" orb_url = configs.get('orb_url') verify_ssl_bool = eval(configs.get('verify_ssl').title()) +backend_type = configs.get('backend_type') @given("that the user has the prometheus/grafana credentials") @@ -44,18 +47,34 @@ def create_sink(context, **kwargs): include_otel_env_var = configs.get("include_otel_env_var") enable_otel = configs.get("enable_otel") otel_map = {"true": "enabled", "false": "disabled"} + if "configuration_type" in kwargs: + configuration_type = kwargs["configuration_type"].lower() + else: + configuration_type = "" if include_otel_env_var == "true": - context.sink = create_new_sink(token, sink_label_name, endpoint, username, password, - include_otel=include_otel_env_var, otel=otel_map[enable_otel]) + context.sink = create_new_sink(token, sink_label_name, endpoint, username, password, backend_type=backend_type, + include_otel=include_otel_env_var, otel=otel_map[enable_otel], + configuration_type=configuration_type) else: - context.sink = create_new_sink(token, sink_label_name, endpoint, username, password) + context.sink = create_new_sink(token, sink_label_name, endpoint, username, password, backend_type=backend_type, + configuration_type=configuration_type) local_orb_path = configs.get("local_orb_path") - sink_schema_path = local_orb_path + "/python-test/features/steps/schemas/sink_schema.json" + schema_map = {"prometheus": "/sink_schema.json", "otlphttp": "/sink_otlphttp_schema.json"} + sink_schema_path = f"{local_orb_path}/python-test/features/steps/schemas{schema_map[backend_type]}" is_schema_valid = validate_json(context.sink, sink_schema_path) assert_that(is_schema_valid, equal_to(True), f"Invalid sink json. \n Sink = {context.sink}") + sink_get_response = get_sink(context.token, context.sink['id']) + is_get_schema_valid = validate_json(sink_get_response, sink_schema_path) + assert_that(is_get_schema_valid, equal_to(True), f"Invalid sink json in get response." + f" \n Sink = {sink_get_response}") context.existent_sinks_id.append(context.sink['id']) +@step("a new sink is created using {configuration_type} configuration type") +def create_yaml_sink(context, configuration_type): + create_sink(context, configuration_type=configuration_type) + + @step("a new sink is is requested to be created with the same name as an existent one") def create_sink_with_conflict_name(context): token = context.token @@ -67,17 +86,18 @@ def create_sink_with_conflict_name(context): otel_map = {"true": "enabled", "false": "disabled"} if include_otel_env_var == "true": context.error_message = create_new_sink(token, context.sink['name'], endpoint, username, password, - include_otel=include_otel_env_var, otel=otel_map[enable_otel], - expected_status_code=409) + backend_type=backend_type, include_otel=include_otel_env_var, + otel=otel_map[enable_otel], expected_status_code=409) else: context.error_message = create_new_sink(token, context.sink['name'], endpoint, username, password, - expected_status_code=409) + backend_type=backend_type, expected_status_code=409) @step("the name of last Sink is edited using an already existent one") def edit_sink_name_with_conflict(context): first_sink = get_sink(context.token, context.existent_sinks_id[0]) context.sink['name'] = first_sink['name'] + context.sink.pop("config") context.error_message = edit_sink(context.token, context.sink['id'], context.sink, expected_status_code=409) @@ -88,10 +108,15 @@ def create_multiple_sinks(context, amount_of_sinks): create_sink(context) -@given("that a sink already exists") -def new_sink(context): +@given("that a sink with {configuration_type} configuration type already exists") +def new_sink(context, configuration_type): check_prometheus_grafana_credentials(context) - create_sink(context) + if configuration_type.lower() == "default": + create_sink(context) + else: + assert_that(configuration_type.lower(), any_of(equal_to("yaml"), equal_to("json")), + f"Unexpected value for sink configuration type") + create_sink(context, configuration_type=configuration_type.lower()) # this step is only necessary for OTEL migration tests, so we can exclude it after the migration @@ -139,10 +164,12 @@ def create_invalid_sink(context, credential): if include_otel_env_var == "true": context.sink = create_new_sink(token, sink_label_name, prometheus_credentials['endpoint'], prometheus_credentials['username'], prometheus_credentials['password'], - include_otel=include_otel_env_var, otel=otel_map[enable_otel]) + backend_type=backend_type, include_otel=include_otel_env_var, + otel=otel_map[enable_otel]) else: context.sink = create_new_sink(token, sink_label_name, prometheus_credentials['endpoint'], - prometheus_credentials['username'], prometheus_credentials['password']) + prometheus_credentials['username'], prometheus_credentials['password'], + backend_type=backend_type) local_orb_path = configs.get("local_orb_path") sink_schema_path = local_orb_path + "/python-test/features/steps/schemas/sink_schema.json" is_schema_valid = validate_json(context.sink, sink_schema_path) @@ -174,7 +201,7 @@ def check_sink_status(context, status, time_to_wait): def edit_sink_field(context, otel): assert_that(otel, any_of("enabled", "disabled")) sink = get_sink(context.token, context.sink['id']) - sink['config']['password'] = configs.get('prometheus_key') + sink['config']['authentication']['password'] = configs.get('prometheus_key') sink['config']["opentelemetry"] = otel context.sink = edit_sink(context.token, context.sink['id'], sink) @@ -201,26 +228,29 @@ def check_all_sinks_status(context): @step("the sink {field_to_edit} is edited and an {type_of_field} one is used") def edit_sink_field(context, field_to_edit, type_of_field): - assert_that(field_to_edit, any_of("remote host", "username", "password", "name", "description", "tags"), + assert_that(field_to_edit, any_of("remote host", "endpoint", "username", "password", "name", "description", "tags"), "Unexpected field to be edited.") assert_that(type_of_field, any_of("valid", "invalid"), "Invalid type of sink field") sink = get_sink(context.token, context.sink['id']) - sink['config']['password'] = configs.get('prometheus_key') - if field_to_edit == "remote host": + sink['config']['authentication']['password'] = configs.get('prometheus_key') + if field_to_edit == "remote host" or field_to_edit == "endpoint": + exporter_key = {"prometheus": "remote_host", "otlphttp": "endpoint"} + sink_backend = context.sink.get("backend") + assert_that(sink_backend, any_of("prometheus", "otlphttp"), f"Invalid sink backend: '{sink_backend}'") if type_of_field == "invalid": - sink['config']['remote_host'] = context.remote_prometheus_endpoint[:-2] + sink['config']['exporter'][exporter_key[sink_backend]] = context.remote_prometheus_endpoint[:-2] else: - sink['config']['remote_host'] = configs.get('remote_prometheus_endpoint') + sink['config']['exporter'][exporter_key[sink_backend]] = configs.get('remote_prometheus_endpoint') if field_to_edit == "password": if type_of_field == "invalid": - sink['config']['password'] = context.prometheus_key[:-2] + sink['config']['authentication']['password'] = context.prometheus_key[:-2] else: - sink['config']['password'] = configs.get('prometheus_key') + sink['config']['authentication']['password'] = configs.get('prometheus_key') if field_to_edit == "username": if type_of_field == "invalid": - sink['config']['username'] = context.prometheus_username[:-2] + sink['config']['authentication']['username'] = context.prometheus_username[:-2] else: - sink['config']['username'] = configs.get('prometheus_username') + sink['config']['authentication']['username'] = configs.get('prometheus_username') context.sink = edit_sink(context.token, context.sink['id'], sink) @@ -230,8 +260,8 @@ def sink_partial_update(context, sink_keys): sink_keys = sink_keys.replace(" and ", ", ") keys_to_update = sink_keys.split(", ") update_sink_body = get_sink(context.token, context.sink['id']) - keys_to_not_update = list(set(update_sink_body.keys()).symmetric_difference(set(keys_to_update))) - all_sink_keys = list(update_sink_body.keys()) + keys_to_not_update = list(set(update_sink_body.keys()).difference(set(keys_to_update))) + all_sink_keys = list(set(update_sink_body.keys()).union(set(keys_to_update))) for sink_key in all_sink_keys: if sink_key in keys_to_not_update: del update_sink_body[sink_key] @@ -247,15 +277,19 @@ def sink_partial_update(context, sink_keys): "partial update") assert_that(include_otel_env_var, any_of("true", "false"), "Unexpected value for 'include_otel_env_var' on " "sinks partial update") + exporter_key = {"prometheus": "remote_host", "otlphttp": "endpoint"} + sink_backend = context.sink.get("backend") + assert_that(sink_backend, any_of("prometheus", "otlphttp"), f"Invalid sink backend: '{sink_backend}'") if include_otel_env_var == "true": - sink_configs = {"remote_host": remote_host, "username": username, "password": password, + sink_configs = {"authentication": {"type": "basicauth", "password": password, "username": username}, + "exporter": {exporter_key[sink_backend]: remote_host}, "opentelemetry": otel_map[enable_otel]} else: - sink_configs = {"remote_host": remote_host, "username": username, "password": password} + sink_configs = {"authentication": {"type": "basicauth", "password": password, "username": username}, + "exporter": {exporter_key[sink_backend]: remote_host}} context.values_to_use_to_update_sink = {"name": f"{context.sink['name']}_updated", "description": "this sink has been updated", - "tags": {"sink": "updated", "new": "tag"}, - "config": sink_configs} + "tags": {"sink": "updated", "new": "tag"}, "config": sink_configs} update_sink_body[sink_key] = context.values_to_use_to_update_sink[sink_key] context.sink_before_update = get_sink(context.token, context.sink['id']) context.sink = edit_sink(context.token, context.sink['id'], update_sink_body, 200) @@ -266,7 +300,7 @@ def verify_sink_after_update(context, sink_keys): sink_keys = sink_keys.replace(" and ", ", ") updated_keys = sink_keys.split(", ") all_sink_keys = list(context.sink.keys()) - assert_that(set(context.sink.keys()), equal_to(set(context.sink_before_update)), + assert_that(set(context.sink.keys()), equal_to(set(context.sink_before_update).union(set(updated_keys))), f"Sink keys are not the same after sink partial update:" f"Sink before update: {context.sink_before_update}. Sink after update: {context.sink}") for sink_key in all_sink_keys: @@ -295,9 +329,9 @@ def clean_sinks(context): delete_sinks(token, sinks_filtered_list) -def create_new_sink(token, name_label, remote_host, username, password, description=None, tag_key='', - tag_value=None, backend_type="prometheus", include_otel="false", otel="disabled", - expected_status_code=201): +def create_new_sink(token, name_label, remote_host, username, password, backend_type=None, + description=None, tag_key=None, tag_value=None, include_otel="false", otel="disabled", + configuration_type="", expected_status_code=201): """ Creates a new sink in Orb control plane @@ -307,26 +341,37 @@ def create_new_sink(token, name_label, remote_host, username, password, descript :param (str) remote_host: base url to send metrics to a dashboard :param (str) username: user that enables access to the dashboard :param (str) password: key that enables access to the dashboard + :param (str) backend_type: type of backend used to send metrics. :param (str) description: description of sink :param (str) tag_key: the key of the tag to be added to this sink. Default: '' :param (str) tag_value: the value of the tag to be added to this sink. Default: None - :param (str) backend_type: type of backend used to send metrics. Default: prometheus :param (str) include_otel: if true it include 'opentelemetry' on sink's configs :param (str) otel: the value to be used on 'opentelemetry' config + :param (str) configuration_type: define in which type the configuration will be written :param (int) expected_status_code: status code expected as response :return: (dict) a dictionary containing the created sink data """ assert_that(include_otel, any_of("true", "false"), "Unexpected value for 'include_otel' on sinks creation") + assert_that(backend_type, any_of("prometheus", equal_to("otlphttp")), f"Unexpected type of backend.") + exporter_key = {"prometheus": "remote_host", "otlphttp": "endpoint"} if include_otel == "true": - sink_configs = {"remote_host": remote_host, "username": username, "password": password, "opentelemetry": otel} + sink_configs = {"authentication": {"type": "basicauth", "password": password, "username": username}, + "exporter": {exporter_key[backend_type]: remote_host}, "opentelemetry": otel} else: - sink_configs = {"remote_host": remote_host, "username": username, "password": password} - json_request = {"name": name_label, "description": description, "tags": {tag_key: tag_value}, - "backend": backend_type, "validate_only": False, - "config": sink_configs} - headers_request = {'Content-type': 'application/json', 'Accept': '*/*', - 'Authorization': f'Bearer {token}'} - + sink_configs = {"authentication": {"type": "basicauth", "password": password, "username": username}, + "exporter": {exporter_key[backend_type]: remote_host}} + if configuration_type == "yaml": + sink_configs_yaml = yaml.dump(sink_configs) + json_request = {"name": name_label, "description": description, "tags": {tag_key: tag_value}, + "backend": backend_type, "config_data": sink_configs_yaml, "format": configuration_type} + elif configuration_type == "json": + json_request = {"name": name_label, "description": description, "tags": {tag_key: tag_value}, + "backend": backend_type, "config": sink_configs, "format": configuration_type} + else: + json_request = {"name": name_label, "description": description, "tags": {tag_key: tag_value}, + "backend": backend_type, "config": sink_configs} + headers_request = {'Content-type': 'application/json', 'Accept': '*/*', 'Authorization': f'Bearer {token}'} + json_request = remove_empty_from_json(json_request) response = requests.post(orb_url + '/api/v1/sinks', json=json_request, headers=headers_request, verify=verify_ssl_bool) try: @@ -335,7 +380,6 @@ def create_new_sink(token, name_label, remote_host, username, password, descript response_json = response.text assert_that(response.status_code, equal_to(expected_status_code), 'Request to create sink failed with status=' + str(response.status_code) + ': ' + str(response_json)) - return response_json @@ -357,8 +401,8 @@ def get_sink(token, sink_id): response_json = get_sink_response.text assert_that(get_sink_response.status_code, equal_to(200), - 'Request to get sink id=' + sink_id + ' failed with status=' + str(get_sink_response.status_code) + ': ' - + str(response_json)) + 'Request to get sink id=' + sink_id + ' failed with status=' + str( + get_sink_response.status_code) + ': ' + str(response_json)) return response_json @@ -396,15 +440,14 @@ def list_up_to_limit_sinks(token, limit=100, offset=0): response = requests.get(orb_url + '/api/v1/sinks', headers={'Authorization': f'Bearer {token}'}, params={'limit': limit, 'offset': offset}, verify=verify_ssl_bool) - + try: response_json = response.json() except ValueError: response_json = response.text assert_that(response.status_code, equal_to(200), - 'Request to list sinks failed with status=' + str(response.status_code) + ': ' - + str(response_json)) + 'Request to list sinks failed with status=' + str(response.status_code) + ': ' + str(response_json)) sinks_as_json = response.json() return sinks_as_json['sinks'], sinks_as_json['total'], sinks_as_json['offset'] @@ -430,11 +473,11 @@ def delete_sink(token, sink_id): :param (str) sink_id: that identifies the sink to be deleted """ - response = requests.delete(orb_url + '/api/v1/sinks/' + sink_id, - headers={'Authorization': f'Bearer {token}'}, verify=verify_ssl_bool) + response = requests.delete(orb_url + '/api/v1/sinks/' + sink_id, headers={'Authorization': f'Bearer {token}'}, + verify=verify_ssl_bool) - assert_that(response.status_code, equal_to(204), 'Request to delete sink id=' - + sink_id + ' failed with status=' + str(response.status_code)) + assert_that(response.status_code, equal_to(204), + 'Request to delete sink id=' + sink_id + ' failed with status=' + str(response.status_code)) @threading_wait_until @@ -466,8 +509,8 @@ def edit_sink(token, sink_id, sink_body, expected_status_code=200): headers_request = {'Content-type': 'application/json', 'Accept': '*/*', 'Authorization': f'Bearer {token}'} - response = requests.put(orb_url + '/api/v1/sinks/' + sink_id, json=sink_body, - headers=headers_request, verify=verify_ssl_bool) + response = requests.put(orb_url + '/api/v1/sinks/' + sink_id, json=sink_body, headers=headers_request, + verify=verify_ssl_bool) try: response_json = response.json() except ValueError: diff --git a/python-test/features/steps/local_agent.py b/python-test/features/steps/local_agent.py index 18f72e0b3..29460d7fa 100644 --- a/python-test/features/steps/local_agent.py +++ b/python-test/features/steps/local_agent.py @@ -24,8 +24,7 @@ def check_metrics_by_handler(context, handler_type): policy_name = context.policy['name'] local_prometheus_endpoint = f"http://localhost:{context.port}/api/v1/policies/{policy_name}/metrics/prometheus" correct_metrics, metrics_dif, metrics_present = wait_until_metrics_scraped(local_prometheus_endpoint, - expected_metrics, timeout=60, - wait_time=5) + expected_metrics, timeout=60) expected_metrics_not_present = expected_metrics.difference(metrics_present) if expected_metrics_not_present == set(): expected_metrics_not_present = None diff --git a/python-test/features/steps/schemas/sink_otlphttp_schema.json b/python-test/features/steps/schemas/sink_otlphttp_schema.json new file mode 100644 index 000000000..0c03751c2 --- /dev/null +++ b/python-test/features/steps/schemas/sink_otlphttp_schema.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://example.com/example.json", + "type": "object", + "required": [ + "id", + "name", + "description", + "state", + "backend", + "config", + "ts_created" + ], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "state": { + "type": "string" + }, + "backend": { + "type": "string" + }, + "config": { + "type": "object", + "required": [ + "authentication", + "exporter", + "opentelemetry" + ], + "properties": { + "authentication": { + "type": "object", + "required": [ + "password", + "type", + "username" + ], + "properties": { + "password": { + "type": "string" + }, + "type": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "exporter": { + "type": "object", + "required": [ + "endpoint" + ], + "properties": { + "endpoint": { + "type": "string" + } + } + }, + "opentelemetry": { + "type": "string" + } + } + }, + "ts_created": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/python-test/features/steps/schemas/sink_schema.json b/python-test/features/steps/schemas/sink_schema.json index 7b1532d86..091f8591d 100644 --- a/python-test/features/steps/schemas/sink_schema.json +++ b/python-test/features/steps/schemas/sink_schema.json @@ -35,9 +35,17 @@ "default": "", "title": "The description Schema", "examples": [ - "" + "This is a sink with prometheus backend" ] }, + "tags": { + "type": "object", + "default": {}, + "title": "The tags Schema", + "required": [], + "properties": {}, + "examples": [{}] + }, "state": { "type": "string", "default": "", @@ -59,40 +67,91 @@ "default": {}, "title": "The config Schema", "required": [ - "password", - "remote_host", - "username" + "authentication", + "exporter" ], "properties": { - "password": { - "type": "string", - "default": "", - "title": "The password Schema", - "examples": [ - "pass" - ] + "authentication": { + "type": "object", + "default": {}, + "title": "The authentication Schema", + "required": [ + "password", + "type", + "username" + ], + "properties": { + "password": { + "type": "string", + "default": "", + "title": "The password Schema", + "examples": [ + "" + ] + }, + "type": { + "type": "string", + "default": "", + "title": "The type Schema", + "examples": [ + "basicauth" + ] + }, + "username": { + "type": "string", + "default": "", + "title": "The username Schema", + "examples": [ + "admin" + ] + } + }, + "examples": [{ + "password": "", + "type": "basicauth", + "username": "admin" + }] }, - "remote_host": { - "type": "string", - "default": "", - "title": "The remote_host Schema", - "examples": [ - "https://prometheus-prod-10-prod-us-central-0.grafana.net/api/prom/push" - ] + "exporter": { + "type": "object", + "default": {}, + "title": "The exporter Schema", + "required": [ + "remote_host" + ], + "properties": { + "remote_host": { + "type": "string", + "default": "", + "title": "The remote_host Schema", + "examples": [ + "https://my.prometheus.endpoint/prom/push" + ] + } + }, + "examples": [{ + "remote_host": "https://my.prometheus.endpoint/prom/push" + }] }, - "username": { + "opentelemetry": { "type": "string", "default": "", - "title": "The username Schema", + "title": "The opentelemetry Schema", "examples": [ - "user" + "enabled" ] } }, "examples": [{ - "password": "pass", - "remote_host": "https://prometheus-prod-10-prod-us-central-0.grafana.net/api/prom/push", - "username": "user" + "authentication": { + "password": "12345678", + "type": "basicauth", + "username": "admin" + }, + "exporter": { + "remote_host": "https://my.prometheus.endpoint/prom/push" + }, + "opentelemetry": "enabled" }] }, "ts_created": { @@ -100,21 +159,27 @@ "default": "", "title": "The ts_created Schema", "examples": [ - "2022-05-27T14:12:09.108703Z" + "2023-06-01T17:37:21.848101999Z" ] } }, "examples": [{ - "id": "ef8b4710-734e-469f-b4ba-724f857499a9", - "name": "sink-schema", + "id": "f000638f-2a8a-4669-b681-b65d5c46d871", + "name": "test_sink_label_name_mEpBRavPHa", "description": "", "state": "unknown", "backend": "prometheus", "config": { - "password": "pass", - "remote_host": "https://prometheus-prod-10-prod-us-central-0.grafana.net/api/prom/push", - "username": "user" + "authentication": { + "password": "", + "type": "basicauth", + "username": "admin" + }, + "exporter": { + "remote_host": "https://prometheus.qa.orb.live/api/v1/write" + }, + "opentelemetry": "enabled" }, - "ts_created": "2022-05-27T14:12:09.108703Z" + "ts_created": "2023-06-01T17:37:21.848101999Z" }] -} +} \ No newline at end of file diff --git a/python-test/features/steps/test_config.py b/python-test/features/steps/test_config.py index 47ceaed40..12b63fd76 100644 --- a/python-test/features/steps/test_config.py +++ b/python-test/features/steps/test_config.py @@ -88,5 +88,6 @@ def _read_configs(): if include_otel_env_var == "false" and enable_otel == "false": raise ValueError("'enable_otel' is false, but the variable is not being included in the commands because of " "'include_otel_env_var' is false. Check your parameters.") + configs['backend_type'] = configs.get("backend_type", "prometheus") return configs diff --git a/python-test/features/steps/utils.py b/python-test/features/steps/utils.py index 96bda49ef..409c7f84b 100644 --- a/python-test/features/steps/utils.py +++ b/python-test/features/steps/utils.py @@ -175,7 +175,7 @@ def remove_key_from_json(json_file, key_to_be_removed): def threading_wait_until(func): - def wait_event(*args, wait_time=0.5, timeout=30, start_func_value=False, **kwargs): + def wait_event(*args, wait_time=5, timeout=35, start_func_value=False, **kwargs): event = threading.Event() func_value = start_func_value start = datetime.now().timestamp() diff --git a/python-test/requirements.txt b/python-test/requirements.txt index c9a11809b..cd8a90e24 100644 --- a/python-test/requirements.txt +++ b/python-test/requirements.txt @@ -1,15 +1,15 @@ behave==1.2.6 -ciso8601==2.2.0 -docker==5.0.3 -PyHamcrest==2.0.3 +ciso8601==2.3.0 +docker==6.1.3 +PyHamcrest==2.0.4 PyYAML==6.0 retry==0.9.2 -selenium==4.3.0 -behavex==1.5.4 -deepdiff==5.8.1 -jsonschema==4.6.0 -mkdocs==1.3.0 -mkdocs-material==8.3.3 -prometheus-client==0.15.0 -psutil==5.9.2 -webdriver-manager==3.8.3 +selenium==4.10.0 +behavex==2.0.1 +deepdiff==6.3.0 +jsonschema==4.18.4 +mkdocs==1.4.3 +mkdocs-material==9.1.18 +prometheus-client==0.17.0 +psutil==5.9.5 +webdriver-manager==3.8.6 diff --git a/sinker/config/types.go b/sinker/config/types.go index 122a3ced3..59a39c8d1 100644 --- a/sinker/config/types.go +++ b/sinker/config/types.go @@ -6,16 +6,15 @@ package config import ( "database/sql/driver" + "github.com/orb-community/orb/pkg/types" "time" ) +// SinkConfigParser to be compatible with new sinks config is coming from eventbus type SinkConfig struct { SinkID string `json:"sink_id"` OwnerID string `json:"owner_id"` - Url string `json:"remote_host"` - User string `json:"username"` - Password string `json:"password"` - Opentelemetry string `json:"opentelemetry"` + Config types.Metadata `json:"config"` State PrometheusState `json:"state,omitempty"` Msg string `json:"msg,omitempty"` LastRemoteWrite time.Time `json:"last_remote_write,omitempty"` diff --git a/sinker/config_state_check.go b/sinker/config_state_check.go index c905f892b..7a6301805 100644 --- a/sinker/config_state_check.go +++ b/sinker/config_state_check.go @@ -15,7 +15,7 @@ const ( streamID = "orb.sinker" streamLen = 1000 CheckerFreq = 5 * time.Minute - DefaultTimeout = 30 * time.Minute + DefaultTimeout = 5 * time.Minute ) func (svc *SinkerService) checkState(_ time.Time) { @@ -35,7 +35,7 @@ func (svc *SinkerService) checkState(_ time.Time) { // Set idle if the sinker is more than 30 minutes not sending metrics (Remove from Redis) if cfg.LastRemoteWrite.Add(DefaultTimeout).Before(time.Now()) { if cfg.State == config.Active { - if cfg.Opentelemetry != "enabled" { + if v, ok := cfg.Config["opentelemetry"]; !ok || v != "enabled" { if err := svc.sinkerCache.Remove(cfg.OwnerID, cfg.SinkID); err != nil { svc.logger.Error("error updating sink config cache", zap.Error(err)) return diff --git a/sinker/message_handler.go b/sinker/message_handler.go index c61d1d484..7db500b25 100644 --- a/sinker/message_handler.go +++ b/sinker/message_handler.go @@ -29,12 +29,18 @@ func (svc SinkerService) remoteWriteToPrometheus(tsList prometheus.TSList, owner return err } ctx := context.Background() - if cfgRepo.Opentelemetry == "enabled" { + otelMetadata, ok := cfgRepo.Config["opentelemetry"] + if ok && otelMetadata == "enabled" { svc.logger.Info("deprecate warning opentelemetry sink scraping legacy agent", zap.String("sink-ID", cfgRepo.SinkID)) ctx = context.WithValue(ctx, "deprecation", "opentelemetry") } + configMetadata := cfgRepo.Config.GetSubMetadata("exporter") + if configMetadata == nil { + svc.logger.Error("unable to find prometheus remote host", zap.Error(err)) + return err + } cfg := prometheus.NewConfig( - prometheus.WriteURLOption(cfgRepo.Url), + prometheus.WriteURLOption(configMetadata["remote_host"].(string)), ) promClient, err := prometheus.NewClient(cfg) @@ -42,9 +48,13 @@ func (svc SinkerService) remoteWriteToPrometheus(tsList prometheus.TSList, owner svc.logger.Error("unable to construct client", zap.Error(err)) return err } - + authMetadata := cfgRepo.Config.GetSubMetadata("authentication") + if authMetadata == nil { + svc.logger.Error("unable to find prometheus remote host", zap.Error(err)) + return err + } var headers = make(map[string]string) - headers["Authorization"] = svc.encodeBase64(cfgRepo.User, cfgRepo.Password) + headers["Authorization"] = svc.encodeBase64(authMetadata["username"].(string), authMetadata["password"].(string)) result, writeErr := promClient.WriteTimeSeries(ctx, tsList, prometheus.WriteOptions{Headers: headers}) if err := error(writeErr); err != nil { if cfgRepo.Msg != fmt.Sprint(err) { @@ -62,7 +72,8 @@ func (svc SinkerService) remoteWriteToPrometheus(tsList prometheus.TSList, owner return err } - svc.logger.Debug("successful sink", zap.Int("payload_size_b", result.PayloadSize), zap.String("sink_id", sinkID), zap.String("url", cfgRepo.Url), zap.String("user", cfgRepo.User)) + svc.logger.Debug("successful sink", zap.Int("payload_size_b", result.PayloadSize), + zap.String("sink_id", sinkID)) if cfgRepo.State != config.Active { cfgRepo.State = config.Active @@ -117,13 +128,15 @@ func (svc SinkerService) handleMetrics(ctx context.Context, agentID string, chan return fleet.ErrSchemaMalformed } - agentPb, err2 := svc.ExtractAgent(ctx, channelID) - if err2 != nil { - return err2 + agentPb, err := svc.ExtractAgent(ctx, channelID) + if err != nil { + return err } - agentName, _ := types.NewIdentifier(agentPb.AgentName) - + agentName, err := types.NewIdentifier(agentPb.AgentName) + if err != nil { + return err + } agent := fleet.Agent{ Name: agentName, MFOwnerID: agentPb.OwnerID, @@ -138,9 +151,9 @@ func (svc SinkerService) handleMetrics(ctx context.Context, agentID string, chan // however, per policy, we want a unique set of sink IDs as we don't want to send the same metrics twice to the same sink for the same policy datasetSinkIDs := make(map[string]bool) // first go through the datasets and gather the unique set of sinks we need for this particular policy - err2 := svc.GetSinks(agent, metricsPayload, datasetSinkIDs) - if err2 != nil { - return err2 + err = svc.GetSinks(agent, metricsPayload, datasetSinkIDs) + if err != nil { + return err } // ensure there are sinks diff --git a/sinker/otel/bridgeservice/bridge.go b/sinker/otel/bridgeservice/bridge.go index 43aa759af..45753d7a3 100644 --- a/sinker/otel/bridgeservice/bridge.go +++ b/sinker/otel/bridgeservice/bridge.go @@ -2,6 +2,7 @@ package bridgeservice import ( "context" + "fmt" "sort" "time" @@ -9,11 +10,13 @@ import ( fleetpb "github.com/orb-community/orb/fleet/pb" policiespb "github.com/orb-community/orb/policies/pb" "github.com/orb-community/orb/sinker/config" + "github.com/patrickmn/go-cache" "go.uber.org/zap" ) type BridgeService interface { ExtractAgent(ctx context.Context, channelID string) (*fleetpb.AgentInfoRes, error) + GetPolicyName(ctx context.Context, policyId, ownerId string) (*policiespb.PolicyRes, error) GetDataSetsFromAgentGroups(ctx context.Context, mfOwnerId string, agentGroupIds []string) (map[string]string, error) NotifyActiveSink(ctx context.Context, mfOwnerId, sinkId, state, message string) error GetSinkIdsFromPolicyID(ctx context.Context, mfOwnerId string, policyID string) (map[string]string, error) @@ -21,28 +24,33 @@ type BridgeService interface { } func NewBridgeService(logger *zap.Logger, + defaultCacheExpiration time.Duration, sinkerCache config.ConfigRepo, policiesClient policiespb.PolicyServiceClient, fleetClient fleetpb.FleetServiceClient, messageInputCounter metrics.Counter) SinkerOtelBridgeService { return SinkerOtelBridgeService{ - logger: logger, - sinkerCache: sinkerCache, - policiesClient: policiesClient, - fleetClient: fleetClient, - messageInputCounter: messageInputCounter, + defaultCacheExpiration: defaultCacheExpiration, + inMemoryCache: *cache.New(defaultCacheExpiration, defaultCacheExpiration*2), + logger: logger, + sinkerCache: sinkerCache, + policiesClient: policiesClient, + fleetClient: fleetClient, + messageInputCounter: messageInputCounter, } } type SinkerOtelBridgeService struct { - logger *zap.Logger - sinkerCache config.ConfigRepo - policiesClient policiespb.PolicyServiceClient - fleetClient fleetpb.FleetServiceClient - messageInputCounter metrics.Counter + inMemoryCache cache.Cache + defaultCacheExpiration time.Duration + logger *zap.Logger + sinkerCache config.ConfigRepo + policiesClient policiespb.PolicyServiceClient + fleetClient fleetpb.FleetServiceClient + messageInputCounter metrics.Counter } // Implementar nova funcao -func (bs *SinkerOtelBridgeService) IncreamentMessageCounter(publisher, subtopic, channel, protocol string) { +func (bs *SinkerOtelBridgeService) IncrementMessageCounter(publisher, subtopic, channel, protocol string) { labels := []string{ "method", "handleMsgFromAgent", "agent_id", publisher, @@ -89,26 +97,44 @@ func (bs *SinkerOtelBridgeService) NotifyActiveSink(ctx context.Context, mfOwner } bs.logger.Info("registering sink activity", zap.String("sinkID", sinkId), zap.String("newState", newState), zap.Any("currentState", cfgRepo.State)) } - } else if cfgRepo.State == config.Active || cfgRepo.State == config.Warning { + } else { err = bs.sinkerCache.AddActivity(mfOwnerId, sinkId) if err != nil { bs.logger.Error("error during update last remote write", zap.String("sinkId", sinkId), zap.Error(err)) return err } bs.logger.Info("registering sink activity", zap.String("sinkID", sinkId), zap.String("newState", newState), zap.Any("currentState", cfgRepo.State)) - } else if cfgRepo.State == config.Error { - cfgRepo.Msg = message } return nil } func (bs *SinkerOtelBridgeService) ExtractAgent(ctx context.Context, channelID string) (*fleetpb.AgentInfoRes, error) { - agentPb, err := bs.fleetClient.RetrieveAgentInfoByChannelID(ctx, &fleetpb.AgentInfoByChannelIDReq{Channel: channelID}) - if err != nil { - return nil, err + cacheKey := fmt.Sprintf("agent-%s", channelID) + value, found := bs.inMemoryCache.Get(cacheKey) + if !found { + agentPb, err := bs.fleetClient.RetrieveAgentInfoByChannelID(ctx, &fleetpb.AgentInfoByChannelIDReq{Channel: channelID}) + if err != nil { + return nil, err + } + bs.inMemoryCache.Set(cacheKey, agentPb, cache.DefaultExpiration) + return agentPb, nil + } + return value.(*fleetpb.AgentInfoRes), nil +} + +func (bs *SinkerOtelBridgeService) GetPolicyName(ctx context.Context, policyId, ownerID string) (*policiespb.PolicyRes, error) { + cacheKey := fmt.Sprintf("policy-%s", policyId) + value, found := bs.inMemoryCache.Get(cacheKey) + if !found { + policyPb, err := bs.policiesClient.RetrievePolicy(ctx, &policiespb.PolicyByIDReq{PolicyID: policyId, OwnerID: ownerID}) + if err != nil { + return nil, err + } + bs.inMemoryCache.Set(cacheKey, policyPb, cache.DefaultExpiration) + return policyPb, nil } - return agentPb, nil + return value.(*policiespb.PolicyRes), nil } func (bs *SinkerOtelBridgeService) GetSinkIdsFromDatasetIDs(ctx context.Context, mfOwnerId string, datasetIDs []string) (map[string]string, error) { @@ -116,15 +142,22 @@ func (bs *SinkerOtelBridgeService) GetSinkIdsFromDatasetIDs(ctx context.Context, mapSinkIdPolicy := make(map[string]string) sort.Strings(datasetIDs) for i := 0; i < len(datasetIDs); i++ { - datasetRes, err := bs.policiesClient.RetrieveDataset(ctx, &policiespb.DatasetByIDReq{ - DatasetID: datasetIDs[i], - OwnerID: mfOwnerId, - }) - if err != nil { - bs.logger.Info("unable to retrieve datasets from policy") - return nil, err + datasetID := datasetIDs[i] + cacheKey := fmt.Sprintf("ds-%s-%s", mfOwnerId, datasetID) + value, found := bs.inMemoryCache.Get(cacheKey) + if !found { + datasetRes, err := bs.policiesClient.RetrieveDataset(ctx, &policiespb.DatasetByIDReq{ + DatasetID: datasetID, + OwnerID: mfOwnerId, + }) + if err != nil { + bs.logger.Info("unable to retrieve datasets from policy") + return nil, err + } + value = datasetRes.SinkIds + bs.inMemoryCache.Set(cacheKey, value, cache.DefaultExpiration) } - for _, sinkId := range datasetRes.SinkIds { + for _, sinkId := range value.([]string) { mapSinkIdPolicy[sinkId] = "active" } } diff --git a/sinker/otel/kafkafanoutexporter/factory.go b/sinker/otel/kafkafanoutexporter/factory.go index 88801aa24..70a4a0e00 100644 --- a/sinker/otel/kafkafanoutexporter/factory.go +++ b/sinker/otel/kafkafanoutexporter/factory.go @@ -41,7 +41,7 @@ const ( // default from sarama.NewConfig() defaultMetadataFull = true // default max.message.bytes for the producer - defaultProducerMaxMessageBytes = 1000000 + defaultProducerMaxMessageBytes = 10000000 // default required_acks for the producer defaultProducerRequiredAcks = sarama.WaitForLocal // default from sarama.NewConfig() diff --git a/sinker/otel/orbreceiver/logs.go b/sinker/otel/orbreceiver/logs.go index c2e418cab..86f844499 100644 --- a/sinker/otel/orbreceiver/logs.go +++ b/sinker/otel/orbreceiver/logs.go @@ -38,7 +38,7 @@ func (r *OrbReceiver) MessageLogsInbound(msg messaging.Message) error { return } - r.sinkerService.IncreamentMessageCounter(msg.Publisher, msg.Subtopic, msg.Channel, msg.Protocol) + r.sinkerService.IncrementMessageCounter(msg.Publisher, msg.Subtopic, msg.Channel, msg.Protocol) if lr.Logs().ResourceLogs().Len() == 0 || lr.Logs().ResourceLogs().At(0).ScopeLogs().Len() == 0 { r.cfg.Logger.Info("No data information from logs request") @@ -92,6 +92,8 @@ func (r *OrbReceiver) ProccessLogsContext(scope plog.ScopeLogs, channel string) for k, v := range agentPb.OrbTags { scope = r.injectScopeLogsAttribute(scope, k, v) } + r.injectScopeLogsAttribute(scope, "agent", agentPb.AgentName) + r.injectScopeLogsAttribute(scope, "policy_id", polID) sinkIds, err := r.sinkerService.GetSinkIdsFromDatasetIDs(execCtx, agentPb.OwnerID, datasetIDs) if err != nil { diff --git a/sinker/otel/orbreceiver/metrics.go b/sinker/otel/orbreceiver/metrics.go index e17bbe516..90a851153 100644 --- a/sinker/otel/orbreceiver/metrics.go +++ b/sinker/otel/orbreceiver/metrics.go @@ -40,7 +40,7 @@ func (r *OrbReceiver) MessageMetricsInbound(msg messaging.Message) error { return } - r.sinkerService.IncreamentMessageCounter(msg.Publisher, msg.Subtopic, msg.Channel, msg.Protocol) + r.sinkerService.IncrementMessageCounter(msg.Publisher, msg.Subtopic, msg.Channel, msg.Protocol) if mr.Metrics().ResourceMetrics().Len() == 0 || mr.Metrics().ResourceMetrics().At(0).ScopeMetrics().Len() == 0 { r.cfg.Logger.Info("No data information from metrics request") @@ -95,6 +95,9 @@ func (r *OrbReceiver) ProccessMetricsContext(scope pmetric.ScopeMetrics, channel scope = r.injectScopeMetricsAttribute(scope, k, v) } + r.injectScopeMetricsAttribute(scope, "agent", agentPb.AgentName) + r.injectScopeMetricsAttribute(scope, "policy_id", polID) + scope = r.replaceScopeMetricsTimestamp(scope, pcommon.NewTimestampFromTime(time.Now())) sinkIds, err := r.sinkerService.GetSinkIdsFromDatasetIDs(execCtx, agentPb.OwnerID, datasetIDs) if err != nil { diff --git a/sinker/otel/orbreceiver/traces.go b/sinker/otel/orbreceiver/traces.go index b47335863..13486c32d 100644 --- a/sinker/otel/orbreceiver/traces.go +++ b/sinker/otel/orbreceiver/traces.go @@ -38,7 +38,7 @@ func (r *OrbReceiver) MessageTracesInbound(msg messaging.Message) error { return } - r.sinkerService.IncreamentMessageCounter(msg.Publisher, msg.Subtopic, msg.Channel, msg.Protocol) + r.sinkerService.IncrementMessageCounter(msg.Publisher, msg.Subtopic, msg.Channel, msg.Protocol) if tr.Traces().ResourceSpans().Len() == 0 || tr.Traces().ResourceSpans().At(0).ScopeSpans().Len() == 0 { r.cfg.Logger.Info("No data information from traces request") @@ -92,6 +92,8 @@ func (r *OrbReceiver) ProccessTracesContext(scope ptrace.ScopeSpans, channel str for k, v := range agentPb.OrbTags { scope = r.injectScopeSpansAttribute(scope, k, v) } + r.injectScopeSpansAttribute(scope, "agent", agentPb.AgentName) + r.injectScopeSpansAttribute(scope, "policy_id", polID) sinkIds, err := r.sinkerService.GetSinkIdsFromDatasetIDs(execCtx, agentPb.OwnerID, datasetIDs) if err != nil { diff --git a/sinker/redis/consumer/events.go b/sinker/redis/consumer/events.go index ea09af950..9d1639e90 100644 --- a/sinker/redis/consumer/events.go +++ b/sinker/redis/consumer/events.go @@ -14,9 +14,9 @@ import ( "github.com/orb-community/orb/pkg/types" ) -type updateSinkEvent struct { - sinkID string - owner string - config types.Metadata - timestamp time.Time +type UpdateSinkEvent struct { + SinkID string + Owner string + Config types.Metadata + Timestamp time.Time } diff --git a/sinker/redis/consumer/streams.go b/sinker/redis/consumer/streams.go index c2de74f22..2faae6d84 100644 --- a/sinker/redis/consumer/streams.go +++ b/sinker/redis/consumer/streams.go @@ -3,6 +3,8 @@ package consumer import ( "context" "encoding/json" + "fmt" + "github.com/orb-community/orb/pkg/errors" "time" "github.com/go-redis/redis/v8" @@ -69,11 +71,6 @@ func (es eventStore) Subscribe(context context.Context) error { break } err = es.handleSinksCreate(context, rte) - if err != nil { - es.logger.Error("Failed to handle event", zap.String("operation", event["operation"].(string)), zap.Error(err)) - break - } - es.client.XAck(context, stream, subGroup, msg.ID) case sinksUpdate: rte, derr := decodeSinksUpdate(event) if derr != nil { @@ -109,83 +106,83 @@ func NewEventStore(sinkerService sinker.Service, configRepo config.ConfigRepo, c } } -func decodeSinksCreate(event map[string]interface{}) (updateSinkEvent, error) { - val := updateSinkEvent{ - sinkID: read(event, "sink_id", ""), - owner: read(event, "owner", ""), - timestamp: time.Time{}, +func decodeSinksCreate(event map[string]interface{}) (UpdateSinkEvent, error) { + val := UpdateSinkEvent{ + SinkID: read(event, "sink_id", ""), + Owner: read(event, "owner", ""), + Config: readMetadata(event, "config"), + Timestamp: time.Now(), } var metadata types.Metadata if err := json.Unmarshal([]byte(read(event, "config", "")), &metadata); err != nil { - return updateSinkEvent{}, err + return UpdateSinkEvent{}, err } - val.config = metadata + val.Config = metadata return val, nil } -func decodeSinksUpdate(event map[string]interface{}) (updateSinkEvent, error) { - val := updateSinkEvent{ - sinkID: read(event, "sink_id", ""), - owner: read(event, "owner", ""), - timestamp: time.Time{}, +func decodeSinksUpdate(event map[string]interface{}) (UpdateSinkEvent, error) { + val := UpdateSinkEvent{ + SinkID: read(event, "sink_id", ""), + Owner: read(event, "owner", ""), + Timestamp: time.Now(), } var metadata types.Metadata if err := json.Unmarshal([]byte(read(event, "config", "")), &metadata); err != nil { - return updateSinkEvent{}, err + return UpdateSinkEvent{}, err } - val.config = metadata + val.Config = metadata return val, nil } -func decodeSinksRemove(event map[string]interface{}) (updateSinkEvent, error) { - val := updateSinkEvent{ - sinkID: read(event, "sink_id", ""), - owner: read(event, "owner", ""), - timestamp: time.Time{}, +func decodeSinksRemove(event map[string]interface{}) (UpdateSinkEvent, error) { + val := UpdateSinkEvent{ + SinkID: read(event, "sink_id", ""), + Owner: read(event, "owner", ""), + Timestamp: time.Now(), } return val, nil } -func (es eventStore) handleSinksRemove(_ context.Context, e updateSinkEvent) error { - if ok := es.configRepo.Exists(e.owner, e.sinkID); ok { - err := es.configRepo.Remove(e.owner, e.sinkID) +func (es eventStore) handleSinksRemove(_ context.Context, e UpdateSinkEvent) error { + if ok := es.configRepo.Exists(e.Owner, e.SinkID); ok { + err := es.configRepo.Remove(e.Owner, e.SinkID) if err != nil { + es.logger.Error("error during remove sinker cache entry", zap.Error(err)) return err } + } else { + es.logger.Error("did not find any sinker cache entry for removal", + zap.String("key", fmt.Sprintf("sinker_key-%s-%s", e.Owner, e.SinkID))) + return errors.New("did not find any sinker cache entry for removal") } return nil } -func (es eventStore) handleSinksUpdate(_ context.Context, e updateSinkEvent) error { - data, err := json.Marshal(e.config) - if err != nil { - return err - } +func (es eventStore) handleSinksUpdate(_ context.Context, e UpdateSinkEvent) error { var cfg config.SinkConfig - if err := json.Unmarshal(data, &cfg); err != nil { - return err - } - - if ok := es.configRepo.Exists(e.owner, e.sinkID); ok { - sinkConfig, err := es.configRepo.Get(e.owner, e.sinkID) + cfg.Config = types.FromMap(e.Config) + cfg.SinkID = e.SinkID + cfg.OwnerID = e.Owner + cfg.State = config.Unknown + if ok := es.configRepo.Exists(e.Owner, e.SinkID); ok { + sinkConfig, err := es.configRepo.Get(e.Owner, e.SinkID) if err != nil { return err } - sinkConfig.Url = cfg.Url - sinkConfig.User = cfg.User - sinkConfig.Password = cfg.Password + sinkConfig.Config = cfg.Config if sinkConfig.OwnerID == "" { - sinkConfig.OwnerID = e.owner + sinkConfig.OwnerID = e.Owner + } + if sinkConfig.SinkID == "" { + sinkConfig.SinkID = e.SinkID } - err = es.configRepo.Edit(sinkConfig) if err != nil { return err } } else { - cfg.SinkID = e.sinkID - cfg.OwnerID = e.owner - err = es.configRepo.Add(cfg) + err := es.configRepo.Add(cfg) if err != nil { return err } @@ -193,19 +190,13 @@ func (es eventStore) handleSinksUpdate(_ context.Context, e updateSinkEvent) err return nil } -func (es eventStore) handleSinksCreate(_ context.Context, e updateSinkEvent) error { - data, err := json.Marshal(e.config) - if err != nil { - return err - } +func (es eventStore) handleSinksCreate(_ context.Context, e UpdateSinkEvent) error { var cfg config.SinkConfig - if err := json.Unmarshal(data, &cfg); err != nil { - return err - } - cfg.SinkID = e.sinkID - cfg.OwnerID = e.owner + cfg.Config = types.FromMap(e.Config) + cfg.SinkID = e.SinkID + cfg.OwnerID = e.Owner cfg.State = config.Unknown - err = es.configRepo.Add(cfg) + err := es.configRepo.Add(cfg) if err != nil { return err } @@ -220,3 +211,12 @@ func read(event map[string]interface{}, key, def string) string { } return val } + +func readMetadata(event map[string]interface{}, key string) types.Metadata { + val, ok := event[key].(types.Metadata) + if !ok { + return types.Metadata{} + } + + return val +} diff --git a/sinker/redis/sinker.go b/sinker/redis/sinker.go index 97c3a4d39..d180f61e4 100644 --- a/sinker/redis/sinker.go +++ b/sinker/redis/sinker.go @@ -111,9 +111,10 @@ func (s *sinkerCache) AddActivity(ownerID string, sinkID string) error { if ownerID == "" || sinkID == "" { return errors.New("invalid parameters") } + defaultExpiration := time.Duration(10) * time.Minute skey := fmt.Sprintf("%s:%s", activityPrefix, sinkID) lastActivity := strconv.FormatInt(time.Now().Unix(), 10) - if err := s.client.Set(context.Background(), skey, lastActivity, 0).Err(); err != nil { + if err := s.client.Set(context.Background(), skey, lastActivity, defaultExpiration).Err(); err != nil { return err } s.logger.Info("added activity for owner and sink ids", zap.String("owner", ownerID), zap.String("sinkID", sinkID)) diff --git a/sinker/redis/sinker_test.go b/sinker/redis/sinker_test.go index 1dd692817..2088cc9e7 100644 --- a/sinker/redis/sinker_test.go +++ b/sinker/redis/sinker_test.go @@ -2,7 +2,7 @@ package redis_test import ( "fmt" - "reflect" + "github.com/orb-community/orb/pkg/types" "testing" "time" @@ -18,17 +18,28 @@ var idProvider = uuid.New() func TestSinkerConfigSave(t *testing.T) { sinkerCache := redis.NewSinkerCache(redisClient, logger) - config := config2.SinkConfig{ - SinkID: "123", - OwnerID: "test", - Url: "localhost", - User: "user", - Password: "password", - State: 0, - Msg: "", - LastRemoteWrite: time.Time{}, + var config config2.SinkConfig + config.SinkID = "123" + config.OwnerID = "test" + config.Config = types.Metadata{ + "authentication": types.Metadata{ + "password": "password", + "type": "basicauth", + "username": "user", + }, + "exporter": types.Metadata{ + "headers": map[string]string{ + "X-Tenant": "MY_TENANT_1", + }, + "remote_host": "localhost", + }, + "opentelemetry": "enabled", } + config.State = 0 + config.Msg = "" + config.LastRemoteWrite = time.Time{} + err := sinkerCache.Add(config) require.Nil(t, err, fmt.Sprintf("save sinker config to cache: expected nil got %s", err)) @@ -40,9 +51,7 @@ func TestSinkerConfigSave(t *testing.T) { config: config2.SinkConfig{ SinkID: "124", OwnerID: "test", - Url: "localhost", - User: "user", - Password: "password", + Config: config.Config, State: 0, Msg: "", LastRemoteWrite: time.Time{}, @@ -65,16 +74,26 @@ func TestSinkerConfigSave(t *testing.T) { func TestGetSinkerConfig(t *testing.T) { sinkerCache := redis.NewSinkerCache(redisClient, logger) - config := config2.SinkConfig{ - SinkID: "123", - OwnerID: "test", - Url: "localhost", - User: "user", - Password: "password", - State: 0, - Msg: "", - LastRemoteWrite: time.Time{}, + var config config2.SinkConfig + config.SinkID = "123" + config.OwnerID = "test" + config.Config = types.Metadata{ + "authentication": types.Metadata{ + "password": "password", + "type": "basicauth", + "username": "user", + }, + "exporter": types.Metadata{ + "headers": map[string]string{ + "X-Tenant": "MY_TENANT_1", + }, + "remote_host": "localhost", + }, + "opentelemetry": "enabled", } + config.State = 0 + config.Msg = "" + config.LastRemoteWrite = time.Time{} err := sinkerCache.Add(config) require.Nil(t, err, fmt.Sprintf("save sinker config to cache: expected nil got %s", err)) @@ -99,7 +118,17 @@ func TestGetSinkerConfig(t *testing.T) { for desc, tc := range cases { t.Run(desc, func(t *testing.T) { sinkConfig, err := sinkerCache.Get(tc.config.OwnerID, tc.sinkID) - assert.True(t, reflect.DeepEqual(tc.config, sinkConfig), fmt.Sprintf("%s: expected %v got %v", desc, tc.config, sinkConfig)) + assert.Equal(t, tc.config.SinkID, sinkConfig.SinkID, fmt.Sprintf("%s: expected %s got %s", desc, tc.config.SinkID, sinkConfig.SinkID)) + assert.Equal(t, tc.config.State, sinkConfig.State, fmt.Sprintf("%s: expected %s got %s", desc, tc.config.State, sinkConfig.State)) + assert.Equal(t, tc.config.OwnerID, sinkConfig.OwnerID, fmt.Sprintf("%s: expected %s got %s", desc, tc.config.OwnerID, sinkConfig.OwnerID)) + assert.Equal(t, tc.config.Msg, sinkConfig.Msg, fmt.Sprintf("%s: expected %s got %s", desc, tc.config.Msg, sinkConfig.Msg)) + assert.Equal(t, tc.config.LastRemoteWrite, sinkConfig.LastRemoteWrite, fmt.Sprintf("%s: expected %s got %s", desc, tc.config.LastRemoteWrite, sinkConfig.LastRemoteWrite)) + if tc.config.Config != nil { + _, ok := sinkConfig.Config["authentication"] + assert.True(t, ok, fmt.Sprintf("%s: should contain authentication metadata", desc)) + _, ok = sinkConfig.Config["exporter"] + assert.True(t, ok, fmt.Sprintf("%s: should contain exporter metadata", desc)) + } assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s", desc, tc.err, err)) }) } @@ -107,7 +136,26 @@ func TestGetSinkerConfig(t *testing.T) { func TestGetAllSinkerConfig(t *testing.T) { sinkerCache := redis.NewSinkerCache(redisClient, logger) - + var config config2.SinkConfig + config.SinkID = "123" + config.OwnerID = "test" + config.State = 0 + config.Msg = "" + config.Config = types.Metadata{ + "authentication": types.Metadata{ + "password": "password", + "type": "basicauth", + "username": "user", + }, + "exporter": types.Metadata{ + "headers": map[string]string{ + "X-Tenant": "MY_TENANT_1", + }, + "remote_host": "localhost", + }, + "opentelemetry": "enabled", + } + config.LastRemoteWrite = time.Time{} sinksConfig := map[string]struct { config config2.SinkConfig }{ @@ -115,9 +163,7 @@ func TestGetAllSinkerConfig(t *testing.T) { config: config2.SinkConfig{ SinkID: "123", OwnerID: "test", - Url: "localhost", - User: "user", - Password: "password", + Config: config.Config, State: 0, Msg: "", LastRemoteWrite: time.Time{}, @@ -127,9 +173,7 @@ func TestGetAllSinkerConfig(t *testing.T) { config: config2.SinkConfig{ SinkID: "134", OwnerID: "test", - Url: "localhost", - User: "user", - Password: "password", + Config: config.Config, State: 0, Msg: "", LastRemoteWrite: time.Time{}, diff --git a/sinker/service.go b/sinker/service.go index 2835fcdcf..89260be98 100644 --- a/sinker/service.go +++ b/sinker/service.go @@ -49,9 +49,10 @@ type SinkerService struct { otelLogsCancelFunct context.CancelFunc otelKafkaUrl string - sinkerCache config.ConfigRepo - esclient *redis.Client - logger *zap.Logger + sinkerCache config.ConfigRepo + inMemoryCacheExpiration time.Duration + esclient *redis.Client + logger *zap.Logger hbTicker *time.Ticker hbDone chan bool @@ -71,7 +72,9 @@ type SinkerService struct { } func (svc SinkerService) Start() error { - svc.asyncContext, svc.cancelAsyncContext = context.WithCancel(context.WithValue(context.Background(), "routine", "async")) + ctx := context.WithValue(context.Background(), "routine", "async") + ctx = context.WithValue(ctx, "cache_expiry", svc.inMemoryCacheExpiration) + svc.asyncContext, svc.cancelAsyncContext = context.WithCancel(ctx) if !svc.otel { topic := fmt.Sprintf("channels.*.%s", BackendMetricsTopic) if err := svc.pubSub.Subscribe(topic, svc.handleMsgFromAgent); err != nil { @@ -96,13 +99,12 @@ func (svc SinkerService) Start() error { func (svc SinkerService) startOtel(ctx context.Context) error { if svc.otel { var err error - // starting Otel Metrics components - bridgeServiceMetrics := bridgeservice.NewBridgeService(svc.logger, svc.sinkerCache, svc.policiesClient, svc.fleetClient, svc.messageInputCounter) - svc.otelMetricsCancelFunct, err = otel.StartOtelMetricsComponents(ctx, &bridgeServiceMetrics, svc.logger, svc.otelKafkaUrl, svc.pubSub) + + bridgeService := bridgeservice.NewBridgeService(svc.logger, svc.inMemoryCacheExpiration, svc.sinkerCache, svc.policiesClient, svc.fleetClient, svc.messageInputCounter) + svc.otelMetricsCancelFunct, err = otel.StartOtelMetricsComponents(ctx, &bridgeService, svc.logger, svc.otelKafkaUrl, svc.pubSub) // starting Otel Logs components - bridgeServiceLogs := bridgeservice.NewBridgeService(svc.logger, svc.sinkerCache, svc.policiesClient, svc.fleetClient, svc.messageInputCounter) - svc.otelLogsCancelFunct, err = otel.StartOtelLogsComponents(ctx, &bridgeServiceLogs, svc.logger, svc.otelKafkaUrl, svc.pubSub) + svc.otelLogsCancelFunct, err = otel.StartOtelLogsComponents(ctx, &bridgeService, svc.logger, svc.otelKafkaUrl, svc.pubSub) if err != nil { svc.logger.Error("error during StartOtelComponents", zap.Error(err)) @@ -147,21 +149,23 @@ func New(logger *zap.Logger, requestGauge metrics.Gauge, requestCounter metrics.Counter, inputCounter metrics.Counter, + defaultCacheExpiration time.Duration, ) Service { pktvisor.Register(logger) return &SinkerService{ - logger: logger, - pubSub: pubSub, - esclient: esclient, - sinkerCache: configRepo, - policiesClient: policiesClient, - fleetClient: fleetClient, - sinksClient: sinksClient, - requestGauge: requestGauge, - requestCounter: requestCounter, - messageInputCounter: inputCounter, - otel: enableOtel, - otelKafkaUrl: otelKafkaUrl, + inMemoryCacheExpiration: defaultCacheExpiration, + logger: logger, + pubSub: pubSub, + esclient: esclient, + sinkerCache: configRepo, + policiesClient: policiesClient, + fleetClient: fleetClient, + sinksClient: sinksClient, + requestGauge: requestGauge, + requestCounter: requestCounter, + messageInputCounter: inputCounter, + otel: enableOtel, + otelKafkaUrl: otelKafkaUrl, } } diff --git a/sinks/api/grpc/server.go b/sinks/api/grpc/server.go index 9fd3869a1..71e30b036 100644 --- a/sinks/api/grpc/server.go +++ b/sinks/api/grpc/server.go @@ -14,6 +14,7 @@ import ( kitgrpc "github.com/go-kit/kit/transport/grpc" "github.com/opentracing/opentracing-go" "github.com/orb-community/orb/sinks" + "github.com/orb-community/orb/sinks/authentication_type" "github.com/orb-community/orb/sinks/pb" "go.uber.org/zap" "google.golang.org/grpc/codes" @@ -26,7 +27,7 @@ type grpcServer struct { logger *zap.Logger pb.UnimplementedSinkServiceServer retrieveSink kitgrpc.Handler - passwordService sinks.PasswordService + passwordService authentication_type.PasswordService retrieveSinks kitgrpc.Handler } diff --git a/sinks/api/http/endpoint.go b/sinks/api/http/endpoint.go index 8a1101a2e..f3b59496f 100644 --- a/sinks/api/http/endpoint.go +++ b/sinks/api/http/endpoint.go @@ -10,36 +10,34 @@ package http import ( "context" + "time" + "github.com/go-kit/kit/endpoint" "github.com/orb-community/orb/pkg/errors" "github.com/orb-community/orb/pkg/types" "github.com/orb-community/orb/sinks" + "github.com/orb-community/orb/sinks/authentication_type" "github.com/orb-community/orb/sinks/backend" "go.uber.org/zap" - "time" ) -var restrictiveKeyPrefixes = []string{backend.ConfigFeatureTypePassword} - -func omitSecretInformation(be backend.Backend, format string, metadata types.Metadata) (restrictedMetadata types.Metadata, configData string) { - metadata.RestrictKeys(func(key string) bool { - match := false - for _, restrictiveKey := range restrictiveKeyPrefixes { - if key == restrictiveKey { - match = true - return match - } - } - return match - }) - var err error - if format != "" { - configData, err = be.ConfigToFormat(format, metadata) - if err != nil { - return metadata, "" - } +func omitSecretInformation(configSvc *sinks.Configuration, inputSink sinks.Sink) (returnSink sinks.Sink, err error) { + a, err := configSvc.Authentication.OmitInformation("object", inputSink.Config) + if err != nil { + return sinks.Sink{}, err } - return metadata, configData + returnSink = inputSink + authMeta := a.(types.Metadata) + returnSink.Config = authMeta + if inputSink.Format == "yaml" { + configData, newErr := configSvc.Authentication.ConfigToFormat(inputSink.Format, authMeta) + if newErr != nil { + err = newErr + return + } + returnSink.ConfigData = configData.(string) + } + return } func addEndpoint(svc sinks.SinkService) endpoint.Endpoint { @@ -49,28 +47,42 @@ func addEndpoint(svc sinks.SinkService) endpoint.Endpoint { svc.GetLogger().Error("got error in validating request", zap.Error(err)) return nil, err } - nID, err := types.NewIdentifier(req.Name) if err != nil { svc.GetLogger().Error("got error in creating new identifier", zap.Error(err)) return nil, err } - var config types.Metadata - reqBackend := backend.GetBackend(req.Backend) - if req.Format != "" { - config, err = reqBackend.ParseConfig(req.Format, req.ConfigData) - if err != nil { - svc.GetLogger().Error("got error in parsing configuration", zap.Error(err)) - return nil, err + var exporterConfig types.Metadata + var authConfig types.Metadata + var configSvc *sinks.Configuration + if len(req.Format) > 0 && req.Format == "yaml" { + if len(req.ConfigData) > 0 { + if req.Backend == "" { + return nil, errors.Wrap(errors.ErrBackendNotFound, errors.New("backend not found")) + } + configSvc, exporterConfig, authConfig, err = GetConfigurationAndMetadataFromYaml(req.Backend, req.ConfigData) + if err != nil { + svc.GetLogger().Error("got error in parse and validate configuration", zap.Error(err)) + return nil, err + } + } else { + svc.GetLogger().Error("got error in parse and validate configuration. config_data field is expected", zap.Error(err)) + return nil, errors.New("malformed entity specification. configuration field is expected") } } else { - if req.Config != nil { - config = req.Config - } else { - svc.GetLogger().Error("did not receive any valid configuration") - return nil, errors.ErrMalformedEntity + if req.Backend == "" { + return nil, errors.Wrap(errors.ErrBackendNotFound, errors.New("backend not found")) + } + configSvc, exporterConfig, authConfig, err = GetConfigurationAndMetadataFromMeta(req.Backend, req.Config) + if err != nil { + svc.GetLogger().Error("got error in parse and validate configuration", zap.Error(err)) + return nil, err } } + config := types.Metadata{ + "exporter": exporterConfig, + authentication_type.AuthenticationKey: authConfig, + } sink := sinks.Sink{ Name: nID, Backend: req.Backend, @@ -83,12 +95,15 @@ func addEndpoint(svc sinks.SinkService) endpoint.Endpoint { } saved, err := svc.CreateSink(ctx, req.token, sink) if err != nil { - svc.GetLogger().Error("received error on creating sink") + svc.GetLogger().Error("received error on creating sink", zap.Error(err)) return nil, err } - omittedConfig, omittedConfigData := omitSecretInformation(reqBackend, saved.Format, saved.Config) - + omittedSink, err := omitSecretInformation(configSvc, saved) + if err != nil { + svc.GetLogger().Error("sink was created, but got error in the response build", zap.Error(err)) + return nil, err + } res := sinkRes{ ID: saved.ID, Name: saved.Name.String(), @@ -97,8 +112,8 @@ func addEndpoint(svc sinks.SinkService) endpoint.Endpoint { State: saved.State.String(), Error: saved.Error, Backend: saved.Backend, - Config: omittedConfig, - ConfigData: omittedConfigData, + Config: omittedSink.Config, + ConfigData: omittedSink.ConfigData, Format: saved.Format, TsCreated: saved.Created, created: true, @@ -119,47 +134,83 @@ func updateSinkEndpoint(svc sinks.SinkService) endpoint.Endpoint { svc.GetLogger().Error("could not find sink with id", zap.String("sinkID", req.id), zap.Error(err)) return nil, err } - sinkBackend := backend.GetBackend(currentSink.Backend) - if err := req.validate(sinkBackend); err != nil { + if err := req.validate(); err != nil { svc.GetLogger().Error("error validating request", zap.Error(err)) return nil, err } - var config types.Metadata + + // Update only the fields in currentSink that are populated in req + if req.Tags != nil { + currentSink.Tags = req.Tags + } + if req.Format != "" { - config, err = sinkBackend.ParseConfig(req.Format, req.ConfigData) - if err != nil { - svc.GetLogger().Error("got error in parsing configuration", zap.Error(err)) - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - } else { - if req.Config != nil { - config = req.Config - } + currentSink.Format = req.Format } - sink := sinks.Sink{ - ID: req.id, - Tags: req.Tags, - Config: config, - ConfigData: req.ConfigData, - Format: req.Format, - Description: req.Description, + if req.Description != nil { + currentSink.Description = req.Description } - if req.Name != "" { nameID, err := types.NewIdentifier(req.Name) if err != nil { svc.GetLogger().Error("error on getting new identifier", zap.Error(err)) - return nil, errors.ErrMalformedEntity + return nil, errors.ErrConflict + } + currentSink.Name = nameID + } + var configSvc *sinks.Configuration + var exporterConfig types.Metadata + var authConfig types.Metadata + + // Update the config if either req.Config or req.ConfigData is populated + if req.Config != nil || req.ConfigData != "" { + if req.Format == "yaml" { + configSvc, exporterConfig, authConfig, err = GetConfigurationAndMetadataFromYaml(currentSink.Backend, req.ConfigData) + if err != nil { + svc.GetLogger().Error("got error in parse and validate configuration", zap.Error(err)) + return nil, errors.Wrap(errors.ErrMalformedEntity, err) + } + } else if req.Config != nil { + configSvc, exporterConfig, authConfig, err = GetConfigurationAndMetadataFromMeta(currentSink.Backend, req.Config) + if err != nil { + svc.GetLogger().Error("got error in parse and validate configuration", zap.Error(err)) + return nil, errors.Wrap(errors.ErrMalformedEntity, err) + } + } + } else { + if currentSink.Format == "yaml" { + configSvc, exporterConfig, authConfig, err = GetConfigurationAndMetadataFromYaml(currentSink.Backend, currentSink.ConfigData) + if err != nil { + svc.GetLogger().Error("got error in parse and validate configuration", zap.Error(err)) + return nil, errors.Wrap(errors.ErrMalformedEntity, err) + } + } else { + configSvc, exporterConfig, authConfig, err = GetConfigurationAndMetadataFromMeta(currentSink.Backend, currentSink.Config) + if err != nil { + svc.GetLogger().Error("got error in parse and validate configuration", zap.Error(err)) + return nil, errors.Wrap(errors.ErrMalformedEntity, err) + } } - sink.Name = nameID } - sinkEdited, err := svc.UpdateSink(ctx, req.token, sink) + currentSink.Config = types.Metadata{ + "exporter": exporterConfig, + authentication_type.AuthenticationKey: authConfig, + } + + sinkEdited, err := svc.UpdateSink(ctx, req.token, currentSink) if err != nil { svc.GetLogger().Error("error on updating sink", zap.Error(err)) return nil, err } - omittedConfig, omittedConfigData := omitSecretInformation(sinkBackend, sinkEdited.Format, sinkEdited.Config) + + var omittedSink sinks.Sink + omittedSink, err = omitSecretInformation(configSvc, sinkEdited) + if err != nil { + svc.GetLogger().Error("sink was updated, but got error in the response build", zap.Error(err)) + return nil, err + } + res := sinkRes{ ID: sinkEdited.ID, Name: sinkEdited.Name.String(), @@ -168,11 +219,12 @@ func updateSinkEndpoint(svc sinks.SinkService) endpoint.Endpoint { State: sinkEdited.State.String(), Error: sinkEdited.Error, Backend: sinkEdited.Backend, - Config: omittedConfig, - ConfigData: omittedConfigData, + Config: omittedSink.Config, + ConfigData: omittedSink.ConfigData, Format: sinkEdited.Format, created: false, } + return res, nil } } @@ -202,7 +254,15 @@ func listSinksEndpoint(svc sinks.SinkService) endpoint.Endpoint { } for _, sink := range page.Sinks { reqBackend := backend.GetBackend(sink.Backend) - omittedConfig, omittedConfigData := omitSecretInformation(reqBackend, sink.Format, sink.Config) + reqAuthType, _ := authentication_type.GetAuthType(sink.GetAuthenticationTypeName()) + cfg := sinks.Configuration{ + Exporter: reqBackend, + Authentication: reqAuthType, + } + responseSink, err := omitSecretInformation(&cfg, sink) + if err != nil { + return nil, err + } view := sinkRes{ ID: sink.ID, Name: sink.Name.String(), @@ -210,8 +270,8 @@ func listSinksEndpoint(svc sinks.SinkService) endpoint.Endpoint { State: sink.State.String(), Error: sink.Error, Backend: sink.Backend, - Config: omittedConfig, - ConfigData: omittedConfigData, + Config: responseSink.Config, + ConfigData: responseSink.ConfigData, Format: sink.Format, TsCreated: sink.Created, } @@ -224,6 +284,44 @@ func listSinksEndpoint(svc sinks.SinkService) endpoint.Endpoint { } } +func listAuthenticationTypes(svc sinks.SinkService) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(listBackendsReq) + if err = req.validate(); err != nil { + return nil, err + } + + authtypes, err := svc.ListAuthenticationTypes(ctx, req.token) + if err != nil { + return nil, err + } + + response = sinkAuthTypesRes{ + AuthenticationTypes: authtypes, + } + + return + } +} + +func viewAuthenticationType(svc sinks.SinkService) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(viewResourceReq) + if err = req.validate(); err != nil { + return nil, err + } + authType, err := svc.ViewAuthenticationType(ctx, req.token, req.id) + if err != nil { + return nil, err + } + + response = sinkAuthTypeRes{ + AuthenticationTypes: authType, + } + return + } +} + func listBackendsEndpoint(svc sinks.SinkService) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { req := request.(listBackendsReq) @@ -283,7 +381,12 @@ func viewSinkEndpoint(svc sinks.SinkService) endpoint.Endpoint { return sink, err } reqBackend := backend.GetBackend(sink.Backend) - omittedConfig, omittedConfigData := omitSecretInformation(reqBackend, sink.Format, sink.Config) + reqAuthType, _ := authentication_type.GetAuthType(sink.GetAuthenticationTypeName()) + cfg := sinks.Configuration{ + Exporter: reqBackend, + Authentication: reqAuthType, + } + responseSink, err := omitSecretInformation(&cfg, sink) res := sinkRes{ ID: sink.ID, Name: sink.Name.String(), @@ -292,8 +395,8 @@ func viewSinkEndpoint(svc sinks.SinkService) endpoint.Endpoint { State: sink.State.String(), Error: sink.Error, Backend: sink.Backend, - Config: omittedConfig, - ConfigData: omittedConfigData, + Config: responseSink.Config, + ConfigData: responseSink.ConfigData, Format: sink.Format, TsCreated: sink.Created, } diff --git a/sinks/api/http/endpoint_test.go b/sinks/api/http/endpoint_test.go index 848cd739e..316885cfa 100644 --- a/sinks/api/http/endpoint_test.go +++ b/sinks/api/http/endpoint_test.go @@ -12,45 +12,71 @@ import ( "context" "encoding/json" "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + "github.com/gofrs/uuid" mfsdk "github.com/mainflux/mainflux/pkg/sdk/go" "github.com/opentracing/opentracing-go/mocktracer" "github.com/orb-community/orb/pkg/types" "github.com/orb-community/orb/sinks" + "github.com/orb-community/orb/sinks/authentication_type" "github.com/orb-community/orb/sinks/backend" - prometheusbackend "github.com/orb-community/orb/sinks/backend/prometheus" skmocks "github.com/orb-community/orb/sinks/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" ) const ( - contentType = "application/json" - token = "token" - invalidToken = "invalid" - email = "user@example.com" - validJson = "{ \"name\": \"my-prom-sink\", \"backend\": \"prometheus\", \"config\": { \"remote_host\": \"https://orb.community/\", \"username\": \"dbuser\", \"password\": \"dbpassword\" }, \"description\": \"An example prometheus sink\", \"tags\": { \"cloud\": \"aws\" }, \"validate_only\": false}" - conflictValidJson = "{\n \"name\": \"conflict\",\n \"backend\": \"prometheus\",\n \"config\": {\n \"remote_host\": \"https://orb.community/\",\n \"username\": \"dbuser\"\n, \"password\": \"dbpass\"\n },\n \"description\": \"An example prometheus sink\",\n \"tags\": {\n \"cloud\": \"aws\"\n },\n \"validate_only\": false\n}" - invalidJson = "{" + contentType = "application/json" + token = "token" + invalidToken = "invalid" + email = "user@example.com" + validJson = `{ + "name": "my-prom-sink", + "backend": "prometheus", + "config": { + "exporter": { + "remote_host": "https://orb.community/" + }, + "authentication": { + "type": "basicauth", + "username": "dbuser", + "password": "dbpassword" + } + }, + "description": "An example prometheus sink", + "tags": { "cloud": "aws" }, + "validate_only": false +}` + conflictValidJson = `{ + "name": "conflict", + "backend": "prometheus", + "config": { + "exporter" : { + "remote_host": "https://orb.community/" + }, + "authentication" : { + "type": "basicauth", + "username": "dbuser", + "password": "dbpass" + } + }, + "description": "An example prometheus sink", + "tags": { + "cloud": "aws" + }, + "validate_only": false +}` + invalidJson = "{" ) var ( - nameID, _ = types.NewIdentifier("my-sink") - description = "An example prometheus sink" - sink = sinks.Sink{ - Name: nameID, - Description: &description, - Backend: "prometheus", - Config: map[string]interface{}{"remote_host": "https://orb.community/", "username": "dbuser", "password": "dbpass"}, - Tags: map[string]string{"cloud": "aws"}, - } invalidName = strings.Repeat("m", maxNameSize+1) notFoundRes = toJSON(errorRes{sinks.ErrNotFound.Error()}) unauthRes = toJSON(errorRes{sinks.ErrUnauthorizedAccess.Error()}) @@ -85,16 +111,16 @@ func (tr testRequest) make() (*http.Response, error) { func newService(tokens map[string]string) sinks.SinkService { logger := zap.NewNop() auth := skmocks.NewAuthService(tokens) - pwdSvc := sinks.NewPasswordService(logger, "_testing_string_") + pwdSvc := authentication_type.NewPasswordService(logger, "_testing_string_") sinkRepo := skmocks.NewSinkRepository(pwdSvc) config := mfsdk.Config{ ThingsURL: "localhost", } - mfsdk := mfsdk.NewSDK(config) + sdk := mfsdk.NewSDK(config) - return sinks.NewSinkService(logger, auth, sinkRepo, mfsdk, pwdSvc) + return sinks.NewSinkService(logger, auth, sinkRepo, sdk, pwdSvc) } func newServer(svc sinks.SinkService) *httptest.Server { @@ -112,6 +138,19 @@ func TestCreateSinks(t *testing.T) { server := newServer(service) defer server.Close() + nameID, _ := types.NewIdentifier("my-sink") + description := "An example prometheus sink" + sink := sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, + } + // Conflict creation scenario sinkConflict := sink conflictNameID, err := types.NewIdentifier("conflict") @@ -122,25 +161,56 @@ func TestCreateSinks(t *testing.T) { require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) invalidNameJson := toJSON(addReq{ - Name: "s", - Backend: "prometheus", + Name: "s", + Description: "An example prometheus sink", + Backend: "prometheus", Config: types.Metadata{ - "username": "test", - "remote_host": "https://orb.community/", - "description": "An example prometheus sink", + "exporter": types.Metadata{ + "remote_host": "https://orb.community/", + }, + "authentication": types.Metadata{ + "type": "basicauth", + "username": "test", + "password": "test", + }, }, Tags: map[string]string{ "cloud": "aws", }, }) + otlpSink := toJSON(addReq{ + Name: "otlp-s-1", + Backend: "otlphttp", + Config: types.Metadata{ + "exporter": types.Metadata{ + "endpoint": "localhost:4318", + }, + "authentication": types.Metadata{ + "type": "basicauth", + "username": "test", + "password": "test", + }, + }, + Description: "the first otlp sink ever", + Tags: map[string]string{ + "cloud": "aws", + }, + }) + emptyNameJson := toJSON(addReq{ - Name: "", - Backend: "prometheus", + Name: "", + Description: "An example prometheus sink", + Backend: "prometheus", Config: types.Metadata{ - "username": "test", - "remote_host": "https://orb.community/", - "description": "An example prometheus sink", + "exporter": types.Metadata{ + "remote_host": "https://orb.community/", + }, + "authentication": types.Metadata{ + "type": "basicauth", + "username": "test", + "password": "test", + }, }, Tags: map[string]string{ "cloud": "aws", @@ -156,7 +226,54 @@ func TestCreateSinks(t *testing.T) { Name: "sinkConfig", Backend: "prometheus", Config: types.Metadata{ - "user": "test", + "authentication": types.Metadata{ + "type": "basicauth", + "username": "test", + "password": "test", + }, + }, + }) + jsonSinkTestConfig2 := toJSON(addReq{ + Name: "sinkConfig", + Backend: "prometheus", + Config: types.Metadata{ + "exporter": types.Metadata{ + "remote_host": "https://orb.community/", + }, + }, + }) + jsonWithoutAuthType := toJSON(addReq{ + Description: "An example prometheus sink", + Backend: "prometheus", + Config: types.Metadata{ + "exporter": types.Metadata{ + "remote_host": "https://orb.community/", + }, + "authentication": types.Metadata{ + "username": "test", + "password": "test", + }, + }, + Tags: map[string]string{ + "cloud": "aws", + }, + }) + + jsonInvalidAuthType := toJSON(addReq{ + Description: "An example prometheus sink", + Backend: "prometheus", + Config: types.Metadata{ + "exporter": types.Metadata{ + "remote_host": "https://orb.community/", + }, + "authentication": types.Metadata{ + "type": "anonymous", + "username": "test", + "password": "test", + }, + }, + Tags: map[string]string{ + "cloud": "aws", }, }) @@ -174,6 +291,13 @@ func TestCreateSinks(t *testing.T) { status: http.StatusCreated, location: "/sinks", }, + "add a otlp sink": { + req: otlpSink, + contentType: contentType, + auth: token, + status: http.StatusCreated, + location: "/sinks", + }, "add a duplicate sink": { req: conflictValidJson, contentType: contentType, @@ -206,7 +330,7 @@ func TestCreateSinks(t *testing.T) { req: invalidNameJson, contentType: contentType, auth: token, - status: http.StatusBadRequest, + status: http.StatusConflict, location: "/sinks", }, "add a sink with empty name": { @@ -217,14 +341,35 @@ func TestCreateSinks(t *testing.T) { location: "/sinks", }, "add sink with missing config": { - req: string(jsonSinkTestConfigNoConfig), + req: jsonSinkTestConfigNoConfig, + contentType: contentType, + auth: token, + status: http.StatusBadRequest, + location: "/sinks", + }, + "add sink with only authentication object on config": { + req: jsonSinkTestConfig, contentType: contentType, auth: token, status: http.StatusBadRequest, location: "/sinks", }, - "add sink with only 1 key on config": { - req: string(jsonSinkTestConfig), + "add sink with only exporter object on config": { + req: jsonSinkTestConfig2, + contentType: contentType, + auth: token, + status: http.StatusBadRequest, + location: "/sinks", + }, + "add sink with no authentication type within config": { + req: jsonWithoutAuthType, + contentType: contentType, + auth: token, + status: http.StatusBadRequest, + location: "/sinks", + }, + "add sink with invalid authentication type within config": { + req: jsonInvalidAuthType, contentType: contentType, auth: token, status: http.StatusBadRequest, @@ -254,7 +399,18 @@ func TestUpdateSink(t *testing.T) { service := newService(map[string]string{token: email}) server := newServer(service) defer server.Close() - + nameID, _ := types.NewIdentifier("my-sink") + description := "An example prometheus sink" + sink := sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, + } sk, err := service.CreateSink(context.Background(), token, sink) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -274,6 +430,30 @@ func TestUpdateSink(t *testing.T) { Tags: sk.Tags, }) + dataNoAuthConfig := toJSON(updateSinkReq{ + Name: "invalid-sink-no-auth", + Backend: "prometheus", + Description: sk.Description, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + }, + Tags: sk.Tags, + }) + + dataNoExporterConfig := toJSON(updateSinkReq{ + Name: "invalid-sink-no-exporter", + Backend: "prometheus", + Description: sk.Description, + Config: types.Metadata{ + "authentication": types.Metadata{ + "type": "basicauth", + "username": "test", + "password": "test", + }, + }, + Tags: sk.Tags, + }) + cases := map[string]struct { req string id string @@ -405,13 +585,34 @@ func TestUpdateSink(t *testing.T) { id: sk.ID, contentType: contentType, auth: token, - status: http.StatusBadRequest, + status: http.StatusConflict, }, "update existing sink with a invalid regex name": { req: dataInvalidRgxName, id: sk.ID, contentType: contentType, auth: token, + status: http.StatusConflict, + }, + "update existing sink with a config without authentication": { + req: dataInvalidRgxName, + id: sk.ID, + contentType: contentType, + auth: token, + status: http.StatusConflict, + }, + "update existing sink with a config without exporter": { + req: dataNoExporterConfig, + id: sk.ID, + contentType: contentType, + auth: token, + status: http.StatusBadRequest, + }, + "update existing sink with a config without auth type": { + req: dataNoAuthConfig, + id: sk.ID, + contentType: contentType, + auth: token, status: http.StatusBadRequest, }, } @@ -444,8 +645,11 @@ func TestListSinks(t *testing.T) { snk := sinks.Sink{ Name: skName, Backend: "prometheus", - Config: map[string]interface{}{"remote_host": "https://orb.community/", "username": "dbuser"}, - Tags: map[string]string{"cloud": "aws"}, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, } sk, err := svc.CreateSink(context.Background(), token, snk) @@ -620,7 +824,8 @@ func TestListSinks(t *testing.T) { res, err := req.make() require.Nil(t, err, fmt.Sprintf("%s: unexpected error: %s", desc, err)) var body sinksPagesRes - json.NewDecoder(res.Body).Decode(&body) + err = json.NewDecoder(res.Body).Decode(&body) + require.NoError(t, err) total := uint64(len(body.Sinks)) assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -630,6 +835,66 @@ func TestListSinks(t *testing.T) { } } +func TestAuthentitcationTypesEndpoints(t *testing.T) { + service := newService(map[string]string{token: email}) + server := newServer(service) + defer server.Close() + + cases := map[string]struct { + path string + auth string + assertionFunctions func(t *testing.T, response http.Response, err error) + }{ + "list authentication types": { + path: "features/authenticationtypes", + auth: token, + assertionFunctions: func(t *testing.T, response http.Response, err error) { + require.NoError(t, err, "must not error") + assert.Equal(t, 200, response.StatusCode, "expected OK Status code") + res := response.Body + body, err := io.ReadAll(res) + require.NoError(t, err, "must not error") + var authResponse sinkAuthTypesRes + err = json.Unmarshal(body, &authResponse) + require.NoError(t, err, "must not error") + require.NotNil(t, authResponse, "response must not be nil") + require.Equal(t, 1, len(authResponse.AuthenticationTypes), "must contain basicauth for now") + }, + }, + "view authentication type basicauth": { + path: "features/authenticationtypes/basicauth", + auth: token, + assertionFunctions: func(t *testing.T, response http.Response, err error) { + require.NoError(t, err, "must not error") + assert.Equal(t, 200, response.StatusCode, "expected OK Status code") + res := response.Body + body, err := io.ReadAll(res) + require.NoError(t, err, "must not error") + var authResponse sinkAuthTypeRes + err = json.Unmarshal(body, &authResponse) + require.NoError(t, err, "must not error") + require.NotNil(t, authResponse, "response must not be nil") + meta := authResponse.AuthenticationTypes.(map[string]interface{}) + require.Equal(t, "basicauth", meta["type"], "must contain basicauth for now") + }, + }, + } + + for desc, tc := range cases { + t.Run(desc, func(t *testing.T) { + req := testRequest{ + client: server.Client(), + method: http.MethodGet, + url: fmt.Sprintf("%s/%s", server.URL, tc.path), + token: fmt.Sprintf("Bearer %s", tc.auth), + } + + res, err := req.make() + tc.assertionFunctions(t, *res, err) + }) + } +} + func TestViewBackend(t *testing.T) { service := newService(map[string]string{token: email}) server := newServer(service) @@ -706,7 +971,6 @@ func TestViewBackend(t *testing.T) { assert.Equal(t, tc.res, data, fmt.Sprintf("%s: expected body %s got %s", desc, tc.res, data)) }) } - } func TestViewBackends(t *testing.T) { @@ -762,11 +1026,28 @@ func TestViewBackends(t *testing.T) { } res, err := req.make() assert.Nil(t, err, fmt.Sprintf("unexpected error %s", err)) - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) assert.Nil(t, err, fmt.Sprintf("unexpected error %s", err)) - data := strings.Trim(string(body), "\n") assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", desc, tc.status, res.StatusCode)) - assert.Equal(t, tc.res, data, fmt.Sprintf("%s: expected body %s got %s", desc, tc.res, data)) + var response sinksBackendsRes + err = json.Unmarshal(body, &response) + assert.Nil(t, err, fmt.Sprintf("unexpected error %s", err)) + assert.NotNil(t, response, fmt.Sprintf("%s: response should not be nil", desc)) + if res.StatusCode == http.StatusOK { + hasPromBe := false + hasOtlphttpBe := false + for _, backendObj := range response.Backends { + if v, ok := backendObj.(map[string]interface{})["backend"]; ok { + if v == "prometheus" { + hasPromBe = true + } else if v == "otlphttp" { + hasOtlphttpBe = true + } + } + } + assert.True(t, hasPromBe, fmt.Sprintf("%s: expected prometheus backend", desc)) + assert.True(t, hasOtlphttpBe, fmt.Sprintf("%s: expected otlphttp backend", desc)) + } }) } @@ -776,18 +1057,35 @@ func TestViewSink(t *testing.T) { service := newService(map[string]string{token: email}) server := newServer(service) defer server.Close() - + nameID, _ := types.NewIdentifier("my-sink") + description := "An example prometheus sink" + sink := sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, + } sk, err := service.CreateSink(context.Background(), token, sink) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) sinkBE := backend.GetBackend("prometheus") - omitedConfig, _ := omitSecretInformation(sinkBE, sk.Format, sk.Config) + sinkAuthType, _ := authentication_type.GetAuthType("basicauth") + cfg := sinks.Configuration{ + Exporter: sinkBE, + Authentication: sinkAuthType, + } + omittedSink, _ := omitSecretInformation(&cfg, sink) require.NoError(t, err, "error during omitting secrets") data := toJSON(sinkRes{ ID: sk.ID, Name: sk.Name.String(), Description: *sk.Description, Backend: sk.Backend, - Config: omitedConfig, + Config: omittedSink.Config, + ConfigData: omittedSink.ConfigData, Tags: sk.Tags, State: sk.State.String(), Error: sk.Error, @@ -860,6 +1158,30 @@ func TestViewSink(t *testing.T) { } func TestDeleteSink(t *testing.T) { + nameID, _ := types.NewIdentifier("my-sink") + description := "An example prometheus sink" + sink := sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, + } + nameID, _ = types.NewIdentifier("my-sink") + description = "An example prometheus sink" + sink = sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, + } svc := newService(map[string]string{token: email}) server := newServer(svc) defer server.Close() @@ -1009,25 +1331,3 @@ func TestValidateSink(t *testing.T) { }) } } - -func TestOmitPasswords(t *testing.T) { - username := "387157" - cases := map[string]struct { - backend backend.Backend - inputMetadata types.Metadata - expectedMetadata types.Metadata - }{ - "omit configuration with password": { - backend: &prometheusbackend.Backend{}, - inputMetadata: types.Metadata{"username": &username, "password": "s3cr3tp@ssw0rd", "remote_host": "someUrl"}, - expectedMetadata: types.Metadata{"username": &username, "password": "", "remote_host": "someUrl"}, - }, - } - - for desc, tc := range cases { - t.Run(desc, func(t *testing.T) { - metadata, _ := omitSecretInformation(tc.backend, "yaml", tc.inputMetadata) - assert.Equal(t, tc.expectedMetadata, metadata) - }) - } -} diff --git a/sinks/api/http/logging.go b/sinks/api/http/logging.go index e420ef25e..42256b88c 100644 --- a/sinks/api/http/logging.go +++ b/sinks/api/http/logging.go @@ -7,6 +7,7 @@ package http import ( "context" "github.com/orb-community/orb/sinks" + "github.com/orb-community/orb/sinks/authentication_type" "github.com/orb-community/orb/sinks/backend" "go.uber.org/zap" "time" @@ -75,6 +76,20 @@ func (l loggingMiddleware) UpdateSink(ctx context.Context, token string, s sinks return l.svc.UpdateSink(ctx, token, s) } +func (l loggingMiddleware) UpdateSinkInternal(ctx context.Context, s sinks.Sink) (sink sinks.Sink, err error) { + defer func(begin time.Time) { + if err != nil { + l.logger.Warn("method call: edit_internal_sink", + zap.Error(err), + zap.Duration("duration", time.Since(begin))) + } else { + l.logger.Info("method call: edit_internal_sink", + zap.Duration("duration", time.Since(begin))) + } + }(time.Now()) + return l.svc.UpdateSinkInternal(ctx, s) +} + func (l loggingMiddleware) ListSinks(ctx context.Context, token string, pm sinks.PageMetadata) (_ sinks.Page, err error) { defer func(begin time.Time) { if err != nil { @@ -173,6 +188,14 @@ func (l loggingMiddleware) ValidateSink(ctx context.Context, token string, s sin return l.svc.ValidateSink(ctx, token, s) } +func (l loggingMiddleware) ListAuthenticationTypes(ctx context.Context, token string) ([]authentication_type.AuthenticationTypeConfig, error) { + return l.svc.ListAuthenticationTypes(ctx, token) +} + +func (l loggingMiddleware) ViewAuthenticationType(ctx context.Context, token string, key string) (authentication_type.AuthenticationTypeConfig, error) { + return l.svc.ViewAuthenticationType(ctx, token, key) +} + func (l loggingMiddleware) GetLogger() *zap.Logger { return l.logger } diff --git a/sinks/api/http/metrics.go b/sinks/api/http/metrics.go index 59bb96538..51bac1ee3 100644 --- a/sinks/api/http/metrics.go +++ b/sinks/api/http/metrics.go @@ -10,6 +10,7 @@ import ( "github.com/mainflux/mainflux" "github.com/orb-community/orb/pkg/errors" "github.com/orb-community/orb/sinks" + "github.com/orb-community/orb/sinks/authentication_type" "github.com/orb-community/orb/sinks/backend" "go.uber.org/zap" "time" @@ -45,6 +46,14 @@ func (m metricsMiddleware) ChangeSinkStateInternal(ctx context.Context, sinkID s return m.svc.ChangeSinkStateInternal(ctx, sinkID, msg, ownerID, state) } +func (m metricsMiddleware) ListAuthenticationTypes(ctx context.Context, token string) ([]authentication_type.AuthenticationTypeConfig, error) { + return m.svc.ListAuthenticationTypes(ctx, token) +} + +func (m metricsMiddleware) ViewAuthenticationType(ctx context.Context, token string, key string) (authentication_type.AuthenticationTypeConfig, error) { + return m.svc.ViewAuthenticationType(ctx, token, key) +} + func (m metricsMiddleware) CreateSink(ctx context.Context, token string, s sinks.Sink) (sink sinks.Sink, _ error) { ownerID, err := m.identify(token) if err != nil { @@ -82,6 +91,11 @@ func (m metricsMiddleware) UpdateSink(ctx context.Context, token string, s sinks return m.svc.UpdateSink(ctx, token, s) } +func (m metricsMiddleware) UpdateSinkInternal(ctx context.Context, s sinks.Sink) (sink sinks.Sink, err error) { + + return m.svc.UpdateSinkInternal(ctx, s) +} + func (m metricsMiddleware) ListSinks(ctx context.Context, token string, pm sinks.PageMetadata) (sink sinks.Page, err error) { ownerID, err := m.identify(token) if err != nil { diff --git a/sinks/api/http/openapi.yaml b/sinks/api/http/openapi.yaml index 35cd07b64..b3f627f8e 100644 --- a/sinks/api/http/openapi.yaml +++ b/sinks/api/http/openapi.yaml @@ -124,6 +124,39 @@ paths: $ref: '#/components/schemas/SinkBackendResSchema' '500': $ref: "#/components/responses/ServiceErrorRes" + /features/authenticationtypes: + get: + summary: 'List supported Sink Authentication types, based on OpenTelemetry Extensions' + operationId: sinkAuthTypes + tags: + - sink + parameters: + - $ref: "#/components/parameters/Authorization" + responses: + '200': + description: "Sink Authentication types listed" + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/SinkAuthTypeResSchema' + /features/authenticationtypes:{id}: + parameters: + - $ref: "#/components/parameters/Authorization" + - $ref: "#/components/parameters/AuthenticationTypeId" + get: + summary: "Get a specific AuthenticationType based on the simple name from OpenTelemetry extension" + operationId: getSinkAuthType + tags: + - sink + responses: + '200': + $ref: "#/components/responses/SinkAuthTypeObjRes" + '404': + description: A non-existent entity request. + '500': + $ref: "#/components/responses/ServiceErrorRes" /features/sinks/{id}: parameters: - $ref: "#/components/parameters/Authorization" @@ -167,31 +200,6 @@ paths: description: Database can't process request. '500': $ref: "#/components/responses/ServiceErrorRes" - /v2/sinks: - parameters: - - $ref: "#/components/parameters/Authorization" - post: - summary: 'Create a new Sink with YAML support' - operationId: createSinkExperimental - tags: - - sink - requestBody: - $ref: "#/components/requestBodies/SinkCreateReqV2" - responses: - '201': - $ref: "#/components/responses/SinkObjV2Res" - '400': - description: Failed due to malformed JSON. - '401': - description: Missing or invalid access token provided. - '409': - description: Entity already exist. - '415': - description: Missing or invalid content type. - '422': - description: Database can't process request. - '500': - $ref: "#/components/responses/ServiceErrorRes" components: securitySchemes: bearerAuth: @@ -287,6 +295,14 @@ components: type: http format: JWT required: true + AuthenticationTypeId: + name: id + description: Unique Sink auth identifier. + in: path + schema: + type: string + format: string + required: true SinkId: name: id description: Unique Sink identifier. @@ -329,6 +345,12 @@ components: schema: type: string format: byte + SinkAuthTypeObjRes: + description: Authentication object. + content: + application/json: + schema: + $ref: "#/components/schemas/SinkAuthTypeResSchema" SinkBackendObjRes: description: Backend object content: @@ -355,8 +377,10 @@ components: config: type: object example: - remote_host: my.prometheus-host.com - username: dbuser + exporter: + remote_host: my.prometheus-host.com + authentication: + username: dbuser description: Object representing backend specific configuration information SinkCreateReqSchema: @@ -386,8 +410,10 @@ components: config: type: object example: - remote_host: my.prometheus-host.com - username: dbuser + exporter: + remote_host: my.prometheus-host.com + authentication: + username: dbuser description: Object representing backend specific configuration information SinkCreateReqV2Schema: type: object @@ -507,8 +533,10 @@ components: config: type: object example: - remote_host: my.prometheus-host.com - username: dbuser + exporter: + remote_host: my.prometheus-host.com + authentication: + username: dbuser opentelemetry: enabled description: Object representing backend specific configuration information ts_created: @@ -553,12 +581,16 @@ components: readOnly: true example: prometheus description: The sink backend to use. Must match a backend from /features/sinks. Cannot change once created. - config: + config_data: type: string example: - remote_host: my.prometheus-host.com - username: dbuser - opentelemetry: enabled + exporter: + remote_host: my.prometheus-host.com + authentication: + exporter: + remote_host: my.prometheus-host.com + authentication: + username: dbuser description: YAML representing backend specific configuration information ts_created: type: string @@ -598,3 +630,20 @@ components: items: items: $ref: '#/components/schemas/ConfigEntrySchema' + SinkAuthTypeResSchema: + required: + - id + properties: + backend: + type: string + example: basicauth + description: Name and identifier of the sink authentication type, used when creating new sinks + description: + type: string + example: Basic Authentication over HTTPS + config: + type: array + description: Backend configuration field details + items: + items: + $ref: '#/components/schemas/ConfigEntrySchema' \ No newline at end of file diff --git a/sinks/api/http/requests.go b/sinks/api/http/requests.go index f9f5bfda0..df5f336a0 100644 --- a/sinks/api/http/requests.go +++ b/sinks/api/http/requests.go @@ -12,7 +12,10 @@ import ( "github.com/orb-community/orb/pkg/errors" "github.com/orb-community/orb/pkg/types" "github.com/orb-community/orb/sinks" + "github.com/orb-community/orb/sinks/authentication_type" + "github.com/orb-community/orb/sinks/authentication_type/basicauth" "github.com/orb-community/orb/sinks/backend" + "gopkg.in/yaml.v3" ) const ( @@ -35,44 +38,113 @@ type addReq struct { token string } -func (req addReq) validate() (err error) { - if req.token == "" { - return errors.ErrUnauthorizedAccess +func GetConfigurationAndMetadataFromMeta(backendName string, config types.Metadata) (configSvc *sinks.Configuration, exporter types.Metadata, authentication types.Metadata, err error) { + + if !backend.HaveBackend(backendName) { + return nil, nil, nil, errors.Wrap(errors.ErrInvalidBackend, errors.New("invalid backend")) + } + + if config == nil { + return nil, nil, nil, errors.Wrap(errors.ErrConfigFieldNotFound, errors.New("backend must not be nil")) } - if req.Backend == "" || !backend.HaveBackend(req.Backend) { - return errors.Wrap(errors.ErrMalformedEntity, errors.New("backend not found")) + configSvc = &sinks.Configuration{ + Exporter: backend.GetBackend(backendName), + } + exporter = config.GetSubMetadata("exporter") + if exporter == nil { + return nil, nil, nil, errors.Wrap(errors.ErrExporterFieldNotFound, errors.New("exporter field must not be nil")) + } + err = configSvc.Exporter.ValidateConfiguration(exporter) + if err != nil { + return } - reqBackend := backend.GetBackend(req.Backend) - if req.ConfigData == "" && req.Config == nil { - return errors.Wrap(errors.ErrMalformedEntity, errors.New("config not found")) + authentication = config.GetSubMetadata(authentication_type.AuthenticationKey) + if authentication == nil { + return nil, nil, nil, errors.Wrap(errors.ErrAuthFieldNotFound, errors.New("authentication field must not be nil")) + } + authtype, ok := authentication["type"] + if !ok { + authtype = basicauth.AuthType } + switch authtype.(type) { + case string: + break + default: + return + } + authTypeSvc, ok := authentication_type.GetAuthType(authtype.(string)) + if !ok { + err = errors.Wrap(errors.ErrInvalidAuthType, errors.New("invalid required field authentication type")) + return + } + configSvc.Authentication = authTypeSvc + err = configSvc.Authentication.ValidateConfiguration("object", authentication) + return +} - var config types.Metadata - if req.Format != "" { - config, err = reqBackend.ParseConfig(req.Format, req.ConfigData) - if err != nil { - return errors.Wrap(errors.ErrMalformedEntity, errors.New("invalid config")) - } - } else { - config = req.Config +func GetConfigurationAndMetadataFromYaml(backendName string, config string) (configSvc *sinks.Configuration, exporter types.Metadata, authentication types.Metadata, err error) { + + if !backend.HaveBackend(backendName) { + return nil, nil, nil, errors.Wrap(errors.ErrInvalidBackend, errors.New("invalid backend")) } - err = reqBackend.ValidateConfiguration(config) + configSvc = &sinks.Configuration{ + Exporter: backend.GetBackend(backendName), + } + var configStr types.Metadata + err = yaml.Unmarshal([]byte(config), &configStr) + if err != nil { + return + } + exporter = configStr.GetSubMetadata("exporter") + if exporter == nil { + return nil, nil, nil, errors.New("malformed entity specification. exporter field is expected on configuration field") + } + err = configSvc.Exporter.ValidateConfiguration(exporter) if err != nil { - return errors.Wrap(errors.ErrMalformedEntity, errors.New("invalid config")) + return + } + + authentication = configStr.GetSubMetadata(authentication_type.AuthenticationKey) + if authentication == nil { + return nil, nil, nil, errors.New("malformed entity specification. authentication fields are expected on configuration field") + } + authtype, ok := authentication["type"] + if !ok { + authtype = basicauth.AuthType + } + switch authtype.(type) { + case string: + break + default: + err = errors.ErrInvalidAuthType + return + } + authTypeSvc, ok := authentication_type.GetAuthType(authtype.(string)) + if !ok { + err = errors.Wrap(errors.ErrInvalidAuthType, errors.New("invalid required field authentication type")) + return + } + configSvc.Authentication = authTypeSvc + err = configSvc.Authentication.ValidateConfiguration("object", authentication) + return +} + +func (req addReq) validate() (err error) { + if req.token == "" { + return errors.ErrUnauthorizedAccess } if req.Name == "" { - return errors.Wrap(errors.ErrMalformedEntity, errors.New("name not found")) + return errors.Wrap(errors.ErrEntityNameNotFound, errors.New("name not found")) } _, err = types.NewIdentifier(req.Name) if err != nil { - return errors.Wrap(errors.ErrMalformedEntity, errors.New("identifier duplicated")) + return errors.Wrap(errors.ErrConflict, errors.New("identifier duplicated")) } - return nil } @@ -88,7 +160,7 @@ type updateSinkReq struct { token string } -func (req updateSinkReq) validate(sinkBackend backend.Backend) error { +func (req updateSinkReq) validate() error { if req.token == "" { return errors.ErrUnauthorizedAccess } @@ -97,23 +169,6 @@ func (req updateSinkReq) validate(sinkBackend backend.Backend) error { return errors.ErrMalformedEntity } - if req.ConfigData != "" || req.Config != nil { - var config types.Metadata - var err error - if req.Format != "" { - config, err = sinkBackend.ParseConfig(req.Format, req.ConfigData) - if err != nil { - return errors.Wrap(errors.ErrMalformedEntity, err) - } - } else { - config = req.Config - } - err = sinkBackend.ValidateConfiguration(config) - if err != nil { - return errors.Wrap(errors.ErrMalformedEntity, err) - } - } - if req.Description == nil && req.Name == "" && req.ConfigData == "" && len(req.Config) == 0 && req.Tags == nil { return errors.ErrMalformedEntity } @@ -182,6 +237,17 @@ func (req *listBackendsReq) validate() error { return nil } +type listAuthTypesReq struct { + token string +} + +func (req *listAuthTypesReq) validate() error { + if req.token == "" { + return errors.ErrUnauthorizedAccess + } + return nil +} + type deleteSinkReq struct { token string id string diff --git a/sinks/api/http/requests_test.go b/sinks/api/http/requests_test.go index 9c6c82780..48dac235b 100644 --- a/sinks/api/http/requests_test.go +++ b/sinks/api/http/requests_test.go @@ -128,7 +128,7 @@ func Test_updateSinkReq_validate(t *testing.T) { id: tt.fields.id, token: tt.fields.token, } - tt.wantErr(t, req.validate(&promBe), fmt.Sprintf("validate(%v)", promBe)) + tt.wantErr(t, req.validate(), fmt.Sprintf("validate(%v)", promBe)) }) } } diff --git a/sinks/api/http/responses.go b/sinks/api/http/responses.go index 627f020ab..677bc7ab4 100644 --- a/sinks/api/http/responses.go +++ b/sinks/api/http/responses.go @@ -6,6 +6,7 @@ package http import ( "github.com/orb-community/orb/pkg/types" + "github.com/orb-community/orb/sinks/authentication_type" "net/http" "time" ) @@ -66,6 +67,38 @@ type pageRes struct { Dir string `json:"direction"` } +type sinkAuthTypeRes struct { + AuthenticationTypes interface{} `json:"authentication_types,omitempty"` +} + +func (s sinkAuthTypeRes) Code() int { + return http.StatusOK +} + +func (s sinkAuthTypeRes) Headers() map[string]string { + return map[string]string{} +} + +func (s sinkAuthTypeRes) Empty() bool { + return false +} + +type sinkAuthTypesRes struct { + AuthenticationTypes []authentication_type.AuthenticationTypeConfig `json:"authentication_types,omitempty"` +} + +func (s sinkAuthTypesRes) Code() int { + return http.StatusOK +} + +func (s sinkAuthTypesRes) Headers() map[string]string { + return map[string]string{} +} + +func (s sinkAuthTypesRes) Empty() bool { + return false +} + type sinksBackendsRes struct { Backends []interface{} `json:"backends,omitempty"` } diff --git a/sinks/api/http/transport.go b/sinks/api/http/transport.go index 3a364926c..8f0277f76 100644 --- a/sinks/api/http/transport.go +++ b/sinks/api/http/transport.go @@ -7,6 +7,10 @@ package http import ( "context" "encoding/json" + "io" + "net/http" + "strings" + kitot "github.com/go-kit/kit/tracing/opentracing" kithttp "github.com/go-kit/kit/transport/http" "github.com/go-zoo/bone" @@ -18,9 +22,6 @@ import ( "github.com/orb-community/orb/pkg/types" "github.com/orb-community/orb/sinks" "github.com/prometheus/client_golang/prometheus/promhttp" - "io" - "net/http" - "strings" ) const ( @@ -76,6 +77,18 @@ func MakeHandler(tracer opentracing.Tracer, svcName string, svc sinks.SinkServic types.EncodeResponse, opts..., )) + r.Get("/features/authenticationtypes", kithttp.NewServer( + kitot.TraceServer(tracer, "list_authentication_types")(listAuthenticationTypes(svc)), + decodeListBackends, + types.EncodeResponse, + opts..., + )) + r.Get("/features/authenticationtypes/:id", kithttp.NewServer( + kitot.TraceServer(tracer, "view_authentication_type")(viewAuthenticationType(svc)), + decodeView, + types.EncodeResponse, + opts..., + )) r.Get("/sinks/:id", kithttp.NewServer( kitot.TraceServer(tracer, "view_sink")(viewSinkEndpoint(svc)), decodeView, @@ -104,7 +117,6 @@ func decodeAddRequest(_ context.Context, r *http.Request) (interface{}, error) { if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return nil, errors.Wrap(errors.ErrMalformedEntity, err) } - return req, nil } @@ -224,6 +236,35 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) { case errors.Contains(errorVal, errors.ErrUnsupportedContentType): w.WriteHeader(http.StatusUnsupportedMediaType) + + case errors.Contains(errorVal, errors.ErrInvalidEndpoint): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrEndpointNotFound): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrBackendNotFound): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrPasswordNotFound): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrAuthTypeNotFound): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrInvalidUsernameType): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrInvalidPasswordType): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrInvalidAuthType): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrRemoteHostNotFound): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrAuthFieldNotFound): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrConfigFieldNotFound): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrExporterFieldNotFound): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrInvalidBackend): + w.WriteHeader(http.StatusBadRequest) + case errors.Contains(errorVal, errors.ErrEntityNameNotFound): + w.WriteHeader(http.StatusBadRequest) case errors.Contains(errorVal, errors.ErrMalformedEntity): w.WriteHeader(http.StatusBadRequest) case errors.Contains(errorVal, errors.ErrNotFound): diff --git a/sinks/authentication_type/authentications.go b/sinks/authentication_type/authentications.go new file mode 100644 index 000000000..71def7ff7 --- /dev/null +++ b/sinks/authentication_type/authentications.go @@ -0,0 +1,46 @@ +package authentication_type + +type AuthenticationType interface { + Metadata() AuthenticationTypeConfig + GetFeatureConfig() []ConfigFeature + ValidateConfiguration(inputFormat string, input interface{}) error + ConfigToFormat(outputFormat string, input interface{}) (interface{}, error) + OmitInformation(outputFormat string, input interface{}) (interface{}, error) + EncodeInformation(outputFormat string, input interface{}) (interface{}, error) + DecodeInformation(outputFormat string, input interface{}) (interface{}, error) +} + +const AuthenticationKey = "authentication" + +type ConfigFeature struct { + Type string `json:"type"` + Input string `json:"input"` + Title string `json:"title"` + Name string `json:"name"` + Required bool `json:"required"` +} + +type AuthenticationTypeConfig struct { + Type string `json:"type"` + Description string `json:"description"` + Config []ConfigFeature `json:"config"` +} + +var authTypes = make(map[string]AuthenticationType) + +func Register(name string, authenticationType AuthenticationType) { + authTypes[name] = authenticationType +} + +func GetList() []AuthenticationTypeConfig { + keys := make([]AuthenticationTypeConfig, 0, len(authTypes)) + for _, v := range authTypes { + keys = append(keys, v.Metadata()) + } + return keys +} + +func GetAuthType(id string) (AuthenticationType, bool) { + v, ok := authTypes[id] + return v, ok +} diff --git a/sinks/authentication_type/basicauth/authentication.go b/sinks/authentication_type/basicauth/authentication.go new file mode 100644 index 000000000..71de2fa66 --- /dev/null +++ b/sinks/authentication_type/basicauth/authentication.go @@ -0,0 +1,245 @@ +package basicauth + +import ( + "github.com/orb-community/orb/pkg/errors" + "github.com/orb-community/orb/pkg/types" + "github.com/orb-community/orb/sinks/authentication_type" + "github.com/orb-community/orb/sinks/backend" + "gopkg.in/yaml.v3" +) + +const ( + UsernameConfigFeature = "username" + PasswordConfigFeature = "password" +) + +var ( + features = []authentication_type.ConfigFeature{ + { + Type: backend.ConfigFeatureTypeText, + Input: "text", + Title: "Username", + Name: UsernameConfigFeature, + Required: true, + }, + { + Type: backend.ConfigFeatureTypePassword, + Input: "text", + Title: "Password", + Name: PasswordConfigFeature, + Required: true, + }, + } +) + +const AuthType = "basicauth" + +type AuthConfig struct { + Username string `json:"username" ,yaml:"username"` + Password string `json:"password" ,yaml:"password"` + encryptionService authentication_type.PasswordService +} + +func (a *AuthConfig) Metadata() authentication_type.AuthenticationTypeConfig { + + return authentication_type.AuthenticationTypeConfig{ + Type: AuthType, + Description: "Basic username and password authentication", + Config: features, + } +} + +func (a *AuthConfig) GetFeatureConfig() []authentication_type.ConfigFeature { + return features +} + +func (a *AuthConfig) ValidateConfiguration(inputFormat string, input interface{}) error { + switch inputFormat { + case "object": + for key, value := range input.(types.Metadata) { + if _, ok := value.(string); !ok { + if key == "password" { + return errors.Wrap(errors.ErrInvalidPasswordType, errors.New("invalid auth type for field: " + key)) + } + if key == "type" { + return errors.Wrap(errors.ErrInvalidAuthType, errors.New("invalid auth type for field: " + key)) + } + if key == "username" { + return errors.Wrap(errors.ErrInvalidUsernameType, errors.New("invalid auth type for field: " + key)) + } + } + vs := value.(string) + if key == UsernameConfigFeature { + if len(vs) == 0 { + return errors.New("username cannot be empty") + } + } + if key == PasswordConfigFeature { + if len(vs) == 0 { + return errors.New("password cannot be empty") + } + } + } + case "yaml": + err := yaml.Unmarshal([]byte(input.(string)), &a) + if err != nil { + return err + } + if len(a.Username) == 0 { + return errors.New("username cannot be empty") + } else if len(a.Password) == 0 { + return errors.New("password cannot be empty") + } + } + return nil +} + +func (a *AuthConfig) ConfigToFormat(outputFormat string, input interface{}) (interface{}, error) { + switch input.(type) { + case types.Metadata: + if outputFormat == "yaml" { + retVal, err := yaml.Marshal(input) + return string(retVal), err + } else { + return nil, errors.New("unsupported format") + } + case string: + if outputFormat == "object" { + retVal := make(types.Metadata) + val := input.(string) + err := yaml.Unmarshal([]byte(val), &retVal) + return retVal, err + } else { + return nil, errors.New("unsupported format") + } + } + return nil, errors.New("unsupported format") +} + +func (a *AuthConfig) OmitInformation(outputFormat string, input interface{}) (interface{}, error) { + switch input.(type) { + case types.Metadata: + inputMeta := input.(types.Metadata) + authMeta := inputMeta.GetSubMetadata(authentication_type.AuthenticationKey) + authMeta[PasswordConfigFeature] = "" + inputMeta[authentication_type.AuthenticationKey] = authMeta + if outputFormat == "yaml" { + return a.ConfigToFormat("yaml", inputMeta) + } else if outputFormat == "object" { + return inputMeta, nil + } else { + return nil, errors.New("unsupported format") + } + case string: + iia, err := a.ConfigToFormat("object", input) + if err != nil { + return nil, err + } + inputMeta := iia.(types.Metadata) + authMeta := inputMeta.GetSubMetadata(authentication_type.AuthenticationKey) + authMeta[PasswordConfigFeature] = "" + inputMeta[authentication_type.AuthenticationKey] = authMeta + if outputFormat == "yaml" { + return a.ConfigToFormat("yaml", inputMeta) + } else if outputFormat == "object" { + return inputMeta, nil + } else { + return nil, errors.New("unsupported format") + } + } + return nil, errors.New("unsupported format") +} + +func (a *AuthConfig) EncodeInformation(outputFormat string, input interface{}) (interface{}, error) { + switch input.(type) { + case types.Metadata: + inputMeta := input.(types.Metadata) + authMeta := inputMeta.GetSubMetadata(authentication_type.AuthenticationKey) + if _, ok := authMeta[PasswordConfigFeature].(string); !ok { + return nil, errors.Wrap(errors.ErrPasswordNotFound, errors.New("password field was not found")) + } + encoded, err := a.encryptionService.EncodePassword(authMeta[PasswordConfigFeature].(string)) + if err != nil { + return nil, err + } + authMeta[PasswordConfigFeature] = encoded + inputMeta[authentication_type.AuthenticationKey] = authMeta + if outputFormat == "yaml" { + return a.ConfigToFormat("yaml", inputMeta) + } else if outputFormat == "object" { + return inputMeta, nil + } else { + return nil, errors.New("unsupported format") + } + case string: + iia, err := a.ConfigToFormat("object", input) + if err != nil { + return nil, err + } + inputMeta := iia.(types.Metadata) + authMeta := inputMeta.GetSubMetadata(authentication_type.AuthenticationKey) + encoded, err := a.encryptionService.EncodePassword(authMeta[PasswordConfigFeature].(string)) + if err != nil { + return nil, err + } + authMeta[PasswordConfigFeature] = encoded + inputMeta[authentication_type.AuthenticationKey] = authMeta + if outputFormat == "yaml" { + return a.ConfigToFormat("yaml", inputMeta) + } else if outputFormat == "object" { + return inputMeta, nil + } else { + return nil, errors.New("unsupported format") + } + } + return nil, errors.New("unsupported format") +} + +func (a *AuthConfig) DecodeInformation(outputFormat string, input interface{}) (interface{}, error) { + switch input.(type) { + case types.Metadata: + inputMeta := input.(types.Metadata) + authMeta := inputMeta.GetSubMetadata(authentication_type.AuthenticationKey) + decoded, err := a.encryptionService.DecodePassword(authMeta[PasswordConfigFeature].(string)) + if err != nil { + return nil, err + } + authMeta[PasswordConfigFeature] = decoded + inputMeta[authentication_type.AuthenticationKey] = authMeta + if outputFormat == "yaml" { + return a.ConfigToFormat("yaml", inputMeta) + } else if outputFormat == "object" { + return inputMeta, nil + } else { + return nil, errors.New("unsupported format") + } + case string: + iia, err := a.ConfigToFormat("object", input) + if err != nil { + return nil, err + } + inputMeta := iia.(types.Metadata) + authMeta := inputMeta.GetSubMetadata(authentication_type.AuthenticationKey) + decoded, err := a.encryptionService.DecodePassword(authMeta[PasswordConfigFeature].(string)) + if err != nil { + return nil, err + } + authMeta[PasswordConfigFeature] = decoded + inputMeta[authentication_type.AuthenticationKey] = authMeta + if outputFormat == "yaml" { + return a.ConfigToFormat("yaml", inputMeta) + } else if outputFormat == "object" { + return inputMeta, nil + } else { + return nil, errors.New("unsupported format") + } + } + return nil, errors.New("unsupported format") +} + +func Register(encryptionService authentication_type.PasswordService) { + basicAuth := AuthConfig{ + encryptionService: encryptionService, + } + authentication_type.Register(AuthType, &basicAuth) +} diff --git a/sinks/password.go b/sinks/authentication_type/password.go similarity index 98% rename from sinks/password.go rename to sinks/authentication_type/password.go index 774e2657a..ff1d77c17 100644 --- a/sinks/password.go +++ b/sinks/authentication_type/password.go @@ -1,4 +1,4 @@ -package sinks +package authentication_type import ( "crypto/aes" diff --git a/sinks/password_test.go b/sinks/authentication_type/password_test.go similarity index 98% rename from sinks/password_test.go rename to sinks/authentication_type/password_test.go index 5f74c96ac..7d9d064fe 100644 --- a/sinks/password_test.go +++ b/sinks/authentication_type/password_test.go @@ -1,4 +1,4 @@ -package sinks +package authentication_type import ( "github.com/stretchr/testify/assert" diff --git a/sinks/backend/backend.go b/sinks/backend/backend.go index 0683fe8f1..ffa153124 100644 --- a/sinks/backend/backend.go +++ b/sinks/backend/backend.go @@ -4,7 +4,9 @@ package backend -import "github.com/orb-community/orb/pkg/types" +import ( + "github.com/orb-community/orb/pkg/types" +) type Backend interface { Metadata() interface{} @@ -25,6 +27,12 @@ type ConfigFeature struct { Required bool `json:"required"` } +type SinkFeature struct { + Backend string `json:"backend"` + Description string `json:"description"` + Config []ConfigFeature `json:"config"` +} + var registry = make(map[string]Backend) func Register(name string, b Backend) { diff --git a/sinks/backend/otlphttpexporter/configuration.go b/sinks/backend/otlphttpexporter/configuration.go new file mode 100644 index 000000000..48eec15f3 --- /dev/null +++ b/sinks/backend/otlphttpexporter/configuration.go @@ -0,0 +1,133 @@ +package otlphttpexporter + +import ( + "net/url" + + "github.com/orb-community/orb/pkg/errors" + "github.com/orb-community/orb/pkg/types" + "github.com/orb-community/orb/sinks/backend" + "gopkg.in/yaml.v3" +) + +// OTLP Exporter Examples +// exporters: +// otlp: +// endpoint: myserver.local:55690 +// tls: +// insecure: false +// ca_file: server.crt +// cert_file: client.crt +// key_file: client.key +// min_version: "1.1" +// max_version: "1.2" +// otlp/insecure: +// endpoint: myserver.local:55690 +// tls: +// insecure: true +// otlp/secure_no_verify: +// endpoint: myserver.local:55690 +// tls: +// insecure: false +// insecure_skip_verify: true + +const EndpointFieldName = "endpoint" +const CustomHeadersConfigFeature = "headers" + +var invalidCustomHeaders = []string{ + "Content-Encoding", "Content-Type", "User-Agent", "Authorization", +} + +type OTLPHTTPBackend struct { + Endpoint string `yaml:"endpoint"` + //TODO will keep TLS until we confirm there is no need for those + //Tls *tlsConfig `yaml:"tls,omitempty,flow"` +} + +func (b *OTLPHTTPBackend) Metadata() interface{} { + return backend.SinkFeature{ + Backend: "otlphttp", + Description: "OTLP Exporter over HTTP", + Config: b.CreateFeatureConfig(), + } +} + +// TODO will keep TLS until we confirm there is no need for those +type tlsConfig struct { + Insecure *bool `yaml:"insecure,omitempty"` + CaFile *string `yaml:"ca_file,omitempty"` + CertFile *string `yaml:"cert_file,omitempty"` + KeyFile *string `yaml:"key_file,omitempty"` + MinVersion *string `yaml:"min_version,omitempty"` + MaxVersion *string `yaml:"max_version,omitempty"` + InsecureSkipVerify *bool `yaml:"insecure_skip_verify,omitempty"` +} + +func Register() bool { + backend.Register("otlphttp", &OTLPHTTPBackend{}) + return true +} + +// CreateFeatureConfig Not available since this is only supported in YAML configuration +func (b *OTLPHTTPBackend) CreateFeatureConfig() []backend.ConfigFeature { + var configs []backend.ConfigFeature + + remoteHost := backend.ConfigFeature{ + Type: backend.ConfigFeatureTypeText, + Input: "text", + Title: "Remote Write URL", + Name: EndpointFieldName, + Required: true, + } + + configs = append(configs, remoteHost) + return configs +} + +func (b *OTLPHTTPBackend) ValidateConfiguration(config types.Metadata) error { + if config[EndpointFieldName] == "" { + return errors.New("malformed entity specification. endpoint must not be empty") + } + endpointUrl, endpointOk := config[EndpointFieldName] + if !endpointOk { + return errors.Wrap(errors.ErrEndpointNotFound, errors.New("endpoint not found")) + } + if _, err := url.ParseRequestURI(endpointUrl.(string)); err != nil { + return errors.Wrap(errors.ErrInvalidEndpoint, err) + } + // check for custom http headers + customHeaders, customHeadersOk := config[CustomHeadersConfigFeature] + if customHeadersOk { + headersAsMap := customHeaders.(map[string]interface{}) + for _, header := range invalidCustomHeaders { + if _, ok := headersAsMap[header]; ok { + return errors.New("invalid custom headers") + } + } + } + return nil +} + +func (b *OTLPHTTPBackend) ParseConfig(format string, config string) (retConfig types.Metadata, err error) { + if format == "yaml" { + var parsedConfig OTLPHTTPBackend + err = yaml.Unmarshal([]byte(config), &parsedConfig) + if err != nil { + return nil, errors.Wrap(errors.New("failed to unmarshal config"), err) + } + retConfig = make(types.Metadata) + retConfig[EndpointFieldName] = parsedConfig.Endpoint + + } else { + return nil, errors.New("format not supported") + } + return +} + +func (b *OTLPHTTPBackend) ConfigToFormat(format string, metadata types.Metadata) (string, error) { + if format == "yaml" { + value, err := yaml.Marshal(metadata) + return string(value), err + } else { + return "", errors.New("format not supported") + } +} diff --git a/sinks/backend/prometheus/configuration.go b/sinks/backend/prometheus/configuration.go index 96c58832b..81ccaf499 100644 --- a/sinks/backend/prometheus/configuration.go +++ b/sinks/backend/prometheus/configuration.go @@ -1,23 +1,25 @@ package prometheus import ( + "net/url" + "github.com/orb-community/orb/pkg/errors" "github.com/orb-community/orb/pkg/types" "github.com/orb-community/orb/sinks/backend" - "golang.org/x/exp/maps" "gopkg.in/yaml.v3" - "net/url" ) +var invalidCustomHeaders = []string{ + "Content-Encoding", "Content-Type", "X-Prometheus-Remote-Write-Version", "User-Agent", "Authorization", +} + func (p *Backend) ConfigToFormat(format string, metadata types.Metadata) (string, error) { if format == "yaml" { - username := metadata[UsernameConfigFeature].(*string) - password := metadata[PasswordConfigFeature].(string) - parseUtil := configParseUtility{ - RemoteHost: metadata[RemoteHostURLConfigFeature].(string), - Username: username, - Password: &password, + remoteHost := metadata[RemoteHostURLConfigFeature].(string) + parseUtil := Backend{ + RemoteHost: remoteHost, } + p.RemoteHost = remoteHost config, err := yaml.Marshal(parseUtil) if err != nil { return "", err @@ -32,16 +34,11 @@ func (p *Backend) ParseConfig(format string, config string) (configReturn types. if format == "yaml" { configAsByte := []byte(config) // Parse the YAML data into a Config struct - var configUtil configParseUtility - err = yaml.Unmarshal(configAsByte, &configUtil) + configReturn = make(types.Metadata) + err = yaml.Unmarshal(configAsByte, &configReturn) if err != nil { return nil, errors.Wrap(errors.New("failed to parse config YAML"), err) } - configReturn = make(types.Metadata) - // Check for Token Auth - configReturn[RemoteHostURLConfigFeature] = configUtil.RemoteHost - configReturn[UsernameConfigFeature] = configUtil.Username - configReturn[PasswordConfigFeature] = configUtil.Password return } else { return nil, errors.New("unsupported format") @@ -49,31 +46,25 @@ func (p *Backend) ParseConfig(format string, config string) (configReturn types. } func (p *Backend) ValidateConfiguration(config types.Metadata) error { - authType := BasicAuth - for _, key := range maps.Keys(config) { - if key == ApiTokenConfigFeature { - authType = TokenAuth - break - } - } - switch authType { - case BasicAuth: - _, userOk := config[UsernameConfigFeature] - _, passwordOk := config[PasswordConfigFeature] - if !userOk || !passwordOk { - return errors.New("basic authentication, must provide username and password fields") - } - case TokenAuth: - return errors.New("not implemented yet") - } + remoteUrl, remoteHostOk := config[RemoteHostURLConfigFeature] if !remoteHostOk { - return errors.New("must send valid URL for Remote Write") + return errors.ErrRemoteHostNotFound } // Validate remote_host _, err := url.ParseRequestURI(remoteUrl.(string)) if err != nil { - return errors.New("must send valid URL for Remote Write") + return errors.ErrInvalidRemoteHost + } + // check for custom http headers + customHeaders, customHeadersOk := config[CustomHeadersConfigFeature] + if customHeadersOk { + headersAsMap := customHeaders.(map[string]interface{}) + for _, header := range invalidCustomHeaders { + if _, ok := headersAsMap[header]; ok { + return errors.New("invalid custom headers") + } + } } return nil } @@ -89,20 +80,6 @@ func (p *Backend) CreateFeatureConfig() []backend.ConfigFeature { Required: true, } - userName := backend.ConfigFeature{ - Type: backend.ConfigFeatureTypeText, - Input: "text", - Title: "Username", - Name: UsernameConfigFeature, - Required: true, - } - password := backend.ConfigFeature{ - Type: backend.ConfigFeatureTypePassword, - Input: "text", - Title: "Password", - Name: PasswordConfigFeature, - Required: true, - } - configs = append(configs, remoteHost, userName, password) + configs = append(configs, remoteHost) return configs } diff --git a/sinks/backend/prometheus/configuration_test.go b/sinks/backend/prometheus/configuration_test.go index 562c8eae9..a7b69e57f 100644 --- a/sinks/backend/prometheus/configuration_test.go +++ b/sinks/backend/prometheus/configuration_test.go @@ -2,13 +2,13 @@ package prometheus import ( "github.com/orb-community/orb/pkg/types" - "reflect" + "github.com/stretchr/testify/require" "testing" ) var ( - validConfiguration = map[string]interface{}{RemoteHostURLConfigFeature: "https://acme.com/prom/push", UsernameConfigFeature: "wile.e.coyote", PasswordConfigFeature: "@secr3t-passw0rd"} - validYaml = "remote_host: https://acme.com/prom/push\nusername: wile.e.coyote\npassword: \"@secr3t-passw0rd\"" + validConfiguration = map[string]interface{}{RemoteHostURLConfigFeature: "https://acme.com/prom/push"} + validYaml = "exporter:\n remote_host: https://acme.com/prom/push" ) func TestBackend_ValidateConfiguration(t *testing.T) { @@ -31,28 +31,14 @@ func TestBackend_ValidateConfiguration(t *testing.T) { { name: "invalid host configuration", args: args{ - config: map[string]interface{}{RemoteHostURLConfigFeature: "acme.com/prom/push", UsernameConfigFeature: "wile.e.coyote", PasswordConfigFeature: "@secr3t-passw0rd"}, + config: map[string]interface{}{RemoteHostURLConfigFeature: "acme.com/prom/push"}, }, wantErr: true, }, { name: "missing host configuration", args: args{ - config: map[string]interface{}{UsernameConfigFeature: "wile.e.coyote", PasswordConfigFeature: "@secr3t-passw0rd"}, - }, - wantErr: true, - }, - { - name: "missing username configuration", - args: args{ - config: map[string]interface{}{RemoteHostURLConfigFeature: "acme.com/prom/push", PasswordConfigFeature: "@secr3t-passw0rd"}, - }, - wantErr: true, - }, - { - name: "missing password configuration", - args: args{ - config: map[string]interface{}{RemoteHostURLConfigFeature: "acme.com/prom/push", UsernameConfigFeature: "wile.e.coyote"}, + config: map[string]interface{}{}, }, wantErr: true, }, @@ -72,8 +58,6 @@ func TestBackend_ParseConfig(t *testing.T) { format string config string } - pass := "@secr3t-passw0rd" - user := "wile.e.coyote" tests := []struct { name string args args @@ -86,16 +70,16 @@ func TestBackend_ParseConfig(t *testing.T) { format: "yaml", config: validYaml, }, - wantConfigReturn: map[string]interface{}{RemoteHostURLConfigFeature: "https://acme.com/prom/push", UsernameConfigFeature: &user, PasswordConfigFeature: &pass}, + wantConfigReturn: map[string]interface{}{"exporter": map[string]interface{}{RemoteHostURLConfigFeature: "https://acme.com/prom/push"}}, wantErr: false, }, { name: "invalid parse", args: args{ format: "yaml", - config: "remote_host: https://acme.com/prom/push\nusername: wile.e.coyote\npassword \"@secr3t-passw0rd\"", + config: "exporter:\n remote_host: \n https://acme.com/prom/push\n\n", }, - wantConfigReturn: map[string]interface{}{RemoteHostURLConfigFeature: "https://acme.com/prom/push", UsernameConfigFeature: &user, PasswordConfigFeature: &pass}, + wantConfigReturn: map[string]interface{}{"exporter": map[string]interface{}{RemoteHostURLConfigFeature: "https://acme.com/prom/push"}}, wantErr: true, }, } @@ -107,8 +91,8 @@ func TestBackend_ParseConfig(t *testing.T) { t.Errorf("ParseConfig() error = %v, wantErr %v", err, tt.wantErr) return } - if !tt.wantErr && !reflect.DeepEqual(gotConfigReturn, tt.wantConfigReturn) { - t.Errorf("ParseConfig() gotConfigReturn = %v, want %v", gotConfigReturn, tt.wantConfigReturn) + if !tt.wantErr { + require.Equal(t, tt.wantConfigReturn.GetSubMetadata("exporter")["remote_host"], gotConfigReturn.GetSubMetadata("exporter")["remote_host"]) } }) } @@ -124,23 +108,13 @@ func TestBackend_CreateFeatureConfig(t *testing.T) { t.Run(tt.name, func(t *testing.T) { p := &Backend{} got := p.CreateFeatureConfig() - usernameOk := false - passwordOk := false remoteHostOk := false for _, feature := range got { - if feature.Name == UsernameConfigFeature { - usernameOk = true - continue - } - if feature.Name == PasswordConfigFeature { - passwordOk = true - continue - } if feature.Name == RemoteHostURLConfigFeature { remoteHostOk = true } } - if usernameOk && passwordOk && remoteHostOk { + if remoteHostOk { return } else { t.Fail() diff --git a/sinks/backend/prometheus/type.go b/sinks/backend/prometheus/type.go index 96f837e2e..1f274d6ed 100644 --- a/sinks/backend/prometheus/type.go +++ b/sinks/backend/prometheus/type.go @@ -12,9 +12,8 @@ var _ backend.Backend = (*Backend)(nil) const ( RemoteHostURLConfigFeature = "remote_host" - UsernameConfigFeature = "username" - PasswordConfigFeature = "password" ApiTokenConfigFeature = "api_token" + CustomHeadersConfigFeature = "headers" ) //type PrometheusConfigMetadata = types.Metadata @@ -27,27 +26,11 @@ const ( ) type Backend struct { - apiHost string - apiPort uint64 - apiUser string - apiPassword string -} - -type configParseUtility struct { - RemoteHost string `yaml:"remote_host"` - Username *string `yaml:"username,omitempty"` - Password *string `yaml:"password,omitempty"` - APIToken *string `yaml:"api_token,omitempty"` -} - -type SinkFeature struct { - Backend string `json:"backend"` - Description string `json:"description"` - Config []backend.ConfigFeature `json:"config"` + RemoteHost string `json:"remote_host"` } func (p *Backend) Metadata() interface{} { - return SinkFeature{ + return backend.SinkFeature{ Backend: "prometheus", Description: "Prometheus time series database sink", Config: p.CreateFeatureConfig(), diff --git a/sinks/migrate/m1_upgrade_config.go b/sinks/migrate/m1_upgrade_config.go new file mode 100644 index 000000000..be7f9bd2f --- /dev/null +++ b/sinks/migrate/m1_upgrade_config.go @@ -0,0 +1,124 @@ +package migrate + +import ( + "context" + + "github.com/orb-community/orb/pkg/types" + "github.com/orb-community/orb/sinks" + "github.com/orb-community/orb/sinks/authentication_type" + "github.com/orb-community/orb/sinks/authentication_type/basicauth" + "go.uber.org/zap" +) + +type Plan1UpdateConfiguration struct { + logger *zap.Logger + service sinks.SinkService + sinkRepo sinks.SinkRepository + passwordService authentication_type.PasswordService +} + +func NewPlan1(logger *zap.Logger, service sinks.SinkService, sinkRepo sinks.SinkRepository, passwordService authentication_type.PasswordService) Plan { + return &Plan1UpdateConfiguration{ + logger: logger, + service: service, + sinkRepo: sinkRepo, + passwordService: passwordService, + } +} + +func (p *Plan1UpdateConfiguration) Version() string { + return "0.25.1" +} + +func (p *Plan1UpdateConfiguration) Up(ctx context.Context) (mainErr error) { + allSinks, mainErr := p.sinkRepo.SearchAllSinks(ctx, sinks.Filter{StateFilter: "", OpenTelemetry: "enabled"}) + if mainErr != nil { + p.logger.Error("could not list sinks", zap.Error(mainErr)) + return + } + needsUpdate := 0 + updated := 0 + for _, sink := range allSinks { + if _, ok := sink.Config[authentication_type.AuthenticationKey]; !ok { + needsUpdate++ + sinkRemoteHost, ok := sink.Config["remote_host"] + if !ok { + p.logger.Error("failed to update sink for lack of remote_host", zap.String("sinkID", sink.ID)) + sink.State = sinks.Error + sink.Error = "sink with invalid configuration, please update" + _, err := p.service.UpdateSinkInternal(ctx, sink) + if err != nil { + p.logger.Error("failed to update sink", + zap.String("sinkID", sink.ID), zap.Error(err)) + mainErr = err + continue + } + continue + } + sinkUsername, ok := sink.Config["username"] + if !ok { + p.logger.Error("failed to update sink for lack of username", zap.String("sinkID", sink.ID)) + sink.State = sinks.Error + sink.Error = "sink with invalid configuration, please update" + _, err := p.service.UpdateSinkInternal(ctx, sink) + if err != nil { + p.logger.Error("failed to update sink", + zap.String("sinkID", sink.ID), zap.Error(err)) + mainErr = err + continue + } + continue + } + encodedPassword, ok := sink.Config["password"] + if !ok { + p.logger.Error("failed to update sink for lack of password", zap.String("sinkID", sink.ID)) + sink.State = sinks.Error + sink.Error = "sink with invalid configuration, please update" + _, err := p.service.UpdateSinkInternal(ctx, sink) + if err != nil { + p.logger.Error("failed to update sink", + zap.String("sinkID", sink.ID), zap.Error(err)) + mainErr = err + continue + } + continue + } + decodedPassword, err := p.passwordService.DecodePassword(encodedPassword.(string)) + if err != nil { + p.logger.Error("failed to update sink for failure in decoding password", + zap.String("sinkID", sink.ID), zap.Error(err)) + sink.State = sinks.Error + sink.Error = "sink with invalid configuration, please update" + _, err := p.service.UpdateSinkInternal(ctx, sink) + if err != nil { + p.logger.Error("failed to update sink", + zap.String("sinkID", sink.ID), zap.Error(err)) + mainErr = err + continue + } + continue + } + newMetadata := types.Metadata{ + "authentication": types.Metadata{ + "type": basicauth.AuthType, + "username": sinkUsername.(string), + "password": decodedPassword, + }, + "exporter": types.Metadata{ + "remote_host": sinkRemoteHost.(string), + }, + } + sink.Config = newMetadata + _, err = p.service.UpdateSinkInternal(ctx, sink) + if err != nil { + p.logger.Error("failed to update sink", + zap.String("sinkID", sink.ID), zap.Error(err)) + mainErr = err + continue + } + updated++ + } + } + p.logger.Info("migration results", zap.Int("total_sinks", needsUpdate), zap.Int("updated_sinks", updated)) + return +} diff --git a/sinks/migrate/service.go b/sinks/migrate/service.go new file mode 100644 index 000000000..9232b5e40 --- /dev/null +++ b/sinks/migrate/service.go @@ -0,0 +1,85 @@ +package migrate + +import ( + "context" + "fmt" + "github.com/hashicorp/go-version" + "github.com/orb-community/orb/sinks" + "go.uber.org/zap" +) + +type Plan interface { + Version() string + Up(ctx context.Context) error +} + +type Service interface { + Migrate(plans ...Plan) error +} + +func NewService(logger *zap.Logger, sinkRepository sinks.SinkRepository) Service { + return &migrateService{ + logger: logger, + sinkRepository: sinkRepository, + } +} + +type migrateService struct { + logger *zap.Logger + sinkRepository sinks.SinkRepository +} + +func (m *migrateService) updateNewVersion(ctx context.Context, newVersion string) { + currentVersion, err := m.getCurrentVersion(ctx) + if err != nil { + m.logger.Error("Could not parse current version", zap.Error(err)) + return + } + incomingSemVer, err := version.NewSemver(newVersion) + if err != nil { + m.logger.Error("Could not parse version for plan", zap.String("version", newVersion), zap.Error(err)) + return + } + if incomingSemVer.GreaterThan(currentVersion) { + err := m.sinkRepository.UpdateVersion(ctx, newVersion) + if err != nil { + m.logger.Error("error during update of version", zap.String("newVersion", newVersion), zap.Error(err)) + return + } + } +} + +func (m *migrateService) getCurrentVersion(ctx context.Context) (*version.Version, error) { + currentVersion, err := m.sinkRepository.GetVersion(ctx) + if err != nil { + m.logger.Error("error fetching current version") + return nil, err + } + return version.NewSemver(currentVersion) +} + +func (m *migrateService) Migrate(plans ...Plan) error { + for i, plan := range plans { + planName := fmt.Sprintf("plan%d", i) + ctx := context.WithValue(context.Background(), "migrate", planName) + v, err := version.NewSemver(plan.Version()) + if err != nil { + m.logger.Error("Could not parse version for plan", zap.String("version", plan.Version()), zap.Error(err)) + } + curV, err := m.getCurrentVersion(ctx) + if err != nil { + m.logger.Error("could not find current version, version", zap.Error(err)) + return err + } + if v.GreaterThan(curV) { + m.logger.Info("Starting plan", zap.Int("plan", i)) + err := plan.Up(ctx) + if err != nil { + m.logger.Error("error during migrate service", zap.Error(err)) + return err + } + m.updateNewVersion(ctx, plan.Version()) + } + } + return nil +} diff --git a/sinks/mocks/sinks.go b/sinks/mocks/sinks.go index 0f195ef65..b3a0c165e 100644 --- a/sinks/mocks/sinks.go +++ b/sinks/mocks/sinks.go @@ -10,9 +10,12 @@ package mocks import ( "context" + "github.com/benbjohnson/immutable" "github.com/gofrs/uuid" "github.com/orb-community/orb/pkg/errors" + "github.com/orb-community/orb/pkg/types" "github.com/orb-community/orb/sinks" + "github.com/orb-community/orb/sinks/authentication_type" "reflect" "sync" ) @@ -21,10 +24,18 @@ var _ sinks.SinkRepository = (*sinkRepositoryMock)(nil) // Mock Repository type sinkRepositoryMock struct { - mu sync.Mutex - counter uint64 - sinksMock map[string]sinks.Sink - passwordService sinks.PasswordService + mu sync.Mutex + counter uint64 + passSvc authentication_type.PasswordService + sinksMock immutable.Map[string, sinks.Sink] +} + +func (s *sinkRepositoryMock) GetVersion(ctx context.Context) (string, error) { + return "", nil +} + +func (s *sinkRepositoryMock) UpdateVersion(ctx context.Context, version string) error { + return nil } func (s *sinkRepositoryMock) SearchAllSinks(ctx context.Context, filter sinks.Filter) ([]sinks.Sink, error) { @@ -39,9 +50,14 @@ func (s *sinkRepositoryMock) RetrieveByOwnerAndId(ctx context.Context, ownerID s s.mu.Lock() defer s.mu.Unlock() - if c, ok := s.sinksMock[key]; ok { - if s.sinksMock[key].MFOwnerID == ownerID { - return c, nil + if sink, ok := s.sinksMock.Get(key); ok { + if sink.MFOwnerID == ownerID { + // pass test code + v := sink.Config.GetSubMetadata(authentication_type.AuthenticationKey) + if v["password"] == "dbpass" { + v["password"], _ = s.passSvc.EncodePassword(v["password"].(string)) + } + return sink, nil } else { return sinks.Sink{}, sinks.ErrNotFound } @@ -50,10 +66,11 @@ func (s *sinkRepositoryMock) RetrieveByOwnerAndId(ctx context.Context, ownerID s return sinks.Sink{}, sinks.ErrNotFound } -func NewSinkRepository(service sinks.PasswordService) sinks.SinkRepository { +func NewSinkRepository(passSvc authentication_type.PasswordService) sinks.SinkRepository { + mocks := immutable.NewMap[string, sinks.Sink](nil) return &sinkRepositoryMock{ - sinksMock: make(map[string]sinks.Sink), - passwordService: service, + sinksMock: *mocks, + passSvc: passSvc, } } @@ -61,43 +78,65 @@ func (s *sinkRepositoryMock) Save(ctx context.Context, sink sinks.Sink) (string, s.mu.Lock() defer s.mu.Unlock() - for _, sk := range s.sinksMock { - if sk.Name == sink.Name { + itr := s.sinksMock.Iterator() + for !itr.Done() { + _, v, _ := itr.Next() + if v.Name == sink.Name { return "", sinks.ErrConflictSink } } - s.counter++ ID, _ := uuid.NewV4() sink.ID = ID.String() - s.sinksMock[sink.ID] = sink + // create a full copy of the Config, because somehow it changes after adding to map + configCopy := make(types.Metadata) + bkpConfig := sink.Config + copyMetadata(configCopy, sink.Config) + sink.Config = configCopy + s.sinksMock = *s.sinksMock.Set(sink.ID, sink) + sink.Config = bkpConfig return sink.ID, nil } func (s *sinkRepositoryMock) Update(ctx context.Context, sink sinks.Sink) (err error) { s.mu.Lock() defer s.mu.Unlock() - if _, ok := s.sinksMock[sink.ID]; ok { - if s.sinksMock[sink.ID].MFOwnerID != sink.MFOwnerID { + if c, ok := s.sinksMock.Get(sink.ID); ok { + if sink.MFOwnerID != c.MFOwnerID { return errors.ErrUpdateEntity } - s.sinksMock[sink.ID] = sink + // create a full copy of the Config, because somehow it changes after adding to map + configCopy := make(types.Metadata) + bkpConfig := sink.Config + copyMetadata(configCopy, sink.Config) + sink.Config = configCopy + s.sinksMock = *s.sinksMock.Set(sink.ID, sink) + sink.Config = bkpConfig return nil } return sinks.ErrNotFound } +func copyMetadata(dst, src types.Metadata) { + dv, sv := reflect.ValueOf(dst), reflect.ValueOf(src) + for _, k := range sv.MapKeys() { + dv.SetMapIndex(k, sv.MapIndex(k)) + } +} + func (s *sinkRepositoryMock) RetrieveAllByOwnerID(ctx context.Context, owner string, pm sinks.PageMetadata) (sinks.Page, error) { s.mu.Lock() defer s.mu.Unlock() first := uint64(pm.Offset) + 1 - last := first + uint64(pm.Limit) + last := first + pm.Limit var sks []sinks.Sink id := uint64(0) - for _, v := range s.sinksMock { + itr := s.sinksMock.Iterator() + for !itr.Done() { + _, v, _ := itr.Next() id++ if v.MFOwnerID == owner && id >= first && id < last { if reflect.DeepEqual(pm.Tags, v.Tags) || pm.Tags == nil { @@ -123,7 +162,12 @@ func (s *sinkRepositoryMock) RetrieveById(ctx context.Context, key string) (sink s.mu.Lock() defer s.mu.Unlock() - if c, ok := s.sinksMock[key]; ok { + if c, ok := s.sinksMock.Get(key); ok { + // Pass test schema + v := c.Config.GetSubMetadata(authentication_type.AuthenticationKey) + if v["password"] == "dbpass" { + v["password"], _ = s.passSvc.EncodePassword(v["password"].(string)) + } return c, nil } @@ -131,5 +175,13 @@ func (s *sinkRepositoryMock) RetrieveById(ctx context.Context, key string) (sink } func (s *sinkRepositoryMock) Remove(ctx context.Context, owner string, key string) error { + if c, ok := s.sinksMock.Get(key); ok { + if c.MFOwnerID == owner { + s.sinksMock = *s.sinksMock.Delete(key) + return nil + } else { + return sinks.ErrNotFound + } + } return nil } diff --git a/sinks/postgres/init.go b/sinks/postgres/init.go index 1b987b21d..cab3b5390 100644 --- a/sinks/postgres/init.go +++ b/sinks/postgres/init.go @@ -75,6 +75,21 @@ func migrateDB(db *sqlx.DB) error { `ALTER TABLE sinks DROP COLUMN config_data;`, }, }, + { + Id: "sinks_3", + Up: []string{ + `CREATE TABLE IF NOT EXISTS current_version ( + id UUID NOT NULL DEFAULT gen_random_uuid(), + version TEXT NOT NULL, + last_updated TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + PRIMARY KEY (id, version) + )`, + `INSERT INTO current_version (id, version, last_updated) VALUES (DEFAULT, '0.25.0', DEFAULT);`, + }, + Down: []string{ + "DROP TABLE current_version", + }, + }, }, } diff --git a/sinks/postgres/sinks.go b/sinks/postgres/sinks.go index 67ed295b9..786dd0c79 100644 --- a/sinks/postgres/sinks.go +++ b/sinks/postgres/sinks.go @@ -32,10 +32,59 @@ type sinksRepository struct { logger *zap.Logger } +func (s sinksRepository) UpdateVersion(ctx context.Context, incomingVersion string) error { + q := `UPDATE current_version SET version = :version, last_updated = :currenttime` + params := map[string]interface{}{ + "version": incomingVersion, + "currenttime": time.Now(), + } + res, err := s.db.NamedExecContext(ctx, q, params) + if err != nil { + pqErr, ok := err.(*pq.Error) + if ok { + switch pqErr.Code.Name() { + case db.ErrInvalid, db.ErrTruncation: + return errors.Wrap(sinks.ErrMalformedEntity, err) + case db.ErrDuplicate: + return errors.Wrap(errors.ErrConflict, err) + } + } + return errors.Wrap(sinks.ErrUpdateEntity, err) + } + + count, err := res.RowsAffected() + if err != nil { + return errors.Wrap(sinks.ErrUpdateEntity, err) + } + + if count == 0 { + return sinks.ErrNotFound + } + return nil +} + +func (s sinksRepository) GetVersion(ctx context.Context) (string, error) { + q := `SELECT version FROM current_version` + params := map[string]interface{}{} + rows, err := s.db.NamedQueryContext(ctx, q, params) + if err != nil { + return "", err + } + for rows.Next() { + version := "" + err := rows.Scan(&version) + if err != nil { + return "", err + } + return version, nil + } + return "", err +} + func (s sinksRepository) SearchAllSinks(ctx context.Context, filter sinks.Filter) ([]sinks.Sink, error) { q := `SELECT id, name, mf_owner_id, description, tags, state, coalesce(error, '') as error, backend, metadata, ts_created FROM sinks` params := map[string]interface{}{} - if filter.StateFilter != "" { + if (filter != sinks.Filter{} && filter.StateFilter != "") { q += `WHERE state == :state` params["state"] = filter.StateFilter } @@ -354,14 +403,6 @@ func toDBSink(sink sinks.Sink) (dbSink, error) { } func toSink(dba dbSink) (sinks.Sink, error) { - configData := "" - format := "" - if dba.ConfigData != nil { - configData = *dba.ConfigData - } - if dba.Format != nil { - format = *dba.Format - } sink := sinks.Sink{ ID: dba.ID, Name: dba.Name, @@ -371,8 +412,6 @@ func toSink(dba dbSink) (sinks.Sink, error) { State: dba.State, Error: dba.Error, Config: types.Metadata(dba.Metadata), - ConfigData: configData, - Format: format, Created: dba.Created, Tags: types.Tags(dba.Tags), } diff --git a/sinks/redis/producer/events.go b/sinks/redis/producer/events.go index a578ba648..acec59a7c 100644 --- a/sinks/redis/producer/events.go +++ b/sinks/redis/producer/events.go @@ -59,7 +59,7 @@ type deleteSinkEvent struct { func (dse deleteSinkEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ "sink_id": dse.sinkID, - "owner_id": dse.ownerID, + "owner": dse.ownerID, "operation": SinkDelete, }, nil } diff --git a/sinks/redis/producer/streams.go b/sinks/redis/producer/streams.go index 49c7f2aa2..7086deb19 100644 --- a/sinks/redis/producer/streams.go +++ b/sinks/redis/producer/streams.go @@ -10,6 +10,7 @@ package producer import ( "context" + "github.com/orb-community/orb/sinks/authentication_type" "github.com/go-redis/redis/v8" "github.com/orb-community/orb/sinks" @@ -73,6 +74,34 @@ func (es eventStore) CreateSink(ctx context.Context, token string, s sinks.Sink) return es.svc.CreateSink(ctx, token, s) } +func (es eventStore) UpdateSinkInternal(ctx context.Context, s sinks.Sink) (sink sinks.Sink, err error) { + defer func() { + event := updateSinkEvent{ + sinkID: sink.ID, + owner: sink.MFOwnerID, + config: sink.Config, + } + + encode, err := event.Encode() + if err != nil { + es.logger.Error("error encoding object", zap.Error(err)) + } + + record := &redis.XAddArgs{ + Stream: streamID, + MaxLen: streamLen, + Approx: true, + Values: encode, + } + + err = es.client.XAdd(ctx, record).Err() + if err != nil { + es.logger.Error("error sending event to sinks event store", zap.Error(err)) + } + }() + return es.svc.UpdateSinkInternal(ctx, s) +} + func (es eventStore) UpdateSink(ctx context.Context, token string, s sinks.Sink) (sink sinks.Sink, err error) { defer func() { event := updateSinkEvent{ @@ -105,6 +134,14 @@ func (es eventStore) ListSinks(ctx context.Context, token string, pm sinks.PageM return es.svc.ListSinks(ctx, token, pm) } +func (es eventStore) ListAuthenticationTypes(ctx context.Context, token string) ([]authentication_type.AuthenticationTypeConfig, error) { + return es.svc.ListAuthenticationTypes(ctx, token) +} + +func (es eventStore) ViewAuthenticationType(ctx context.Context, token string, key string) (authentication_type.AuthenticationTypeConfig, error) { + return es.svc.ViewAuthenticationType(ctx, token, key) +} + func (es eventStore) ListBackends(ctx context.Context, token string) (_ []string, err error) { return es.svc.ListBackends(ctx, token) } diff --git a/sinks/service.go b/sinks/service.go index 4ceef585d..24c73a174 100644 --- a/sinks/service.go +++ b/sinks/service.go @@ -14,6 +14,9 @@ import ( mfsdk "github.com/mainflux/mainflux/pkg/sdk/go" "github.com/orb-community/orb/pkg/errors" "github.com/orb-community/orb/pkg/types" + "github.com/orb-community/orb/sinks/authentication_type" + "github.com/orb-community/orb/sinks/authentication_type/basicauth" + "github.com/orb-community/orb/sinks/backend/otlphttpexporter" "github.com/orb-community/orb/sinks/backend/prometheus" "go.uber.org/zap" "time" @@ -41,7 +44,7 @@ type sinkService struct { // Sinks sinkRepo SinkRepository // passwordService - passwordService PasswordService + passwordService authentication_type.PasswordService } func (svc sinkService) identify(token string) (string, error) { @@ -60,15 +63,16 @@ func (svc sinkService) GetLogger() *zap.Logger { return svc.logger } -func NewSinkService(logger *zap.Logger, auth mainflux.AuthServiceClient, sinkRepo SinkRepository, mfsdk mfsdk.SDK, services PasswordService) SinkService { - +func NewSinkService(logger *zap.Logger, auth mainflux.AuthServiceClient, sinkRepo SinkRepository, mfsdk mfsdk.SDK, passwordService authentication_type.PasswordService) SinkService { + otlphttpexporter.Register() prometheus.Register() - + basicauth.Register(passwordService) + // bearerauth.Register(passwordService) return &sinkService{ logger: logger, auth: auth, sinkRepo: sinkRepo, mfsdk: mfsdk, - passwordService: services, + passwordService: passwordService, } } diff --git a/sinks/sinks.go b/sinks/sinks.go index d45d4eb81..14cb8455f 100644 --- a/sinks/sinks.go +++ b/sinks/sinks.go @@ -9,6 +9,8 @@ import ( "database/sql/driver" "github.com/orb-community/orb/pkg/errors" "github.com/orb-community/orb/pkg/types" + "github.com/orb-community/orb/sinks/authentication_type" + "github.com/orb-community/orb/sinks/authentication_type/basicauth" "github.com/orb-community/orb/sinks/backend" "go.uber.org/zap" "time" @@ -95,6 +97,18 @@ func (s *State) Scan(value interface{}) error { } func (s State) Value() (driver.Value, error) { return s.String(), nil } +func NewConfigBackends(e backend.Backend, a authentication_type.AuthenticationType) Configuration { + return Configuration{ + Exporter: e, + Authentication: a, + } +} + +type Configuration struct { + Exporter backend.Backend `json:"exporter" ,yaml:"exporter"` + Authentication authentication_type.AuthenticationType `json:"authentication" ,yaml:"authentication"` +} + type Sink struct { ID string Name types.Identifier @@ -110,6 +124,15 @@ type Sink struct { Created time.Time } +func (s *Sink) GetAuthenticationTypeName() string { + authMeta := s.Config.GetSubMetadata("authentication") + // Defaults to basicauth + if authMeta == nil { + return basicauth.AuthType + } + return authMeta["type"].(string) +} + // Page contains page related metadata as well as list of sinks that // belong to this page type Page struct { @@ -123,6 +146,8 @@ type SinkService interface { CreateSink(ctx context.Context, token string, s Sink) (Sink, error) // UpdateSink by id UpdateSink(ctx context.Context, token string, s Sink) (Sink, error) + // UpdateSinkInternal by id + UpdateSinkInternal(ctx context.Context, s Sink) (Sink, error) // ListSinks retrieves data about sinks ListSinks(ctx context.Context, token string, pm PageMetadata) (Page, error) // ListSinksInternal retrieves data from sinks filtered by SinksFilter for Services like Maestro, to build DeploymentEntries @@ -131,6 +156,10 @@ type SinkService interface { ListBackends(ctx context.Context, token string) ([]string, error) // ViewBackend retrieves a backend by the name ViewBackend(ctx context.Context, token string, key string) (backend.Backend, error) + // ListAuthenticationTypes retrieves a list of available AuthenticationTypes + ListAuthenticationTypes(ctx context.Context, token string) ([]authentication_type.AuthenticationTypeConfig, error) + // ViewAuthenticationType retrieves a AuthenticationType by the name + ViewAuthenticationType(ctx context.Context, token string, key string) (authentication_type.AuthenticationTypeConfig, error) // ViewSink retrieves a sink by id, for View, does not send password ViewSink(ctx context.Context, token string, key string) (Sink, error) // ViewSinkInternal retrieves a sink by id, via GRPC, sends password @@ -163,4 +192,8 @@ type SinkRepository interface { Remove(ctx context.Context, owner string, key string) error // UpdateSinkState updates sink state like active, idle, new, unknown UpdateSinkState(ctx context.Context, sinkID string, msg string, ownerID string, state State) error + // GetVersion for migrate service + GetVersion(ctx context.Context) (string, error) + // UpsertVersion for migrate service + UpdateVersion(ctx context.Context, version string) error } diff --git a/sinks/sinks_service.go b/sinks/sinks_service.go index 3ec0072fe..e87941b66 100644 --- a/sinks/sinks_service.go +++ b/sinks/sinks_service.go @@ -10,11 +10,13 @@ package sinks import ( "context" + "github.com/orb-community/orb/pkg/errors" "github.com/orb-community/orb/pkg/types" + "github.com/orb-community/orb/sinks/authentication_type" "github.com/orb-community/orb/sinks/backend" "go.uber.org/zap" - "net/url" + "gopkg.in/yaml.v3" ) var ( @@ -28,26 +30,28 @@ func (svc sinkService) CreateSink(ctx context.Context, token string, sink Sink) mfOwnerID, err := svc.identify(token) if err != nil { - return Sink{}, err + return Sink{}, errors.Wrap(ErrCreateSink, err) } sink.MFOwnerID = mfOwnerID - err = validateBackend(&sink) + be, err := validateBackend(&sink) if err != nil { - return Sink{}, err + return Sink{}, errors.Wrap(ErrCreateSink, err) } - - // Validate remote_host - _, err = url.ParseRequestURI(sink.Config["remote_host"].(string)) + at, err := validateAuthType(&sink) if err != nil { - return Sink{}, errors.Wrap(errors.New("invalid remote url"), err) + return Sink{}, err + } + cfg := Configuration{ + Authentication: at, + Exporter: be, } // encrypt data for the password - sink, err = svc.encryptMetadata(sink) + sink, err = svc.encryptMetadata(cfg, sink) if err != nil { - return Sink{}, errors.Wrap(ErrCreateSink, err) + return Sink{}, err } //// add default values @@ -62,71 +66,205 @@ func (svc sinkService) CreateSink(ctx context.Context, token string, sink Sink) sink.ID = id // After creating, decrypt Metadata to send correct information to Redis - sink, err = svc.decryptMetadata(sink) - + sink, err = svc.decryptMetadata(cfg, sink) + if err != nil { + return Sink{}, errors.Wrap(ErrCreateSink, err) + } return sink, nil } -func (svc sinkService) encryptMetadata(sink Sink) (Sink, error) { - var err error - sink.Config.FilterMap(func(key string) bool { - return key == backend.ConfigFeatureTypePassword - }, func(key string, value interface{}) (string, interface{}) { - var stringVal string - switch v := value.(type) { - case *string: - stringVal = *v - case string: - stringVal = v +func validateAuthType(s *Sink) (authentication_type.AuthenticationType, error) { + var authMetadata types.Metadata + if len(s.ConfigData) != 0 { + var helper types.Metadata + if s.Format == "yaml" { + err := yaml.Unmarshal([]byte(s.ConfigData), &helper) + if err != nil { + return nil, err + } + authMetadata = helper.GetSubMetadata(authentication_type.AuthenticationKey) + } else { + return nil, errors.New("config format not supported") } - newValue, err2 := svc.passwordService.EncodePassword(stringVal) - if err2 != nil { - err = err2 - return key, value + } else { + authMetadata = s.Config.GetSubMetadata(authentication_type.AuthenticationKey) + } + authTypeStr, ok := authMetadata["type"] + if !ok { + return nil, errors.Wrap(errors.ErrAuthTypeNotFound, errors.New("authentication type not found")) + } + + if _, ok := authTypeStr.(string); !ok { + return nil, errors.Wrap(errors.ErrInvalidAuthType, errors.New("invalid authentication type")) + } + + authType, ok := authentication_type.GetAuthType(authTypeStr.(string)) + if !ok { + return nil, errors.Wrap(errors.ErrInvalidAuthType, errors.New("invalid authentication type")) + } + + err := authType.ValidateConfiguration("object", authMetadata) + if err != nil { + return nil, err + } + + return authType, nil +} + +func (svc sinkService) encryptMetadata(configSvc Configuration, sink Sink) (Sink, error) { + var err error + if sink.Config != nil { + encodeMetadata, err := configSvc.Authentication.EncodeInformation("object", sink.Config) + if err != nil { + svc.logger.Error("error on parsing encrypted config in data") + return sink, err } - return key, newValue - }) + sink.Config = encodeMetadata.(types.Metadata) + } if sink.ConfigData != "" { - sinkBE := backend.GetBackend(sink.Backend) - if sinkBE == nil { - return sink, errors.New("backend cannot be nil") - } - sink.ConfigData, err = sinkBE.ConfigToFormat(sink.Format, sink.Config) + encodeMetadata, err := configSvc.Authentication.EncodeInformation("yaml", sink.ConfigData) if err != nil { svc.logger.Error("error on parsing encrypted config in data") return sink, err } + sink.ConfigData = encodeMetadata.(string) } - return sink, err } -func (svc sinkService) decryptMetadata(sink Sink) (Sink, error) { +func (svc sinkService) ViewAuthenticationType(ctx context.Context, token string, key string) (authentication_type.AuthenticationTypeConfig, error) { + _, err := svc.identify(token) + if err != nil { + return authentication_type.AuthenticationTypeConfig{}, err + } + + value, ok := authentication_type.GetAuthType(key) + if !ok { + return authentication_type.AuthenticationTypeConfig{}, errors.New("invalid authentication type given name") + } + return value.Metadata(), nil +} + +func (svc sinkService) ListAuthenticationTypes(ctx context.Context, token string) ([]authentication_type.AuthenticationTypeConfig, error) { + _, err := svc.identify(token) + if err != nil { + return nil, err + } + + value := authentication_type.GetList() + + return value, nil +} + +func (svc sinkService) decryptMetadata(configSvc Configuration, sink Sink) (Sink, error) { var err error - sink.Config.FilterMap(func(key string) bool { - return key == backend.ConfigFeatureTypePassword - }, func(key string, value interface{}) (string, interface{}) { - newValue, err2 := svc.passwordService.DecodePassword(value.(string)) - if err2 != nil { - err = err2 - return key, value + if sink.Config != nil { + decodeMetadata, err := configSvc.Authentication.DecodeInformation("object", sink.Config) + if err != nil { + svc.logger.Error("error on parsing encrypted config in data") + return sink, err } - return key, newValue - }) + sink.Config = decodeMetadata.(types.Metadata) + } if sink.ConfigData != "" { - sinkBE := backend.GetBackend(sink.Backend) - if sinkBE == nil { - return sink, errors.New("backend cannot be nil") - } - sink.ConfigData, err = sinkBE.ConfigToFormat(sink.Format, sink.Config) + decodeMetadata, err := configSvc.Authentication.DecodeInformation("yaml", sink.ConfigData) if err != nil { svc.logger.Error("error on parsing encrypted config in data") return sink, err } + sink.ConfigData = decodeMetadata.(string) } return sink, err } +func (svc sinkService) UpdateSinkInternal(ctx context.Context, sink Sink) (Sink, error) { + var currentSink Sink + currentSink, err := svc.sinkRepo.RetrieveById(ctx, sink.ID) + if err != nil { + return Sink{}, errors.Wrap(ErrUpdateEntity, err) + } + var cfg Configuration + if sink.Config == nil && sink.ConfigData == "" { + // No config sent, keep the previous + sink.Config = currentSink.Config + authType, _ := authentication_type.GetAuthType(sink.GetAuthenticationTypeName()) + be := backend.GetBackend(currentSink.Backend) + cfg = Configuration{ + Authentication: authType, + Exporter: be, + } + + // get the decrypted config, otherwise the password would be encrypted again + sink, err = svc.decryptMetadata(cfg, sink) + if err != nil { + return Sink{}, errors.Wrap(ErrUpdateEntity, err) + } + } else { + sink.Backend = currentSink.Backend + // we still need to validate here + be, err := validateBackend(&sink) + if err != nil { + return Sink{}, errors.Wrap(ErrMalformedEntity, err) + } + at, err := validateAuthType(&sink) + if err != nil { + return Sink{}, errors.Wrap(ErrMalformedEntity, err) + } + cfg = Configuration{ + Authentication: at, + Exporter: be, + } + //// add default values + defaultMetadata := make(types.Metadata, 1) + defaultMetadata["opentelemetry"] = "enabled" + sink.Config.Merge(defaultMetadata) + sink.State = Unknown + sink.Error = "" + if sink.Format == "yaml" { + configDataByte, err := yaml.Marshal(sink.Config) + if err != nil { + return Sink{}, errors.Wrap(ErrMalformedEntity, err) + } + sink.ConfigData = string(configDataByte) + } + } + + if sink.Tags == nil { + sink.Tags = currentSink.Tags + } + + if sink.Description == nil { + sink.Description = currentSink.Description + } + + if newName := sink.Name.String(); newName == "" { + sink.Name = currentSink.Name + } + + sink.MFOwnerID = currentSink.MFOwnerID + if sink.Backend == "" && currentSink.Backend != "" { + sink.Backend = currentSink.Backend + } + sink, err = svc.encryptMetadata(cfg, sink) + if err != nil { + return Sink{}, errors.Wrap(ErrUpdateEntity, err) + } + err = svc.sinkRepo.Update(ctx, sink) + if err != nil { + return Sink{}, errors.Wrap(ErrUpdateEntity, err) + } + sinkEdited, err := svc.sinkRepo.RetrieveById(ctx, sink.ID) + if err != nil { + return Sink{}, errors.Wrap(ErrUpdateEntity, err) + } + sinkEdited, err = svc.decryptMetadata(cfg, sinkEdited) + if err != nil { + return Sink{}, errors.Wrap(ErrUpdateEntity, err) + } + + return sinkEdited, nil +} + func (svc sinkService) UpdateSink(ctx context.Context, token string, sink Sink) (Sink, error) { skOwnerID, err := svc.identify(token) if err != nil { @@ -138,34 +276,50 @@ func (svc sinkService) UpdateSink(ctx context.Context, token string, sink Sink) if err != nil { return Sink{}, err } - + var cfg Configuration if sink.Config == nil && sink.ConfigData == "" { - // No config sent + // No config sent, keep the previous sink.Config = currentSink.Config + authType, _ := authentication_type.GetAuthType(sink.GetAuthenticationTypeName()) + be := backend.GetBackend(currentSink.Backend) + cfg = Configuration{ + Authentication: authType, + Exporter: be, + } + // get the decrypted config, otherwise the password would be encrypted again - sink, err = svc.decryptMetadata(sink) + sink, err = svc.decryptMetadata(cfg, sink) if err != nil { - return Sink{}, err + return Sink{}, errors.Wrap(ErrUpdateEntity, err) } } else { - if sink.ConfigData != "" { - sinkBE := backend.GetBackend(currentSink.Backend) - if sinkBE == nil { - return sink, errors.New("backend cannot be nil") - } - sink.Config, err = sinkBE.ParseConfig(sink.Format, sink.ConfigData) - if err != nil { - return Sink{}, err - } - if err := sinkBE.ValidateConfiguration(sink.Config); err != nil { - return Sink{}, err - } + sink.Backend = currentSink.Backend + be, err := validateBackend(&sink) + if err != nil { + return Sink{}, errors.Wrap(errors.New("incorrect backend and exporter configuration"), err) + } + at, err := validateAuthType(&sink) + if err != nil { + return Sink{}, errors.Wrap(errors.New("incorrect authentication configuration"), err) + } + cfg = Configuration{ + Authentication: at, + Exporter: be, } //// add default values defaultMetadata := make(types.Metadata, 1) defaultMetadata["opentelemetry"] = "enabled" sink.Config.Merge(defaultMetadata) - currentSink.Error = "" + sink.State = Unknown + sink.Error = "" + if sink.Format == "yaml" { + configDataByte, err := yaml.Marshal(sink.Config) + if err != nil { + svc.logger.Error("failed to marshal config data", zap.Error(err)) + return Sink{}, errors.Wrap(errors.New("configuration is invalid for yaml format"), err) + } + sink.ConfigData = string(configDataByte) + } } if sink.Tags == nil { @@ -184,7 +338,7 @@ func (svc sinkService) UpdateSink(ctx context.Context, token string, sink Sink) if sink.Backend == "" && currentSink.Backend != "" { sink.Backend = currentSink.Backend } - sink, err = svc.encryptMetadata(sink) + sink, err = svc.encryptMetadata(cfg, sink) if err != nil { return Sink{}, errors.Wrap(ErrUpdateEntity, err) } @@ -194,9 +348,9 @@ func (svc sinkService) UpdateSink(ctx context.Context, token string, sink Sink) } sinkEdited, err := svc.sinkRepo.RetrieveById(ctx, sink.ID) if err != nil { - return Sink{}, errors.Wrap(ErrUpdateEntity, err) + return Sink{}, err } - sinkEdited, err = svc.decryptMetadata(sinkEdited) + sinkEdited, err = svc.decryptMetadata(cfg, sinkEdited) if err != nil { return Sink{}, errors.Wrap(ErrUpdateEntity, err) } @@ -241,7 +395,13 @@ func (svc sinkService) ViewSinkInternal(ctx context.Context, ownerID string, key if err != nil { return Sink{}, errors.Wrap(errors.ErrNotFound, err) } - res, err = svc.decryptMetadata(res) + authType, _ := authentication_type.GetAuthType(res.GetAuthenticationTypeName()) + be := backend.GetBackend(res.Backend) + cfg := Configuration{ + Authentication: authType, + Exporter: be, + } + res, err = svc.decryptMetadata(cfg, res) if err != nil { return Sink{}, errors.Wrap(errors.ErrViewEntity, err) } @@ -254,7 +414,13 @@ func (svc sinkService) ListSinksInternal(ctx context.Context, filter Filter) (si return nil, errors.Wrap(errors.ErrNotFound, err) } for _, sink := range sinks { - sink, err = svc.decryptMetadata(sink) + authType, _ := authentication_type.GetAuthType(sink.GetAuthenticationTypeName()) + be := backend.GetBackend(sink.Backend) + cfg := Configuration{ + Authentication: authType, + Exporter: be, + } + sink, err = svc.decryptMetadata(cfg, sink) if err != nil { return nil, errors.Wrap(errors.ErrViewEntity, err) } @@ -291,7 +457,12 @@ func (svc sinkService) ValidateSink(ctx context.Context, token string, sink Sink sink.MFOwnerID = mfOwnerID - err = validateBackend(&sink) + _, err = validateBackend(&sink) + if err != nil { + return Sink{}, errors.Wrap(ErrValidateSink, err) + } + + _, err = validateAuthType(&sink) if err != nil { return Sink{}, errors.Wrap(ErrValidateSink, err) } @@ -303,11 +474,29 @@ func (svc sinkService) ChangeSinkStateInternal(ctx context.Context, sinkID strin return svc.sinkRepo.UpdateSinkState(ctx, sinkID, msg, ownerID, state) } -func validateBackend(sink *Sink) error { +func validateBackend(sink *Sink) (be backend.Backend, err error) { if backend.HaveBackend(sink.Backend) { sink.State = Unknown } else { - return ErrInvalidBackend + return nil, ErrInvalidBackend + } + sinkBe := backend.GetBackend(sink.Backend) + if len(sink.ConfigData) == 0 { + config := sink.Config.GetSubMetadata("exporter") + if config == nil { + return nil, errors.Wrap(ErrInvalidBackend, errors.New("missing exporter configuration")) + } + return sinkBe, sinkBe.ValidateConfiguration(config) + } else { + parseConfig, err := sinkBe.ParseConfig("yaml", sink.ConfigData) + if err != nil { + return nil, errors.Wrap(ErrInvalidBackend, err) + } + sink.Config = parseConfig + config2 := sink.Config.GetSubMetadata("exporter") + if config2 == nil { + return nil, errors.Wrap(ErrInvalidBackend, errors.New("missing exporter configuration")) + } + return sinkBe, sinkBe.ValidateConfiguration(config2) } - return nil } diff --git a/sinks/sinks_service_test.go b/sinks/sinks_service_test.go index 0654de8b6..9b90ea0db 100644 --- a/sinks/sinks_service_test.go +++ b/sinks/sinks_service_test.go @@ -11,17 +11,19 @@ package sinks_test import ( "context" "fmt" + "testing" + "github.com/gofrs/uuid" mfsdk "github.com/mainflux/mainflux/pkg/sdk/go" thmocks "github.com/mainflux/mainflux/things/mocks" "github.com/orb-community/orb/pkg/errors" "github.com/orb-community/orb/pkg/types" "github.com/orb-community/orb/sinks" + "github.com/orb-community/orb/sinks/authentication_type" skmocks "github.com/orb-community/orb/sinks/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" - "testing" ) const ( @@ -32,25 +34,10 @@ const ( n = uint64(10) ) -var ( - nameID, _ = types.NewIdentifier("my-sink") - description = "An example prometheus sink" - sink = sinks.Sink{ - Name: nameID, - Description: &description, - Backend: "prometheus", - State: sinks.Unknown, - Error: "", - Config: map[string]interface{}{"remote_host": "https://orb.community/", "username": "dbuser"}, - Tags: map[string]string{"cloud": "aws"}, - } - wrongID, _ = uuid.NewV4() -) - func newService(tokens map[string]string) sinks.SinkService { logger := zap.NewNop() auth := thmocks.NewAuthService(tokens, make(map[string][]thmocks.MockSubjectSet)) - pwdSvc := sinks.NewPasswordService(logger, "_testing_string_") + pwdSvc := authentication_type.NewPasswordService(logger, "_testing_string_") sinkRepo := skmocks.NewSinkRepository(pwdSvc) config := mfsdk.Config{ @@ -63,16 +50,31 @@ func newService(tokens map[string]string) sinks.SinkService { func TestCreateSink(t *testing.T) { service := newService(map[string]string{token: email}) - + nameID, _ := types.NewIdentifier("my-sink") description := "An example prometheus sink" + sink := sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + State: sinks.Unknown, + Error: "", + Config: types.Metadata{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, + } var invalidBackendSink = sinks.Sink{ Name: nameID, Description: &description, Backend: "invalid", State: sinks.Unknown, Error: "", - Config: map[string]interface{}{"remote_host": "data", "username": "dbuser"}, - Tags: map[string]string{"cloud": "aws"}, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, } cases := map[string]struct { @@ -121,22 +123,26 @@ func TestIdempotencyUpdateSink(t *testing.T) { Backend: "prometheus", State: sinks.Unknown, Error: "", - Config: map[string]interface{}{"remote_host": "https://orb.community/", "username": "netops", "password": "w0w-orb-Rocks!"}, - Tags: map[string]string{"cloud": "aws"}, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "netops", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, } - initialUsername := "netops" - initialPassword := "w0w-orb-Rocks!" initialYamlSink := sinks.Sink{ Name: yamlSinkName, Description: &aInitialDescription, Backend: "prometheus", State: sinks.Unknown, Error: "", - ConfigData: "remote_host: https://orb.community/\nusername: netops\npassword: w0w-orb-Rocks!", + ConfigData: "exporter: \n remote_host: https://orb.community/\nauthentication:\n type: basicauth\n username: netops\n password: dbpass\n", Format: "yaml", - MFOwnerID: "OrbCommunity", - Config: map[string]interface{}{"remote_host": "https://orb.community/", "username": &initialUsername, "password": &initialPassword}, - Tags: map[string]string{"cloud": "aws"}, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "netops", "password": "dbpass"}, + }, + MFOwnerID: "OrbCommunity", + Tags: map[string]string{"cloud": "aws"}, } jsonCreatedSink, err := service.CreateSink(ctx, token, initialJsonSink) require.NoError(t, err, "failed to create entity") @@ -162,8 +168,8 @@ func TestIdempotencyUpdateSink(t *testing.T) { tagVal, tagOk := value.Tags["cloud"] require.True(t, tagOk) require.Equal(t, "aws", tagVal) - require.Equalf(t, "https://orb.community/", value.Config["remote_host"], "remote host is not equal") - require.Equalf(t, "netops", value.Config["username"], "username is not equal") + require.Equalf(t, "https://orb.community/", value.Config.GetSubMetadata("exporter")["remote_host"], "remote host is not equal") + require.Equalf(t, "netops", value.Config.GetSubMetadata("authentication")["username"], "username is not equal") }, token: token, }, @@ -178,9 +184,8 @@ func TestIdempotencyUpdateSink(t *testing.T) { tagVal, tagOk := value.Tags["cloud"] require.True(t, tagOk) require.Equal(t, "aws", tagVal) - require.Equalf(t, "https://orb.community/", value.Config["remote_host"], "remote host is not equal") - actual := value.Config["username"].(*string) - require.Equalf(t, "netops", *actual, "username is not equal") + require.Equalf(t, "https://orb.community/", value.Config.GetSubMetadata("exporter")["remote_host"], "remote host is not equal") + require.Equalf(t, "netops", value.Config.GetSubMetadata("authentication")["username"], "username is not equal") }, token: token, }, @@ -208,23 +213,21 @@ func TestPartialUpdateSink(t *testing.T) { Name: jsonSinkName, Description: &aInitialDescription, Backend: "prometheus", - State: sinks.Unknown, - Error: "", - Config: map[string]interface{}{"remote_host": "https://orb.community/", "username": "netops", "password": "w0w-orb-Rocks!"}, - Tags: map[string]string{"cloud": "aws"}, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, } - initialUsername := "netops" - initialPassword := "w0w-orb-Rocks!" initialYamlSink := sinks.Sink{ Name: yamlSinkName, Description: &aInitialDescription, Backend: "prometheus", State: sinks.Unknown, Error: "", - ConfigData: "remote_host:https://orb.community/\nusername: netops\npassword: w0w-orb-Rocks!", + ConfigData: "exporter:\n remote_host: https://orb.community/\nauthentication:\n type: basicauth\n username: netops\n password: w0w-orb-Rocks!", Format: "yaml", MFOwnerID: "OrbCommunity", - Config: map[string]interface{}{"remote_host": "https://orb.community/", "username": &initialUsername, "password": &initialPassword}, Tags: map[string]string{"cloud": "aws"}, } jsonCreatedSink, err := service.CreateSink(ctx, token, initialJsonSink) @@ -232,7 +235,6 @@ func TestPartialUpdateSink(t *testing.T) { require.NotEmptyf(t, jsonCreatedSink.ID, "id must not be empty") yamlCreatedSink, err := service.CreateSink(ctx, token, initialYamlSink) require.NoError(t, err, "failed to create entity") - userHelper := "netops_admin" initialJsonSink.ID = jsonCreatedSink.ID initialYamlSink.ID = yamlCreatedSink.ID var cases = map[string]struct { @@ -241,70 +243,30 @@ func TestPartialUpdateSink(t *testing.T) { expected func(t *testing.T, value sinks.Sink, err error) token string }{ - // TODO this will fail locally because of password encryption, - // TODO we will revisit this whenever there is a update on password encryption - //"update only name": { - // requestSink: sinks.Sink{ - // ID: jsonCreatedSink.ID, - // Name: newSinkName, - // }, - // expected: func(t *testing.T, value sinks.Sink, err error) { - // require.NoError(t, err, "no error expected") - // require.Equal(t, value.Name, newSinkName, "sink name is not equal") - // }, - // token: token, - //}, - //"update only description": { - // requestSink: sinks.Sink{ - // ID: jsonCreatedSink.ID, - // Description: &aNewDescription, - // }, - // expected: func(t *testing.T, value sinks.Sink, err error) { - // require.NoError(t, err, "no error expected") - // require.NotNilf(t, value.Description, "description is nil") - // desc := *value.Description - // require.Equal(t, desc, aNewDescription, "description is not equal") - // }, - // token: token, - //}, "update only tags": { - // requestSink: sinks.Sink{ - // ID: jsonCreatedSink.ID, - // Tags: map[string]string{"cloud": "gcp", "from_aws": "true"}, - // }, - // expected: func(t *testing.T, value sinks.Sink, err error) { - // require.NoError(t, err, "no error expected") - // tagVal, tagOk := value.Tags["cloud"] - // tag2Val, tag2Ok := value.Tags["from_aws"] - // require.True(t, tagOk) - // require.Equal(t, "gcp", tagVal) - // require.True(t, tag2Ok) - // require.Equal(t, "true", tag2Val) - // }, - // token: token, - //}, "update config json": { requestSink: sinks.Sink{ - ID: jsonCreatedSink.ID, - Config: map[string]interface{}{"remote_host": "https://orb.community/prom/push", "username": "netops_admin", "password": "w0w-orb-Rocks!"}, + ID: jsonCreatedSink.ID, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/prom/push"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "netops_admin", "password": "newpass"}, + }, }, expected: func(t *testing.T, value sinks.Sink, err error) { require.NoError(t, err, "no error expected") - require.Equalf(t, "https://orb.community/prom/push", value.Config["remote_host"], "want %s, got %s", "https://orb.community/prom/push", value.Config["remote_host"]) - require.Equalf(t, "netops_admin", value.Config["username"], "want %s, got %s", "netops_admin", value.Config["username"]) + require.Equalf(t, "https://orb.community/prom/push", value.Config.GetSubMetadata("exporter")["remote_host"], "want %s, got %s", "https://orb.community/prom/push", value.Config.GetSubMetadata("exporter")["remote_host"]) + require.Equalf(t, "netops_admin", value.Config.GetSubMetadata("authentication")["username"], "want %s, got %s", "netops_admin", value.Config.GetSubMetadata("authentication")["username"]) + require.Equalf(t, "newpass", value.Config.GetSubMetadata("authentication")["password"], "want %s, got %s", "newpass", value.Config.GetSubMetadata("authentication")["password"]) }, token: token, }, "update config yaml": { requestSink: sinks.Sink{ ID: yamlCreatedSink.ID, Format: "yaml", - ConfigData: "remote_host: https://orb.community/prom/push\nusername: netops_admin\npassword: \"w0w-orb-Rocks!\"", + ConfigData: "exporter:\n remote_host: https://orb.community/\nauthentication:\n type: basicauth\n username: netops\n password: w0w-orb-Rocks!", }, expected: func(t *testing.T, value sinks.Sink, err error) { require.NoError(t, err, "no error expected") - require.Equalf(t, "https://orb.community/prom/push", value.Config["remote_host"], "want %s, got %s", "https://orb.community/prom/push", value.Config["remote_host"]) - require.NotNilf(t, value.Config["username"], "description is nil") - desc := value.Config["username"] - require.Equal(t, &userHelper, desc, "description is not equal") + require.Equalf(t, "https://orb.community/", value.Config.GetSubMetadata("exporter")["remote_host"], "want %s, got %s", "https://orb.community/", value.Config.GetSubMetadata("exporter")["remote_host"]) }, token: token, }, @@ -319,6 +281,21 @@ func TestPartialUpdateSink(t *testing.T) { func TestUpdateSink(t *testing.T) { service := newService(map[string]string{token: email}) + nameID, _ := types.NewIdentifier("my-sink") + description := "An example prometheus sink" + sink := sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + State: sinks.Unknown, + Error: "", + Config: types.Metadata{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, + } + wrongID, _ := uuid.NewV4() sk, err := service.CreateSink(context.Background(), token, sink) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -330,10 +307,7 @@ func TestUpdateSink(t *testing.T) { noConfig := sk noConfig.Config = make(map[string]interface{}) - - description := "An example prometheus sink" newDescription := "new description" - nameTestConfigAttribute, _ := types.NewIdentifier("configSink") sinkTestConfigAttribute, err := service.CreateSink(context.Background(), token, sinks.Sink{ Name: nameTestConfigAttribute, @@ -341,8 +315,11 @@ func TestUpdateSink(t *testing.T) { Backend: "prometheus", State: sinks.Unknown, Error: "", - Config: map[string]interface{}{"remote_host": "https://orb.community/", "username": "dbuser"}, - Tags: map[string]string{"cloud": "aws"}, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, }) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -353,8 +330,11 @@ func TestUpdateSink(t *testing.T) { Backend: "prometheus", State: sinks.Unknown, Error: "", - Config: map[string]interface{}{"remote_host": "https://orb.community/", "username": "dbuser"}, - Tags: map[string]string{"cloud": "aws"}, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, }) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -365,8 +345,11 @@ func TestUpdateSink(t *testing.T) { Backend: "prometheus", State: sinks.Unknown, Error: "", - Config: map[string]interface{}{"remote_host": "https://orb.community/", "username": "dbuser"}, - Tags: map[string]string{"cloud": "aws"}, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, }) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -377,8 +360,11 @@ func TestUpdateSink(t *testing.T) { Backend: "prometheus", State: sinks.Unknown, Error: "", - Config: map[string]interface{}{"remote_host": "https://orb.community/", "username": "dbuser"}, - Tags: map[string]string{"cloud": "aws"}, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, }) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -408,14 +394,25 @@ func TestUpdateSink(t *testing.T) { incomingSink: sinks.Sink{ ID: sinkTestConfigAttribute.ID, Config: types.Metadata{ - "remote_host": "https://orb.community/", + "exporter": types.Metadata{ + "remote_host": "https://orb.community/", + }, + "authentication": types.Metadata{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, }, Error: "", }, expectedSink: sinks.Sink{ Name: sinkTestConfigAttribute.Name, Config: types.Metadata{ - "opentelemetry": "enabled", "remote_host": "https://orb.community/", + "opentelemetry": "enabled", + "exporter": types.Metadata{ + "remote_host": "https://orb.community/", + }, + "authentication": types.Metadata{ + "type": "basicauth", + "username": "dbuser", + "password": "dbpass", + }, }, Description: sinkTestConfigAttribute.Description, Tags: sinkTestConfigAttribute.Tags, @@ -509,22 +506,45 @@ func TestUpdateSink(t *testing.T) { } for desc, tc := range cases { + ctx := context.WithValue(context.Background(), "tc", desc) t.Run(desc, func(t *testing.T) { - res, err := service.UpdateSink(context.Background(), tc.token, tc.incomingSink) + res, err := service.UpdateSink(ctx, tc.token, tc.incomingSink) if err == nil { - assert.Equal(t, tc.expectedSink.Config, res.Config, "config not as expected") assert.Equal(t, tc.expectedSink.Name.String(), res.Name.String(), "sink name not as expected") assert.Equal(t, *tc.expectedSink.Description, *res.Description, "sink description not as expected") assert.Equal(t, tc.expectedSink.Tags, res.Tags, "sink tags not as expected") + } else { + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %d got %d", desc, tc.err, err)) } - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %d got %d", desc, tc.err, err)) }) } } func TestViewSink(t *testing.T) { service := newService(map[string]string{token: email}) - + nameID, _ := types.NewIdentifier("my-sink") + description := "An example prometheus sink" + sink := sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + State: sinks.Unknown, + Error: "", + Config: types.Metadata{ + "exporter": map[string]interface{}{ + "remote_host": "https://orb.community/", + }, + "authentication": map[string]interface{}{ + "type": "basicauth", + "username": "dbuser", + "password": "dbpass", + }, + }, + Tags: map[string]string{ + "cloud": "aws", + }, + } + wrongID, _ := uuid.NewV4() sk, err := service.CreateSink(context.Background(), token, sink) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -560,6 +580,20 @@ func TestViewSink(t *testing.T) { func TestListSinks(t *testing.T) { service := newService(map[string]string{token: email}) + nameID, _ := types.NewIdentifier("my-sink") + description := "An example prometheus sink" + sink := sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + State: sinks.Unknown, + Error: "", + Config: types.Metadata{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, + } metadata := make(map[string]interface{}) metadata["serial"] = "12345" var sks []sinks.Sink @@ -739,7 +773,21 @@ func TestListBackends(t *testing.T) { func TestDeleteSink(t *testing.T) { svc := newService(map[string]string{token: email}) - + nameID, _ := types.NewIdentifier("my-sink") + description := "An example prometheus sink" + sink := sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + State: sinks.Unknown, + Error: "", + Config: types.Metadata{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, + } + wrongID, _ := uuid.NewV4() sk, err := svc.CreateSink(context.Background(), token, sink) require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err)) @@ -775,8 +823,20 @@ func TestDeleteSink(t *testing.T) { func TestValidateSink(t *testing.T) { service := newService(map[string]string{token: email}) - + nameID, _ := types.NewIdentifier("my-sink") description := "An example prometheus sink" + sink := sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + State: sinks.Unknown, + Error: "", + Config: types.Metadata{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, + } cases := map[string]struct { sink sinks.Sink @@ -798,8 +858,11 @@ func TestValidateSink(t *testing.T) { Name: nameID, Description: &description, Backend: "invalid", - Config: map[string]interface{}{"remote_host": "data", "username": "dbuser"}, - Tags: map[string]string{"cloud": "aws"}, + Config: map[string]interface{}{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, }, token: token, err: sinks.ErrValidateSink, @@ -817,7 +880,21 @@ func TestValidateSink(t *testing.T) { func TestViewSinkInternal(t *testing.T) { service := newService(map[string]string{token: email}) - + nameID, _ := types.NewIdentifier("my-sink") + description := "An example prometheus sink" + sink := sinks.Sink{ + Name: nameID, + Description: &description, + Backend: "prometheus", + State: sinks.Unknown, + Error: "", + Config: types.Metadata{ + "exporter": map[string]interface{}{"remote_host": "https://orb.community/"}, + "authentication": map[string]interface{}{"type": "basicauth", "username": "dbuser", "password": "dbpass"}, + }, + Tags: map[string]string{"cloud": "aws"}, + } + wrongID, _ := uuid.NewV4() sk, err := service.CreateSink(context.Background(), token, sink) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) diff --git a/ui/docker/Dockerfile b/ui/docker/Dockerfile index af1e3fa52..9bacbdae4 100644 --- a/ui/docker/Dockerfile +++ b/ui/docker/Dockerfile @@ -3,14 +3,12 @@ FROM orbcommunity/orb-ui-modules as built-module # ARG variables which direct the UI build # can be overwritten with --build-arg docker flag -ARG ENV_PS_SID="" -ARG ENV_PS_GROUP_KEY="" ARG ENV="" ARG ENV_GTAGID="" COPY ./ /app/ -RUN PS_SID=$ENV_PS_SID PS_GROUP_KEY=$ENV_PS_GROUP_KEY GTAGID=$ENV_GTAGID yarn build:prod +RUN GTAGID=$ENV_GTAGID yarn build:prod # Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx FROM nginx:1.13-alpine diff --git a/ui/docker/Dockerfile.buildyarn b/ui/docker/Dockerfile.buildyarn index 49e496a1c..3e3721a3c 100644 --- a/ui/docker/Dockerfile.buildyarn +++ b/ui/docker/Dockerfile.buildyarn @@ -1,5 +1,5 @@ # Stage 0, based on Node.js, install all dependencies -FROM node:12.21.0 as node +FROM node:14.17 WORKDIR /app COPY package.json /app/ diff --git a/ui/package.json b/ui/package.json index 204189256..392a46884 100644 --- a/ui/package.json +++ b/ui/package.json @@ -49,7 +49,6 @@ "@nebular/eva-icons": "5.0.0", "@nebular/security": "5.0.0", "@nebular/theme": "5.0.0", - "@pactsafe/pactsafe-angular-sdk": "^2.0.0", "@swimlane/ngx-charts": "^13.0.2", "bootstrap": "4.3.1", "classlist.js": "1.1.20150312", diff --git a/ui/proxy-config.json b/ui/proxy-config.json index 091b5727a..9305b41ef 100644 --- a/ui/proxy-config.json +++ b/ui/proxy-config.json @@ -1,111 +1,136 @@ { "/password/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/users": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/members/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/groups/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/version": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/things/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/connect": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/channels/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/http/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false, "pathRewrite": { "^/http": "" } }, "/reader/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false, "pathRewrite": { "^/reader": "" } }, "/bootstrap/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false, "pathRewrite": { "^/bootstrap": "" } }, "/browse": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/twins/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/states/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/tokens/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/features/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/sinks/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/agents/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/agent_groups/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/policies/agent/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/policies/dataset/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/agents/backends/*": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/agents/backends/taps": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/agents/backends/handlers": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false }, "/agents/backends/inputs": { - "target": "http://localhost:80/api/v1", + "target": "https://stg.orb.live/api/v1", + "changeOrigin": true, "secure": false } } diff --git a/ui/scripts/setenv.ts b/ui/scripts/setenv.ts index e0878f0e9..3d5e37df1 100644 --- a/ui/scripts/setenv.ts +++ b/ui/scripts/setenv.ts @@ -5,22 +5,6 @@ require('dotenv').config(); const targetPath = `./src/environments/environment.env.ts`; -// PACTSAFE -const enablePS = () => { - if (process.env.PS_SID && process.env?.PS_GROUP_KEY) { - return ` - PS: { - // site id - SID: '${ process.env.PS_SID }', - // group key - GROUP_KEY: '${ process.env.PS_GROUP_KEY }', - }, -`; - } else { - return ''; - } -}; - const enableMaintenace = () => { if (process.env.MAINTENANCE) { return ` @@ -44,7 +28,7 @@ const enableGTAG = () => { // we have access to our environment variables // in the process.env object thanks to dotenv const environmentFileContent = ` -export const environment = {${enablePS()}${enableMaintenace()}${enableGTAG()}}; +export const environment = {${enableMaintenace()}${enableGTAG()}}; `; // write the content to the respective file diff --git a/ui/src/app/@core/core.module.ts b/ui/src/app/@core/core.module.ts index 2c578de4a..3d6eefb9b 100644 --- a/ui/src/app/@core/core.module.ts +++ b/ui/src/app/@core/core.module.ts @@ -61,7 +61,7 @@ export const NB_CORE_PROVIDERS = [ }, }, register: { - terms: false, + terms: true, redirectDelay: 0, showMessages: { success: true, @@ -88,6 +88,9 @@ export const NB_CORE_PROVIDERS = [ minLength: 1, maxLength: 30, }, + tos: { + required: true, + } }, }, }).providers, diff --git a/ui/src/app/auth/auth.module.ts b/ui/src/app/auth/auth.module.ts index 6db72c065..c5093de8a 100644 --- a/ui/src/app/auth/auth.module.ts +++ b/ui/src/app/auth/auth.module.ts @@ -41,17 +41,16 @@ import { RegisterComponent } from 'app/auth/pages/register/register.component'; import { LogoutComponent } from 'app/auth/pages/logout/logout.component'; import { RequestPasswordComponent } from 'app/auth/pages/request-password/request-password.component'; import { ResetPasswordComponent } from 'app/auth/pages/reset-password/reset-password.component'; -import { PSModule } from '@pactsafe/pactsafe-angular-sdk'; @NgModule({ imports: [ - PSModule.forRoot(), CommonModule, NbLayoutModule, NbCheckboxModule, NbAlertModule, NbInputModule, NbButtonModule, + NbCheckboxModule, RouterModule, FormsModule, NbIconModule, diff --git a/ui/src/app/auth/pages/login/login.component.scss b/ui/src/app/auth/pages/login/login.component.scss index c8c9b0e0d..3f009f11c 100644 --- a/ui/src/app/auth/pages/login/login.component.scss +++ b/ui/src/app/auth/pages/login/login.component.scss @@ -53,6 +53,15 @@ top: 0; width: 600px; + input[type="checkbox"] { + height: 2rem; + padding: 0 1rem; + margin-top: 0.5rem; + margin-right: 0.5rem; + vertical-align: bottom !important; + position: relative; + } + a { color: #62d9ff; font-family: 'Montserrat', serif; diff --git a/ui/src/app/auth/pages/register/register.component.html b/ui/src/app/auth/pages/register/register.component.html index 0c6bdf614..c555fe44a 100644 --- a/ui/src/app/auth/pages/register/register.component.html +++ b/ui/src/app/auth/pages/register/register.component.html @@ -189,15 +189,21 @@

Create an account

- - +
+ + + +

+ You must agree to the terms of service! +

+
+
- +
+ + + +

+ Password confirmation is required! +

+

+ Password does not match the confirm password. +

+
+
- + + + + + + + + diff --git a/ui/src/app/auth/pages/reset-password/reset-password.component.scss b/ui/src/app/auth/pages/reset-password/reset-password.component.scss index 11a9b4e9f..9e11fbad8 100644 --- a/ui/src/app/auth/pages/reset-password/reset-password.component.scss +++ b/ui/src/app/auth/pages/reset-password/reset-password.component.scss @@ -1,3 +1,4 @@ +@import '../../../@theme/styles/themes'; /** * @license * Copyright Akveo. All Rights Reserved. @@ -8,3 +9,126 @@ margin-bottom: 3rem; } } + +.login-layout { + background: url('../../../../assets/images/login-background.png'); + transform: translateY(-2px); + + .orb-pane { + background-blend-mode: normal; + background-color: transparent; + display: flex; + margin: 0.2vh 350px 0.2vh 144px; + padding: 0; + + div { + max-width: 331px; + + img { + height: 119px; + margin-bottom: 47px; + width: 313px; + } + + p { + color: #969fb9; + font-family: 'Montserrat', serif; + font-size: 24px; + font-style: normal; + font-weight: 200 !important; + letter-spacing: 0; + line-height: 32px; + text-align: left; + } + + strong { + color: #fff; + font-family: 'Montserrat', serif; + font-size: 24px; + font-style: normal; + font-weight: 400 !important; + letter-spacing: 0; + line-height: 32px; + text-align: left; + } + } + } +} + +.form-pane { + height: 100%; + position: absolute; + right: 0; + top: 0; + width: 600px; + + a { + color: #62d9ff; + font-family: 'Montserrat', serif; + } + + .label { + color: #fff; + } + + .big { + font-size: 24px; + line-height: 32px; + } + + .bg { + background: #fff; + background-blend-mode: overlay; + height: 100%; + opacity: 0.2; + position: absolute; + width: 100%; + } + + .pane { + height: 100%; + padding: 0 6rem; + position: relative; + z-index: 3; + + span { + font-family: 'Montserrat', serif; + } + + p { + font-family: 'Montserrat', serif; + text-align: left; + } + + strong { + font-family: 'Montserrat', serif; + text-align: left; + } + + ul { + margin: 0; + padding: 0; + + li { + list-style: none; + margin: 0; + padding: 0; + } + } + + .alert-error { + background: linear-gradient(0deg, rgba(175, 80, 255, 0.25), rgba(175, 80, 255, 0.25)), #313e5d; + border: 1px solid #af50ff; + border-radius: 2px; + box-sizing: border-box; + } + } + + .password-icon { + color: nb-theme(color-basic-600); + float: right; + margin-right: 22px; + margin-top: -40px; + position: relative; + } +} diff --git a/ui/src/app/auth/pages/reset-password/reset-password.component.ts b/ui/src/app/auth/pages/reset-password/reset-password.component.ts index 4986718f0..a863e4f48 100644 --- a/ui/src/app/auth/pages/reset-password/reset-password.component.ts +++ b/ui/src/app/auth/pages/reset-password/reset-password.component.ts @@ -7,6 +7,7 @@ import { NbAuthService, NbResetPasswordComponent, } from '@nebular/auth'; +import { STRINGS } from 'assets/text/strings'; @Component({ selector: 'ngx-reset-password-page', @@ -15,6 +16,7 @@ import { changeDetection: ChangeDetectionStrategy.OnPush, }) export class ResetPasswordComponent extends NbResetPasswordComponent { + strings = STRINGS.login; redirectDelay: number = 0; diff --git a/ui/src/app/common/interfaces/orb/agent.policy.interface.ts b/ui/src/app/common/interfaces/orb/agent.policy.interface.ts index a59fa059f..cdb932037 100644 --- a/ui/src/app/common/interfaces/orb/agent.policy.interface.ts +++ b/ui/src/app/common/interfaces/orb/agent.policy.interface.ts @@ -13,6 +13,11 @@ export enum AgentPolicyStates { failedToApply = 'failed_to_apply', } +export enum AgentPolicyUsage { + inUse = 'in use', + notInUse = 'not in use', +} + export interface AgentPolicyState { id?: string; name?: string; @@ -77,6 +82,11 @@ export interface AgentPolicy extends OrbEntity { */ policy_data?: string; + /** + * Will say if the policy is in use or not + */ + policy_usage?: AgentPolicyUsage; + /** HELPERS */ datasets?: any[]; groups?: any[]; diff --git a/ui/src/app/common/interfaces/orb/filter-option.ts b/ui/src/app/common/interfaces/orb/filter-option.ts index 94e1ed483..ac959d094 100644 --- a/ui/src/app/common/interfaces/orb/filter-option.ts +++ b/ui/src/app/common/interfaces/orb/filter-option.ts @@ -43,6 +43,9 @@ export function filterNumber(item: any, prop: any, value: any) { } export function filterTags(item: any, prop: any, value: any, exact?: any) { + if (!item || typeof item[prop] !== 'object') { + return false; + } // tag values const values = Object.entries(item[prop]).map( (entry) => `${entry[0]}:${entry[1]}`, diff --git a/ui/src/app/common/interfaces/orb/sink.interface.ts b/ui/src/app/common/interfaces/orb/sink.interface.ts index 4f1b78c3e..21b7adf93 100644 --- a/ui/src/app/common/interfaces/orb/sink.interface.ts +++ b/ui/src/app/common/interfaces/orb/sink.interface.ts @@ -4,9 +4,9 @@ * [Sinks Architecture]{@link https://github.com/orb-community/orb/wiki/Architecture:-Sinks} */ -import { PrometheusConfig } from 'app/common/interfaces/orb/sink/config/prometheus.config.interface'; import { OrbEntity } from 'app/common/interfaces/orb/orb.entity.interface'; +import { OtlpConfig } from './sink/config/otlp.config.interface'; /** * @enum SinkStates @@ -23,6 +23,7 @@ export enum SinkStates { */ export enum SinkBackends { prometheus = 'prometheus', + otlp = 'otlphttp' } /** @@ -69,16 +70,16 @@ export interface Sink extends OrbEntity { config?: SinkTypes; } -export type SinkTypes = PrometheusConfig; +export type SinkTypes = OtlpConfig; /** * Prometheus Sink Type - * @type PromSink + * @type OtlpSink */ -export type PromSink = +export type OtlpSink = | Sink | { - config?: PrometheusConfig; + config?: OtlpConfig; }; /** diff --git a/ui/src/app/common/interfaces/orb/sink/config/otlp.config.interface.ts b/ui/src/app/common/interfaces/orb/sink/config/otlp.config.interface.ts new file mode 100644 index 000000000..4d191fce6 --- /dev/null +++ b/ui/src/app/common/interfaces/orb/sink/config/otlp.config.interface.ts @@ -0,0 +1,35 @@ +/** + * Oltp Sink Config Interface + * [Sinks Architecture]{@link https://github.com/orb-community/orb/wiki/Architecture:-Sinks} + */ +import { SinkConfig } from 'app/common/interfaces/orb/sink/sink.config.interface'; + +/** + * @interface OtlpConfig + */ +export interface OtlpConfig extends SinkConfig { + name: 'Otlp'; + + authentication: |any| { + /** + * Authentication type, "type": "basicauth" Default, can be ommitted + */ + type?: string; + /** + * Password {string} + */ + password?: string; + /** + * Username|Email(?) {string} + */ + username?: string; + } + exporter: |any| { + /** + * Endpoint (Otlp sinks) or Remote Host (Prometheus sink) Link {string} + */ + endpoint?: string; + remote_host?: string; + } + +} \ No newline at end of file diff --git a/ui/src/app/common/services/agents/agent.policies.service.ts b/ui/src/app/common/services/agents/agent.policies.service.ts index ab4bb2963..35fb9d7ca 100644 --- a/ui/src/app/common/services/agents/agent.policies.service.ts +++ b/ui/src/app/common/services/agents/agent.policies.service.ts @@ -70,6 +70,10 @@ export class AgentPoliciesService { return this.http .put(`${environment.agentPoliciesUrl}/${agentPolicy.id}`, agentPolicy) .catch((err) => { + this.notificationsService.error( + 'Failed to Edit Agent Policy', + `Error: ${err.status} - ${err.statusText}`, + ); return throwError(err); }); } diff --git a/ui/src/app/common/services/dataset/dataset.policies.service.ts b/ui/src/app/common/services/dataset/dataset.policies.service.ts index c1ec1a0ed..97697e5a4 100644 --- a/ui/src/app/common/services/dataset/dataset.policies.service.ts +++ b/ui/src/app/common/services/dataset/dataset.policies.service.ts @@ -40,10 +40,12 @@ export class DatasetPoliciesService { .get(`${environment.datasetPoliciesUrl}/${id}`) .pipe( catchError((err) => { - this.notificationsService.error( - 'Failed to fetch Dataset of this Policy', - `Error: ${err.status} - ${err.statusText}`, - ); + if (err.status !== 404 && err.error.error !== "non-existent entity") { + this.notificationsService.error( + 'Failed to fetch Dataset of this Policy', + `Error: ${err.status} - ${err.statusText}`, + ); + } err['id'] = id; return of(err); }), diff --git a/ui/src/app/common/services/filter.service.ts b/ui/src/app/common/services/filter.service.ts index 3fe36f530..329f8ee18 100644 --- a/ui/src/app/common/services/filter.service.ts +++ b/ui/src/app/common/services/filter.service.ts @@ -90,10 +90,12 @@ export class FilterService { const filterDef = filterOptions.find( (_item) => _item.name === _filter.name, ); - const {filter: filterFn, prop, exact} = filterDef; - const result = + if (filterDef) { + const {filter: filterFn, prop, exact} = filterDef; + const result = !!filterFn && filterFn(value, prop, paramValue, exact); - return result; + return result; + } }); }); diff --git a/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.html b/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.html index 0a53a6200..7253eb0a2 100644 --- a/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.html +++ b/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.html @@ -358,15 +358,24 @@

{{ isEdit ? 'Edit Agent Policy' : 'Create Agent Policy'}}

-
- - +
+
+

Paste or Upload your YAML configuration

+ + +
+
+ + +

diff --git a/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.scss b/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.scss index 21425334c..0af6c504f 100644 --- a/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.scss +++ b/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.scss @@ -170,7 +170,6 @@ ngx-tag-control, ngx-tag-display { .code-editor { height: calc(100%); width: calc(100%); - padding: calc(1rem); } .code-editor-wrapper { @@ -194,3 +193,17 @@ ngx-tag-control, ngx-tag-display { margin-top: 1rem; margin: 10px 0; } + +.upload-button { + color: #3089fc; + background-color: transparent; + border: none; + font-weight: 600; + outline: none; +} +.summary-accent { + color: #969fb9 !important; +} +.align { + margin: 0px; +} \ No newline at end of file diff --git a/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.ts b/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.ts index 17f9dd6de..6b96e9ff3 100644 --- a/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.ts +++ b/ui/src/app/pages/datasets/policies.agent/add/agent.policy.add.component.ts @@ -130,6 +130,8 @@ kind: collection`; selectedTags: Tags; + uploadIconKey = 'upload-outline' + constructor( private agentPoliciesService: AgentPoliciesService, private notificationsService: NotificationsService, @@ -453,7 +455,16 @@ kind: collection`; viewPolicy(id) { this.router.navigateByUrl(`/pages/datasets/policies/view/${id}`); } - + onFileSelected(event: any) { + const file: File = event.target.files[0]; + const reader: FileReader = new FileReader(); + + reader.onload = (e: any) => { + this.code = e.target.result; + }; + + reader.readAsText(file); + } onYAMLSubmit() { const payload = { name: this.detailsFG.controls.name.value, diff --git a/ui/src/app/pages/datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation.html b/ui/src/app/pages/datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation.html new file mode 100644 index 000000000..aad567c19 --- /dev/null +++ b/ui/src/app/pages/datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation.html @@ -0,0 +1,25 @@ + + + Duplicate Policy Confirmation + + + +

Are you sure you want to duplicate {{ policy }}?

+

*Please press the button to confirm. You will be redirected to the view page of the newly created policy.

+
+ + + +
\ No newline at end of file diff --git a/ui/src/app/pages/datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation.scss b/ui/src/app/pages/datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation.scss new file mode 100644 index 000000000..9b2c47f2a --- /dev/null +++ b/ui/src/app/pages/datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation.scss @@ -0,0 +1,51 @@ +nb-card { + max-width: 38rem !important; + + nb-card-header { + background: #232940 !important; + color: #969fb9 !important; + } + + nb-card-body { + margin: 2rem 3rem !important; + padding: 0 !important; + + p { + color: #969fb9 !important; + } + + .ns1-red { + color: #df316f !important; + } + } + + nb-card-footer { + text-align: center !important; + padding: 1.5rem !important; + } + } + + // ORB + ::ng-deep { + .orb-close-dialog { + background-color: #23294000 !important; + border-radius: 4px !important; + display: contents !important; + float: right !important; + + > span { + float: right; + font-size: 1.5rem !important; + color: #3089fc !important; + font-weight: 900 !important; + } + } + .duplicate-button { + background-color: #3089fc !important; + color: #ffffff !important; + } + } + +span { + color: #3089fc; +} \ No newline at end of file diff --git a/ui/src/app/pages/datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation.ts b/ui/src/app/pages/datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation.ts new file mode 100644 index 000000000..ce767ad28 --- /dev/null +++ b/ui/src/app/pages/datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation.ts @@ -0,0 +1,25 @@ +import { Component, Input } from '@angular/core'; +import { NbDialogRef } from '@nebular/theme'; + +@Component({ + selector: 'ngx-policy-duplicate-component', + templateUrl: './agent.policy.duplicate.confirmation.html', + styleUrls: ['./agent.policy.duplicate.confirmation.scss'], +}) + +export class PolicyDuplicateComponent { + @Input() policy: string + constructor( + protected dialogRef: NbDialogRef, + ) { + } + + onDuplicate() { + this.dialogRef.close(true); + } + + onClose() { + this.dialogRef.close(false); + } + +} \ No newline at end of file diff --git a/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.html b/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.html index d54b7e26d..4d5060dbf 100644 --- a/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.html +++ b/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.html @@ -16,7 +16,16 @@

All Policies

- +
+ + +
All Policies [rowHeight]="50" [rows]="filteredPolicies$ | async" [scrollbarV]="true" + [scrollbarH]="true" [sorts]="tableSorts" class="orb orb-table" + [selected]="selected" + [selectionType]="'checkbox'" >
@@ -106,7 +118,7 @@

All Policies

+ + + + + {{ value | titlecase }} + + + + + + + + + + diff --git a/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.scss b/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.scss index f68c359ec..383329cf7 100644 --- a/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.scss +++ b/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.scss @@ -187,3 +187,31 @@ mat-chip-list { animation-direction: alternate; } } +.orb-service-{ + &in { + color: #6fcf97; + } + ¬ { + color: #f2994a; + } +} +.scroll { + width: 1520px; +} +.options { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +} +.delete-selected { + color: #ffffff !important; + font-family: 'Montserrat', sans-serif; + font-weight: 600; + text-transform: none !important; + width: 90px; + background-color: #df316f !important; +} +input[type=checkbox] { + margin-left: 10px; +} \ No newline at end of file diff --git a/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.ts b/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.ts index 5d5d5b1b1..9725f1f5a 100644 --- a/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.ts +++ b/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.ts @@ -15,19 +15,24 @@ import { DatatableComponent, TableColumn, } from '@swimlane/ngx-datatable'; -import { AgentPolicy } from 'app/common/interfaces/orb/agent.policy.interface'; +import { AgentPolicy, AgentPolicyUsage } from 'app/common/interfaces/orb/agent.policy.interface'; import { filterNumber, FilterOption, filterString, filterTags, FilterTypes, + filterMultiSelect, } from 'app/common/interfaces/orb/filter-option'; import { AgentPoliciesService } from 'app/common/services/agents/agent.policies.service'; import { FilterService } from 'app/common/services/filter.service'; import { NotificationsService } from 'app/common/services/notifications/notifications.service'; import { OrbService } from 'app/common/services/orb.service'; import { AgentPolicyDeleteComponent } from 'app/pages/datasets/policies.agent/delete/agent.policy.delete.component'; -import { Observable } from 'rxjs'; +import { DeleteSelectedComponent } from 'app/shared/components/delete/delete.selected.component'; +import { combineLatest, Observable, Subscription } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; import { STRINGS } from '../../../../../assets/text/strings'; +import { PolicyDuplicateComponent } from '../duplicate/agent.policy.duplicate.confirmation'; + @Component({ selector: 'ngx-agent-policy-list-component', @@ -44,12 +49,22 @@ export class AgentPolicyListComponent loading = false; + selected: any[] = []; + + private policiesSubscription: Subscription; + @ViewChild('nameTemplateCell') nameTemplateCell: TemplateRef; @ViewChild('versionTemplateCell') versionTemplateCell: TemplateRef; @ViewChild('actionsTemplateCell') actionsTemplateCell: TemplateRef; + @ViewChild('usageStateTemplateCell') usageStateTemplateCell: TemplateRef; + + @ViewChild('checkboxTemplateCell') checkboxTemplateCell: TemplateRef; + + @ViewChild('checkboxTemplateHeader') checkboxTemplateHeader: TemplateRef; + tableSorts = [ { prop: 'name', @@ -81,8 +96,20 @@ export class AgentPolicyListComponent private orb: OrbService, private filters: FilterService, ) { - this.policies$ = this.orb.getPolicyListView(); this.filters$ = this.filters.getFilters(); + this.selected = []; + this.policies$ = combineLatest([ + this.orb.getPolicyListView(), + this.orb.getDatasetListView() + ]).pipe( + filter(([policies, datasets]) => policies !== undefined && policies !== null && datasets !== undefined && datasets !== null), + map(([policies, datasets]) => { + return policies.map((policy) => { + const dataset = datasets.filter((d) => d.valid && d.agent_policy_id === policy.id); + return { ...policy, policy_usage: dataset.length > 0 ? AgentPolicyUsage.inUse : AgentPolicyUsage.notInUse }; + }); + }) + ); this.filterOptions = [ { @@ -109,6 +136,13 @@ export class AgentPolicyListComponent filter: filterString, type: FilterTypes.Input, }, + { + name: 'Usage', + prop: 'policy_usage', + filter: filterMultiSelect, + type: FilterTypes.MultiSelect, + options: Object.values(AgentPolicyUsage).map((value) => value as string), + }, ]; this.filteredPolicies$ = this.filters.createFilteredList()( @@ -118,24 +152,40 @@ export class AgentPolicyListComponent ); } + onOpenDuplicatePolicy(agentPolicy: any) { + const policy = agentPolicy.name; + this.dialogService + .open(PolicyDuplicateComponent, { + context: { policy }, + autoFocus: true, + closeOnEsc: true, + }) + .onClose.subscribe((confirm) => { + if (confirm) { + this.duplicatePolicy(agentPolicy); + } + }) + } duplicatePolicy(agentPolicy: any) { this.agentPoliciesService - .duplicateAgentPolicy(agentPolicy.id) - .subscribe((newAgentPolicy) => { - if (newAgentPolicy?.id) { - this.notificationsService.success( - 'Agent Policy Duplicated', - `New Agent Policy Name: ${newAgentPolicy?.name}`, - ); - - this.router.navigate([`view/${newAgentPolicy.id}`], { - relativeTo: this.route, - }); - } - }); + .duplicateAgentPolicy(agentPolicy.id) + .subscribe((newAgentPolicy) => { + if (newAgentPolicy?.id) { + this.notificationsService.success( + 'Agent Policy Duplicated', + `New Agent Policy Name: ${newAgentPolicy?.name}`, + ); + this.router.navigate([`view/${newAgentPolicy.id}`], { + relativeTo: this.route, + }); + } + }); } - + ngOnDestroy(): void { + if (this.policiesSubscription) { + this.policiesSubscription.unsubscribe(); + } this.orb.killPolling.next(); } @@ -153,28 +203,48 @@ export class AgentPolicyListComponent } ngAfterViewInit() { + this.orb.refreshNow(); this.columns = [ + { + name: '', + prop: 'checkbox', + width: 1, + minWidth: 62, + canAutoResize: true, + sortable: false, + cellTemplate: this.checkboxTemplateCell, + headerTemplate: this.checkboxTemplateHeader, + }, { prop: 'name', name: 'Policy Name', - resizeable: false, + resizeable: true, canAutoResize: true, - flexGrow: 2, + width: 230, minWidth: 100, cellTemplate: this.nameTemplateCell, }, + { + prop: 'policy_usage', + name: 'Usage', + resizeable: true, + canAutoResize: true, + width: 130, + minWidth: 100, + cellTemplate: this.usageStateTemplateCell, + }, { prop: 'description', name: 'Description', - resizeable: false, - flexGrow: 1, + resizeable: true, + width: 280, minWidth: 100, cellTemplate: this.nameTemplateCell, }, { prop: 'tags', - flexGrow: 1, + width: 170, canAutoResize: true, name: 'Tags', minWidth: 150, @@ -192,8 +262,8 @@ export class AgentPolicyListComponent { prop: 'version', name: 'Version', - resizeable: false, - flexGrow: 1, + resizeable: true, + width: 100, minWidth: 50, cellTemplate: this.versionTemplateCell, }, @@ -205,16 +275,16 @@ export class AgentPolicyListComponent }, name: 'Last Modified', minWidth: 110, - flexGrow: 1, - resizeable: false, + width: 150, + resizeable: true, }, { name: '', prop: 'actions', - minWidth: 150, - resizeable: false, + minWidth: 200, + resizeable: true, sortable: false, - flexGrow: 1, + width: 150, cellTemplate: this.actionsTemplateCell, }, ]; @@ -261,4 +331,72 @@ export class AgentPolicyListComponent } }); } + onOpenDeleteSelected() { + const elementName = "Policies" + const selected = this.selected; + this.dialogService + .open(DeleteSelectedComponent, { + context: { selected, elementName }, + autoFocus: true, + closeOnEsc: true, + }) + .onClose.subscribe((confirm) => { + if (confirm) { + this.deleteSelectedAgentsPolicy(); + this.selected = []; + this.orb.refreshNow(); + } + }); + } + + deleteSelectedAgentsPolicy() { + this.selected.forEach((policy) => { + this.agentPoliciesService.deleteAgentPolicy(policy.id).subscribe(); + }) + this.notificationsService.success('All selected Policies delete requests succeeded', ''); + } + public onCheckboxChange(event: any, row: any): void { + const policySelected = { + id: row.id, + name: row.name, + usage: row.policy_usage, + } + if (this.getChecked(row) === false) { + this.selected.push(policySelected); + } + else { + for (let i = 0; i < this.selected.length; i++) { + if (this.selected[i].id === row.id) { + this.selected.splice(i, 1); + break; + } + } + } + } + + public getChecked(row: any): boolean { + const item = this.selected.filter((e) => e.id === row.id); + return item.length > 0 ? true : false; + } + + onHeaderCheckboxChange(event: any) { + if (event.target.checked && this.filteredPolicies$) { + this.policiesSubscription = this.filteredPolicies$.subscribe(rows => { + this.selected = []; + rows.forEach(row => { + const policySelected = { + id: row.id, + name: row.name, + usage: row.policy_usage, + } + this.selected.push(policySelected); + }); + }); + } else { + if (this.policiesSubscription) { + this.policiesSubscription.unsubscribe(); + } + this.selected = []; + } + } } diff --git a/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.html b/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.html index 315bd53b5..7fbad4da7 100644 --- a/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.html +++ b/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.html @@ -8,34 +8,25 @@

Policy View

- - - +
+ + + + Last Update: {{ lastUpdate | date: 'HH:mm:ss a' }} + + + + + +
diff --git a/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.scss b/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.scss index 6fa3e165c..a7c18bc61 100644 --- a/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.scss +++ b/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.scss @@ -194,3 +194,48 @@ button { } } } +.refresh-button { + border: none !important; + box-sizing: border-box; + box-shadow: none !important; + margin-right: 0.3rem; + + &:active, &:focus { + background-color: unset !important; + } + + &:hover { + background-color: rgba(143, 155, 179, 0.16); + } +} + +.paused { + animation: rotation 750ms linear infinite; + animation-play-state: paused; +} + +.rotate { + animation: rotation 750ms linear infinite; + animation-play-state: running; + + &:hover { + background: unset !important; + } +} + +@keyframes rotation { + from { + transform: rotate(0deg); + } + to { + transform: rotate(359deg); + } +} +.last-update { + color: #969fb9; + font-size: 14px; +} +.right-content { + display: flex; + justify-content: flex-end; +} diff --git a/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.ts b/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.ts index a90bb6bc3..c060f8eb0 100644 --- a/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.ts +++ b/ui/src/app/pages/datasets/policies.agent/view/agent.policy.view.component.ts @@ -25,6 +25,8 @@ import { Subscription } from 'rxjs'; import yaml from 'js-yaml'; import { AgentGroup } from 'app/common/interfaces/orb/agent.group.interface'; import { filter } from 'rxjs/operators'; +import { PolicyDuplicateComponent } from '../duplicate/agent.policy.duplicate.confirmation'; +import { NbDialogService } from '@nebular/theme'; @Component({ selector: 'ngx-agent-view', @@ -50,6 +52,8 @@ export class AgentPolicyViewComponent implements OnInit, OnDestroy, OnChanges { interface: false, }; + lastUpdate: Date | null = null; + @ViewChild(PolicyDetailsComponent) detailsComponent: PolicyDetailsComponent; @ViewChild(PolicyInterfaceComponent) @@ -62,21 +66,23 @@ export class AgentPolicyViewComponent implements OnInit, OnDestroy, OnChanges { private cdr: ChangeDetectorRef, private notifications: NotificationsService, private router: Router, + private dialogService: NbDialogService, ) {} ngOnInit() { - this.router.events - .pipe(filter((event: RouterEvent) => event instanceof NavigationEnd)) - .subscribe(() => { - this.fetchData(); - }); this.fetchData(); } - fetchData() { + fetchData(newPolicyId?: any) { this.isLoading = true; - this.policyId = this.route.snapshot.paramMap.get('id'); + if (newPolicyId) { + this.policyId = newPolicyId; + } + else { + this.policyId = this.route.snapshot.paramMap.get('id'); + } this.retrievePolicy(); + this.lastUpdate = new Date(); } ngOnChanges(): void { @@ -146,12 +152,12 @@ export class AgentPolicyViewComponent implements OnInit, OnDestroy, OnChanges { } as AgentPolicy; this.policiesService.editAgentPolicy(payload).subscribe((resp) => { + this.notifications.success('Agent Policy updated successfully', ''); this.discard(); this.policy = resp; this.fetchData(); }); - this.notifications.success('Agent Policy updated successfully', ''); } catch (err) { this.notifications.error( 'Failed to edit Agent Policy', @@ -171,21 +177,33 @@ export class AgentPolicyViewComponent implements OnInit, OnDestroy, OnChanges { this.cdr.markForCheck(); }); } - - duplicatePolicy() { - this.policiesService - .duplicateAgentPolicy(this.policyId || this.policy.id) - .subscribe((resp) => { - if (resp?.id) { - this.notifications.success( - 'Agent Policy Duplicated', - `New Agent Policy Name: ${resp?.name}`, - ); - this.router.navigate([`view/${resp.id}`], { - relativeTo: this.route.parent, - }); + onOpenDuplicatePolicy() { + const policy = this.policy.name; + this.dialogService + .open(PolicyDuplicateComponent, { + context: { policy }, + autoFocus: true, + closeOnEsc: true, + }) + .onClose.subscribe((confirm) => { + if (confirm) { + this.duplicatePolicy(this.policy); } - }); + }) + } + duplicatePolicy(agentPolicy: any) { + this.policiesService + .duplicateAgentPolicy(agentPolicy.id) + .subscribe((newAgentPolicy) => { + if (newAgentPolicy?.id) { + this.notifications.success( + 'Agent Policy Duplicated', + `New Agent Policy Name: ${newAgentPolicy?.name}`, + ); + this.router.navigateByUrl(`/pages/datasets/policies/view/${newAgentPolicy?.id}`); + this.fetchData(newAgentPolicy.id); + } + }); } ngOnDestroy() { diff --git a/ui/src/app/pages/fleet/agents/delete/agent.delete.component.html b/ui/src/app/pages/fleet/agents/delete/agent.delete.component.html index 9f8b2add3..969c8afe4 100644 --- a/ui/src/app/pages/fleet/agents/delete/agent.delete.component.html +++ b/ui/src/app/pages/fleet/agents/delete/agent.delete.component.html @@ -12,7 +12,7 @@ -

Are you sure you want to delete this Agent? This action cannot be undone.*

+

Are you sure you want to delete Agent {{name}}? This action cannot be undone.*

*To confirm, type your Agent name exactly as it appears.

+ pack="eva"> + {{ agent?.key }} @@ -33,9 +34,20 @@ for more advanced options.

-
{{ command2show }}
+
+      
+      
+      
+        {{ command2show }}
+      
+    
diff --git a/ui/src/app/pages/fleet/agents/key/agent.key.component.ts b/ui/src/app/pages/fleet/agents/key/agent.key.component.ts index 7f7362869..c6b5e6b76 100644 --- a/ui/src/app/pages/fleet/agents/key/agent.key.component.ts +++ b/ui/src/app/pages/fleet/agents/key/agent.key.component.ts @@ -19,6 +19,7 @@ export class AgentKeyComponent implements OnInit { key2copy: string; copyKeyIcon: string; + saveKeyIcon: string; @Input() agent: Agent = {}; @@ -34,6 +35,7 @@ export class AgentKeyComponent implements OnInit { this.key2copy = this.agent.key; this.copyCommandIcon = 'copy-outline'; this.copyKeyIcon = 'copy-outline'; + this.saveKeyIcon = 'save-outline'; } makeCommand2Copy() { @@ -65,4 +67,13 @@ orbcommunity/orb-agent`; onClose() { this.dialogRef.close(false); } + downloadCommand() { + const blob = new Blob([this.command2copy], { type: 'text/plain' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${this.agent.id}.txt`; + a.click(); + window.URL.revokeObjectURL(url); + } } diff --git a/ui/src/app/pages/fleet/agents/list/agent.list.component.html b/ui/src/app/pages/fleet/agents/list/agent.list.component.html index 4d6c5a246..8e20c44cd 100644 --- a/ui/src/app/pages/fleet/agents/list/agent.list.component.html +++ b/ui/src/app/pages/fleet/agents/list/agent.list.component.html @@ -15,7 +15,26 @@

All Agents

- +
+ + + +
+
All Agents [scrollbarV]="true" [sorts]="tableSorts" class="orb orb-table" + [selected]="selected" + [selectionType]="'checkbox'" >
@@ -144,3 +165,22 @@

All Agents

+ + + + + + + + + + + + {{ value }} + diff --git a/ui/src/app/pages/fleet/agents/list/agent.list.component.scss b/ui/src/app/pages/fleet/agents/list/agent.list.component.scss index 26b224e2b..ed9f51ff2 100644 --- a/ui/src/app/pages/fleet/agents/list/agent.list.component.scss +++ b/ui/src/app/pages/fleet/agents/list/agent.list.component.scss @@ -168,13 +168,45 @@ tr div p { &online, &healthy { color: #6fcf97; } - &stale, &none { + &stale { color: #f2994a; } &error, &failure { color: #df316f; } - &offline { + &offline, &none { color: #969fb9; } } +input[type=checkbox] { + margin-left: 10px; +} +.agent-reset { + color: #fff !important; + font-family: 'Montserrat', sans-serif; + font-weight: 600; + text-transform: none !important; + width: 125px; + + &.btn-disabled { + background: #2b3148; + } + + &:not(.btn-disabled) { + background-color: #3089fc !important; + } +} +.options { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +} +.delete-selected { + color: #ffffff !important; + font-family: 'Montserrat', sans-serif; + font-weight: 600; + text-transform: none !important; + width: 90px; + background-color: #df316f !important; +} \ No newline at end of file diff --git a/ui/src/app/pages/fleet/agents/list/agent.list.component.ts b/ui/src/app/pages/fleet/agents/list/agent.list.component.ts index 7f21c61a5..7ee7c0e45 100644 --- a/ui/src/app/pages/fleet/agents/list/agent.list.component.ts +++ b/ui/src/app/pages/fleet/agents/list/agent.list.component.ts @@ -28,8 +28,11 @@ import { NotificationsService } from 'app/common/services/notifications/notifica import { OrbService } from 'app/common/services/orb.service'; import { AgentDeleteComponent } from 'app/pages/fleet/agents/delete/agent.delete.component'; import { AgentDetailsComponent } from 'app/pages/fleet/agents/details/agent.details.component'; +import { DeleteSelectedComponent } from 'app/shared/components/delete/delete.selected.component'; import { STRINGS } from 'assets/text/strings'; -import { Observable } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; +import { AgentResetComponent } from '../reset/agent.reset.component'; @Component({ selector: 'ngx-agent-list-component', @@ -45,6 +48,15 @@ export class AgentListComponent implements AfterViewInit, AfterViewChecked, OnDe loading = false; + selected: any[] = []; + + canResetAgents: boolean; + + isResetting: boolean; + + private agentsSubscription: Subscription; + + // templates @ViewChild('agentNameTemplateCell') agentNameTemplateCell: TemplateRef; @@ -56,9 +68,15 @@ export class AgentListComponent implements AfterViewInit, AfterViewChecked, OnDe @ViewChild('actionsTemplateCell') actionsTemplateCell: TemplateRef; + @ViewChild('checkboxTemplateCell') checkboxTemplateCell: TemplateRef; + @ViewChild('agentLastActivityTemplateCell') agentLastActivityTemplateCell: TemplateRef; + @ViewChild('agentVersionTemplateCell') agentVersionTemplateCell: TemplateRef; + + @ViewChild('checkboxTemplateHeader') checkboxTemplateHeader: TemplateRef; + tableSorts = [ { prop: 'name', @@ -84,8 +102,28 @@ export class AgentListComponent implements AfterViewInit, AfterViewChecked, OnDe private router: Router, private orb: OrbService, private filters: FilterService, + protected agentsService: AgentsService, + protected notificationService: NotificationsService, ) { - this.agents$ = this.orb.getAgentListView(); + this.isResetting = false; + this.selected = []; + this.agents$ = this.orb.getAgentListView().pipe( + map(agents => { + return agents.map(agent => { + let version: string; + if (agent.state !== 'new') { + version = agent.agent_metadata.orb_agent.version; + } else { + version = '-'; + } + return { + ...agent, + version, + }; + }); + }) + ); + this.columns = []; this.filters$ = this.filters.getFilters(); @@ -118,6 +156,12 @@ export class AgentListComponent implements AfterViewInit, AfterViewChecked, OnDe type: FilterTypes.MultiSelect, options: Object.values(AgentPolicyAggStates).map((value) => value as string), }, + { + name: 'Version', + prop: 'version', + filter: filterString, + type: FilterTypes.Input, + }, ]; this.filteredAgents$ = this.filters.createFilteredList()( @@ -128,6 +172,9 @@ export class AgentListComponent implements AfterViewInit, AfterViewChecked, OnDe } ngOnDestroy() { + if (this.agentsSubscription) { + this.agentsSubscription.unsubscribe(); + } this.orb.killPolling.next(); } @@ -147,32 +194,46 @@ export class AgentListComponent implements AfterViewInit, AfterViewChecked, OnDe ngAfterViewInit() { this.orb.refreshNow(); this.columns = [ + { + name: '', + prop: 'checkbox', + width: 1, + minWidth: 62, + canAutoResize: true, + sortable: false, + cellTemplate: this.checkboxTemplateCell, + headerTemplate: this.checkboxTemplateHeader, + }, { prop: 'name', - flexGrow: 4, + width: 250, canAutoResize: true, minWidth: 150, name: 'Name', cellTemplate: this.agentNameTemplateCell, + resizeable: true, }, { prop: 'state', - flexGrow: 2, + width: 100, + minWidth: 90, canAutoResize: true, name: 'Status', cellTemplate: this.agentStateTemplateRef, + resizeable: true, }, { prop: 'policy_agg_info', - flexGrow: 4, + width: 170, canAutoResize: true, minWidth: 150, name: 'Policies', cellTemplate: this.agentPolicyStateTemplateRef, + resizeable: true, }, { prop: 'combined_tags', - flexGrow: 9, + width: 300, canAutoResize: true, name: 'Tags', cellTemplate: this.agentTagsTemplateCell, @@ -185,28 +246,79 @@ export class AgentListComponent implements AfterViewInit, AfterViewChecked, OnDe .map(([key, value]) => `${key}:${value}`) .join(','), ), + resizeable: true, + }, + { + prop: 'version', + width: 200, + minWidth: 150, + canAutoResize: true, + name: 'Version', + sortable: true, + cellTemplate: this.agentVersionTemplateCell, + resizeable: true, }, { prop: 'ts_last_hb', - flexGrow: 2, + width: 150, minWidth: 150, canAutoResize: true, name: 'Last Activity', - sortable: false, + sortable: true, cellTemplate: this.agentLastActivityTemplateCell, + resizeable: true, }, { name: '', prop: 'actions', - flexGrow: 3, + width: 150, minWidth: 150, canAutoResize: true, sortable: false, cellTemplate: this.actionsTemplateCell, + resizeable: true, }, ]; } + + public onCheckboxChange(event: any, row: any): void { + let selectedAgent = { + id: row.id, + resetable: true, + name: row.name, + state: row.state, + } + if (this.getChecked(row) === false) { + let resetable = true; + if (row.state === 'new' || row.state === 'offline') { + resetable = false; + } + selectedAgent.resetable = resetable; + this.selected.push(selectedAgent); + } else { + for (let i = 0; i < this.selected.length; i++) { + if (this.selected[i].id === row.id) { + this.selected.splice(i, 1); + break; + } + } + } + const reset = this.selected.filter((e) => e.resetable === false); + this.canResetAgents = reset.length > 0 ? true : false; + } + + + + public getChecked(row: any): boolean { + const item = this.selected.filter((e) => e.id === row.id); + return item.length > 0 ? true : false; + } + + notifyResetSuccess() { + this.notificationService.success('All Agents Resets Requested', ''); + } + onOpenView(agent: any) { this.router.navigate([`view/${agent.id}`], { relativeTo: this.route, @@ -243,6 +355,81 @@ export class AgentListComponent implements AfterViewInit, AfterViewChecked, OnDe } }); } + onOpenDeleteSelected() { + const selected = this.selected; + const elementName = "Agents" + this.dialogService + .open(DeleteSelectedComponent, { + context: { selected, elementName }, + autoFocus: true, + closeOnEsc: true, + }) + .onClose.subscribe((confirm) => { + if (confirm) { + this.deleteSelectedAgents(); + this.selected = []; + this.orb.refreshNow(); + } + }); + } + + deleteSelectedAgents() { + this.selected.forEach((agent) => { + this.agentService.deleteAgent(agent.id).subscribe(); + }) + this.notificationsService.success('All selected Agents delete requests succeeded', ''); + } + + onOpenResetAgents() { + const size = this.selected.length; + this.dialogService + .open(AgentResetComponent, { + context: { size }, + autoFocus: true, + closeOnEsc: true, + }) + .onClose.subscribe((confirm) => { + if (confirm) { + this.resetAgents(); + this.orb.refreshNow(); + } + }) + } + resetAgents() { + if (!this.isResetting) { + this.isResetting = true; + this.selected.forEach((agent) => { + this.agentService.resetAgent(agent.id).subscribe(); + }) + this.notifyResetSuccess(); + this.selected = []; + this.isResetting = false; + } + } + + onHeaderCheckboxChange(event: any) { + if (event.target.checked && this.filteredAgents$) { + this.agentsSubscription = this.filteredAgents$.subscribe(rows => { + this.selected = []; + rows.forEach(row => { + const policySelected = { + id: row.id, + name: row.name, + state: row.state, + resetable: row.state === 'new' || row.state === 'offline' ? false : true, + } + this.selected.push(policySelected); + }); + }); + } else { + if (this.agentsSubscription) { + this.agentsSubscription.unsubscribe(); + } + this.selected = []; + } + const reset = this.selected.filter((e) => e.resetable === false); + this.canResetAgents = reset.length > 0 ? true : false; + } openDetailsModal(row: any) { this.dialogService diff --git a/ui/src/app/pages/fleet/agents/match/agent.match.component.html b/ui/src/app/pages/fleet/agents/match/agent.match.component.html index 3d1ed1cd3..9dbd339fa 100644 --- a/ui/src/app/pages/fleet/agents/match/agent.match.component.html +++ b/ui/src/app/pages/fleet/agents/match/agent.match.component.html @@ -62,8 +62,12 @@ - - - {{ value | titlecase }} + + {{ value | titlecase }} + + + {{ value | titlecase }} + \ No newline at end of file diff --git a/ui/src/app/pages/fleet/agents/match/agent.match.component.scss b/ui/src/app/pages/fleet/agents/match/agent.match.component.scss index f1375c70f..ad7b3c14a 100644 --- a/ui/src/app/pages/fleet/agents/match/agent.match.component.scss +++ b/ui/src/app/pages/fleet/agents/match/agent.match.component.scss @@ -32,13 +32,13 @@ nb-card { &new { color: #9b51e0; } - &online { + &online, &running { color: #6fcf97; } &stale { color: #f2994a; } - &error { + &error, &failed_to_apply { color: #df316f; } &offline { diff --git a/ui/src/app/pages/fleet/agents/match/agent.match.component.ts b/ui/src/app/pages/fleet/agents/match/agent.match.component.ts index 2a6659c6f..f2273ad6c 100644 --- a/ui/src/app/pages/fleet/agents/match/agent.match.component.ts +++ b/ui/src/app/pages/fleet/agents/match/agent.match.component.ts @@ -3,11 +3,11 @@ import { NbDialogRef } from '@nebular/theme'; import { STRINGS } from 'assets/text/strings'; import { ColumnMode, TableColumn } from '@swimlane/ngx-datatable'; import { DropdownFilterItem } from 'app/common/interfaces/mainflux.interface'; -import { Agent } from 'app/common/interfaces/orb/agent.interface'; +import { Agent, AgentPolicyAggStates } from 'app/common/interfaces/orb/agent.interface'; import { AgentGroup } from 'app/common/interfaces/orb/agent.group.interface'; import { AgentsService } from 'app/common/services/agents/agents.service'; import { Router } from '@angular/router'; -import { AgentPolicy } from 'app/common/interfaces/orb/agent.policy.interface'; +import { AgentPolicy, AgentPolicyStates } from 'app/common/interfaces/orb/agent.policy.interface'; @Component({ selector: 'ngx-agent-match-component', @@ -32,6 +32,10 @@ export class AgentMatchComponent implements OnInit, AfterViewInit { columns: TableColumn[]; + statePolicy: AgentPolicyAggStates; + + specificPolicy: boolean; + // templates @ViewChild('agentNameTemplateCell') agentNameTemplateCell: TemplateRef; @@ -39,7 +43,7 @@ export class AgentMatchComponent implements OnInit, AfterViewInit { @ViewChild('agentStateTemplateCell') agentStateTemplateRef: TemplateRef; - @ViewChild('agentEspecificPolicyStateTemplateCell') agentEspecificPolicyStateTemplateRef: TemplateRef; + @ViewChild('agentSpecificPolicyStateTemplateCell') agentSpecificPolicyStateTemplateRef: TemplateRef; tableFilters: DropdownFilterItem[] = [ { @@ -61,6 +65,7 @@ export class AgentMatchComponent implements OnInit, AfterViewInit { protected agentsService: AgentsService, protected router: Router, ) { + this.specificPolicy = false; } ngOnInit() { @@ -116,7 +121,7 @@ export class AgentMatchComponent implements OnInit, AfterViewInit { flexGrow: 3, canAutoResize: true, minWidth: 150, - cellTemplate: this.agentEspecificPolicyStateTemplateRef, + cellTemplate: this.agentSpecificPolicyStateTemplateRef, }, ]; } @@ -131,11 +136,11 @@ export class AgentMatchComponent implements OnInit, AfterViewInit { const tagsList = Object.keys(tags).map(key => ({ [key]: tags[key] })); this.agentsService.getAllAgents(tagsList).subscribe( resp => { - if(!!this.policy){ - this.agents = resp.map((agent)=>{ + if(!!this.policy) { + this.specificPolicy = true; + this.agents = resp.map((agent) => { const {policy_state} = agent; - const policy_agg_info = !!policy_state && policy_state[this.policy.id].state || "Not Applied"; - + const policy_agg_info = !!policy_state && policy_state[this.policy.id].state || AgentPolicyStates.failedToApply; return {...agent, policy_agg_info }; }) } else { diff --git a/ui/src/app/pages/fleet/agents/reset/agent.reset.component.html b/ui/src/app/pages/fleet/agents/reset/agent.reset.component.html new file mode 100644 index 000000000..6a7488242 --- /dev/null +++ b/ui/src/app/pages/fleet/agents/reset/agent.reset.component.html @@ -0,0 +1,36 @@ + + + Reset Agents Confirmation + + + +

Are you sure you want to reset a total of {{ size }} Agents?

+

*To confirm, type the amount of agents to be reset.

+ + + {{size}} + +
+ + + +
\ No newline at end of file diff --git a/ui/src/app/pages/fleet/agents/reset/agent.reset.component.scss b/ui/src/app/pages/fleet/agents/reset/agent.reset.component.scss new file mode 100644 index 000000000..4f28bb2d8 --- /dev/null +++ b/ui/src/app/pages/fleet/agents/reset/agent.reset.component.scss @@ -0,0 +1,51 @@ +nb-card { + max-width: 38rem !important; + + nb-card-header { + background: #232940 !important; + color: #969fb9 !important; + } + + nb-card-body { + margin: 2rem 3rem !important; + padding: 0 !important; + + p { + color: #969fb9 !important; + } + + .ns1-red { + color: #df316f !important; + } + } + + nb-card-footer { + text-align: center !important; + padding: 1.5rem !important; + } + } + + // ORB + ::ng-deep { + .orb-close-dialog { + background-color: #23294000 !important; + border-radius: 4px !important; + display: contents !important; + float: right !important; + + > span { + float: right; + font-size: 1.5rem !important; + color: #3089fc !important; + font-weight: 900 !important; + } + } + + .orb-sink-delete-warning-button:not(.btn-disabled) { + background-color: #3089fc !important; + color: #ffffff !important; + } + } + .ns1red { + color: #df316f !important; + } \ No newline at end of file diff --git a/ui/src/app/pages/fleet/agents/reset/agent.reset.component.ts b/ui/src/app/pages/fleet/agents/reset/agent.reset.component.ts new file mode 100644 index 000000000..59ec7a923 --- /dev/null +++ b/ui/src/app/pages/fleet/agents/reset/agent.reset.component.ts @@ -0,0 +1,33 @@ +import { Component, Input } from '@angular/core'; +import { NbDialogRef } from '@nebular/theme'; +import { STRINGS } from 'assets/text/strings'; + +@Component({ + selector: 'ngx-agent-reset-component', + templateUrl: './agent.reset.component.html', + styleUrls: ['./agent.reset.component.scss'], +}) + +export class AgentResetComponent { + strings = STRINGS.agents; + @Input() size: Number; + + validationInput: Number; + + constructor( + protected dialogRef: NbDialogRef, + ) { + } + + onDelete() { + this.dialogRef.close(true); + } + + onClose() { + this.dialogRef.close(false); + } + + isEnabled(): boolean { + return this.validationInput === this.size; + } +} \ No newline at end of file diff --git a/ui/src/app/pages/fleet/agents/view/agent.view.component.html b/ui/src/app/pages/fleet/agents/view/agent.view.component.html index c0ea7b1bb..3cd7c0e11 100644 --- a/ui/src/app/pages/fleet/agents/view/agent.view.component.html +++ b/ui/src/app/pages/fleet/agents/view/agent.view.component.html @@ -1,14 +1,21 @@ -
-
+
+

Agent View

-
- - {{ agent?.state | ngxCapitalize }}. +
+ + + + Last Update: {{ lastUpdate | date: 'HH:mm:ss a' }} + +
+
+ + {{ agent?.state | ngxCapitalize }}.   Last activity @@ -23,6 +30,7 @@
+
diff --git a/ui/src/app/pages/fleet/agents/view/agent.view.component.scss b/ui/src/app/pages/fleet/agents/view/agent.view.component.scss index 4ef228886..e001af077 100644 --- a/ui/src/app/pages/fleet/agents/view/agent.view.component.scss +++ b/ui/src/app/pages/fleet/agents/view/agent.view.component.scss @@ -1,3 +1,18 @@ +header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.left-content { + flex: 1; +} + +.right-content { + display: flex; + align-items: center; +} + h4 { font-family: 'Montserrat', sans-serif; font-size: 1.5rem; @@ -142,3 +157,44 @@ nb-card { color: #df316f !important; } } +.refresh-button { + border: none !important; + box-sizing: border-box; + box-shadow: none !important; + margin-right: 0.3rem; + + &:active, &:focus { + background-color: unset !important; + } + + &:hover { + background-color: rgba(143, 155, 179, 0.16); + } +} + +.paused { + animation: rotation 750ms linear infinite; + animation-play-state: paused; +} + +.rotate { + animation: rotation 750ms linear infinite; + animation-play-state: running; + + &:hover { + background: unset !important; + } +} + +@keyframes rotation { + from { + transform: rotate(0deg); + } + to { + transform: rotate(359deg); + } +} +.last-update { + color: #969fb9; + font-size: 14px; +} diff --git a/ui/src/app/pages/fleet/agents/view/agent.view.component.ts b/ui/src/app/pages/fleet/agents/view/agent.view.component.ts index 18b4d6235..4bfd43204 100644 --- a/ui/src/app/pages/fleet/agents/view/agent.view.component.ts +++ b/ui/src/app/pages/fleet/agents/view/agent.view.component.ts @@ -6,7 +6,8 @@ import { Dataset } from 'app/common/interfaces/orb/dataset.policy.interface'; import { AgentsService } from 'app/common/services/agents/agents.service'; import { OrbService } from 'app/common/services/orb.service'; import { STRINGS } from 'assets/text/strings'; -import { Subscription } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; +import { shareReplay } from 'rxjs/operators'; @Component({ selector: 'ngx-agent-view', @@ -14,6 +15,9 @@ import { Subscription } from 'rxjs'; styleUrls: ['./agent.view.component.scss'], }) export class AgentViewComponent implements OnInit, OnDestroy { + + lastUpdate: Date | null = null; + strings = STRINGS.agents; agentStates = AgentStates; @@ -63,6 +67,7 @@ export class AgentViewComponent implements OnInit, OnDestroy { }, }); this.isLoading = true; + this.lastUpdate = new Date(); } isToday() { @@ -79,4 +84,8 @@ export class AgentViewComponent implements OnInit, OnDestroy { ngOnDestroy() { this.agentSubscription?.unsubscribe(); } + refreshAgent() { + this.isLoading = true; + this.retrieveAgent(); + } } diff --git a/ui/src/app/pages/fleet/groups/add/agent.group.add.component.ts b/ui/src/app/pages/fleet/groups/add/agent.group.add.component.ts index 5220af89d..b63301529 100644 --- a/ui/src/app/pages/fleet/groups/add/agent.group.add.component.ts +++ b/ui/src/app/pages/fleet/groups/add/agent.group.add.component.ts @@ -279,7 +279,6 @@ export class AgentGroupAddComponent const payload = this.wrapPayload(false); // // remove line bellow - // console.log(payload) if (this.isEdit) { this.agentGroupsService .editAgentGroup({ ...payload, id: this.agentGroupID }) diff --git a/ui/src/app/pages/fleet/groups/delete/agent.group.delete.component.html b/ui/src/app/pages/fleet/groups/delete/agent.group.delete.component.html index 12660f26d..9c17d6634 100644 --- a/ui/src/app/pages/fleet/groups/delete/agent.group.delete.component.html +++ b/ui/src/app/pages/fleet/groups/delete/agent.group.delete.component.html @@ -10,7 +10,9 @@ -

{{strings.delete.body}}*

+

Are you sure you want to delete Agent Group {{name}}? This may cause Datasets which + use this Agent Group to become invalid. + This action cannot be undone.*

{{strings.delete.warning}}

diff --git a/ui/src/app/pages/fleet/groups/list/agent.group.list.component.html b/ui/src/app/pages/fleet/groups/list/agent.group.list.component.html index 5f842bcd8..70681af7e 100644 --- a/ui/src/app/pages/fleet/groups/list/agent.group.list.component.html +++ b/ui/src/app/pages/fleet/groups/list/agent.group.list.component.html @@ -16,7 +16,16 @@

{{ strings.list.header }}

- +
+ + +
{{ strings.list.header }} [rowHeight]="50" [rows]="filteredGroups$ | async" [scrollbarV]="true" + [scrollbarH]="true" [sorts]="tableSorts" class="orb orb-table" + [selected]="selected" + [selectionType]="'checkbox'" >
@@ -121,3 +133,12 @@

{{ strings.list.header }}

+ + + + + + + + diff --git a/ui/src/app/pages/fleet/groups/list/agent.group.list.component.scss b/ui/src/app/pages/fleet/groups/list/agent.group.list.component.scss index 16ab47381..c65dc661e 100644 --- a/ui/src/app/pages/fleet/groups/list/agent.group.list.component.scss +++ b/ui/src/app/pages/fleet/groups/list/agent.group.list.component.scss @@ -181,3 +181,20 @@ mat-chip-list { transform: translateX(-70%); } } +.options { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +} +.delete-selected { + color: #ffffff !important; + font-family: 'Montserrat', sans-serif; + font-weight: 600; + text-transform: none !important; + width: 90px; + background-color: #df316f !important; +} +input[type=checkbox] { + margin-left: 10px; +} \ No newline at end of file diff --git a/ui/src/app/pages/fleet/groups/list/agent.group.list.component.ts b/ui/src/app/pages/fleet/groups/list/agent.group.list.component.ts index c37b597a9..46c40e338 100644 --- a/ui/src/app/pages/fleet/groups/list/agent.group.list.component.ts +++ b/ui/src/app/pages/fleet/groups/list/agent.group.list.component.ts @@ -29,8 +29,9 @@ import { OrbService } from 'app/common/services/orb.service'; import { AgentMatchComponent } from 'app/pages/fleet/agents/match/agent.match.component'; import { AgentGroupDeleteComponent } from 'app/pages/fleet/groups/delete/agent.group.delete.component'; import { AgentGroupDetailsComponent } from 'app/pages/fleet/groups/details/agent.group.details.component'; +import { DeleteSelectedComponent } from 'app/shared/components/delete/delete.selected.component'; import { STRINGS } from 'assets/text/strings'; -import { Observable } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; @Component({ selector: 'ngx-agent-group-list-component', @@ -49,6 +50,10 @@ export class AgentGroupListComponent searchPlaceholder = 'Search by name'; + selected: any[] = []; + + private groupsSubscription: Subscription; + // templates @ViewChild('agentGroupNameTemplateCell') agentGroupNameTemplateCell: TemplateRef; @@ -61,6 +66,10 @@ export class AgentGroupListComponent @ViewChild('actionsTemplateCell') actionsTemplateCell: TemplateRef; + @ViewChild('checkboxTemplateCell') checkboxTemplateCell: TemplateRef; + + @ViewChild('checkboxTemplateHeader') checkboxTemplateHeader: TemplateRef; + filterValue = null; tableSorts = [ @@ -91,6 +100,8 @@ export class AgentGroupListComponent private orb: OrbService, private filters: FilterService, ) { + this.selected = []; + this.groups$ = this.orb.getGroupListView(); this.filters$ = this.filters.getFilters(); @@ -132,6 +143,9 @@ export class AgentGroupListComponent } ngOnDestroy(): void { + if (this.groupsSubscription) { + this.groupsSubscription.unsubscribe(); + } this.orb.killPolling.next(); } @@ -151,40 +165,49 @@ export class AgentGroupListComponent ngAfterViewInit() { this.orb.refreshNow(); this.columns = [ + { + name: '', + prop: 'checkbox', + width: 1, + minWidth: 62, + canAutoResize: true, + sortable: false, + cellTemplate: this.checkboxTemplateCell, + headerTemplate: this.checkboxTemplateHeader, + }, { prop: 'name', name: 'Name', - flexGrow: 1, + width: 230, canAutoResize: true, - resizeable: false, + resizeable: true, minWidth: 150, cellTemplate: this.agentGroupNameTemplateCell, }, { prop: 'description', name: 'Description', - flexGrow: 2, + width: 350, canAutoResize: true, - resizeable: false, + resizeable: true, minWidth: 180, cellTemplate: this.agentGroupNameTemplateCell, }, { prop: 'matching_agents', name: 'Agents', - flexGrow: 1, + width: 150, canAutoResize: true, - resizeable: false, + resizeable: true, minWidth: 80, comparator: (a, b) => a.total - b.total, cellTemplate: this.agentGroupsTemplateCell, }, { prop: 'tags', - name: 'Tags', - flexGrow: 3, + width: 450, canAutoResize: true, - resizeable: false, + resizeable: true, cellTemplate: this.agentGroupTagsTemplateCell, comparator: (a, b) => Object.entries(a) @@ -199,8 +222,8 @@ export class AgentGroupListComponent { name: '', prop: 'actions', - flexGrow: 2, - canAutoResize: true, + width: 150, + resizeable: true, minWidth: 150, sortable: false, cellTemplate: this.actionsTemplateCell, @@ -241,7 +264,30 @@ export class AgentGroupListComponent } }); } + onOpenDeleteSelected() { + const selected = this.selected; + const elementName = "Agent Groups" + this.dialogService + .open(DeleteSelectedComponent, { + context: { selected, elementName }, + autoFocus: true, + closeOnEsc: true, + }) + .onClose.subscribe((confirm) => { + if (confirm) { + this.deleteSelectedAgentGroups(); + this.selected = []; + this.orb.refreshNow(); + } + }); + } + deleteSelectedAgentGroups() { + this.selected.forEach((group) => { + this.agentGroupsService.deleteAgentGroup(group.id).subscribe(); + }) + this.notificationsService.success('All selected Groups delete requests succeeded', ''); + } openDetailsModal(row: any) { this.dialogService .open(AgentGroupDetailsComponent, { @@ -263,4 +309,46 @@ export class AgentGroupListComponent closeOnEsc: true, }); } + public onCheckboxChange(event: any, row: any): void { + let selectedGroup = { + id: row.id, + name: row.name, + } + if (this.getChecked(row) === false) { + this.selected.push(selectedGroup); + } + else { + for (let i = 0; i < this.selected.length; i++) { + if (this.selected[i].id === row.id) { + this.selected.splice(i, 1); + break; + } + } + } + } + + public getChecked(row: any): boolean { + const item = this.selected.filter((e) => e.id === row.id); + return item.length > 0 ? true : false; + } + + onHeaderCheckboxChange(event: any) { + if (event.target.checked && this.filteredGroups$) { + this.groupsSubscription = this.filteredGroups$.subscribe(rows => { + this.selected = []; + rows.forEach(row => { + const policySelected = { + id: row.id, + name: row.name, + } + this.selected.push(policySelected); + }); + }); + } else { + if (this.groupsSubscription) { + this.groupsSubscription.unsubscribe(); + } + this.selected = []; + } + } } diff --git a/ui/src/app/pages/pages-routing.module.ts b/ui/src/app/pages/pages-routing.module.ts index 96ba9881b..27830bf10 100644 --- a/ui/src/app/pages/pages-routing.module.ts +++ b/ui/src/app/pages/pages-routing.module.ts @@ -11,7 +11,7 @@ import { AgentPolicyListComponent } from 'app/pages/datasets/policies.agent/list import { DatasetListComponent } from 'app/pages/datasets/list/dataset.list.component'; // Sink Management import { SinkListComponent } from 'app/pages/sinks/list/sink.list.component'; -import { SinkAddComponent } from 'app/pages/sinks/add/sink.add.component'; +import { SinkAddComponent } from 'app/pages/sinks/add/sink-add.component'; // Fleet Management import { AgentListComponent } from 'app/pages/fleet/agents/list/agent.list.component'; import { AgentAddComponent } from 'app/pages/fleet/agents/add/agent.add.component'; diff --git a/ui/src/app/pages/pages.module.ts b/ui/src/app/pages/pages.module.ts index 62904cb14..eb3d31a38 100644 --- a/ui/src/app/pages/pages.module.ts +++ b/ui/src/app/pages/pages.module.ts @@ -39,6 +39,7 @@ import { AgentPolicyDeleteComponent } from 'app/pages/datasets/policies.agent/de import { AgentPolicyDetailsComponent } from 'app/pages/datasets/policies.agent/details/agent.policy.details.component'; import { AgentPolicyListComponent } from 'app/pages/datasets/policies.agent/list/agent.policy.list.component'; import { AgentPolicyViewComponent } from 'app/pages/datasets/policies.agent/view/agent.policy.view.component'; + import { AgentAddComponent } from 'app/pages/fleet/agents/add/agent.add.component'; import { AgentDeleteComponent } from 'app/pages/fleet/agents/delete/agent.delete.component'; import { AgentDetailsComponent } from 'app/pages/fleet/agents/details/agent.details.component'; @@ -49,8 +50,9 @@ import { AgentGroupAddComponent } from 'app/pages/fleet/groups/add/agent.group.a import { AgentGroupDeleteComponent } from 'app/pages/fleet/groups/delete/agent.group.delete.component'; import { AgentGroupDetailsComponent } from 'app/pages/fleet/groups/details/agent.group.details.component'; import { AgentGroupListComponent } from 'app/pages/fleet/groups/list/agent.group.list.component'; +import { AgentResetComponent } from 'app/pages/fleet/agents/reset/agent.reset.component'; import { ShowcaseComponent } from 'app/pages/showcase/showcase.component'; -import { SinkAddComponent } from 'app/pages/sinks/add/sink.add.component'; +import { SinkAddComponent } from 'app/pages/sinks/add/sink-add.component'; import { SinkDeleteComponent } from 'app/pages/sinks/delete/sink.delete.component'; import { SinkDetailsComponent } from 'app/pages/sinks/details/sink.details.component'; @@ -69,6 +71,8 @@ import { AgentViewComponent } from './fleet/agents/view/agent.view.component'; import { PagesRoutingModule } from './pages-routing.module'; import { PagesComponent } from './pages.component'; import { SinkViewComponent } from './sinks/view/sink.view.component'; +import { DeleteSelectedComponent } from 'app/shared/components/delete/delete.selected.component'; +import { PolicyDuplicateComponent } from './datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation'; @NgModule({ imports: [ @@ -124,6 +128,7 @@ import { SinkViewComponent } from './sinks/view/sink.view.component'; AgentListComponent, AgentAddComponent, AgentDeleteComponent, + AgentResetComponent, AgentDetailsComponent, AgentKeyComponent, AgentMatchComponent, @@ -146,6 +151,7 @@ import { SinkViewComponent } from './sinks/view/sink.view.component'; AgentPolicyListComponent, AgentPolicyViewComponent, HandlerPolicyAddComponent, + PolicyDuplicateComponent, // Sink Management SinkListComponent, SinkAddComponent, @@ -154,6 +160,8 @@ import { SinkViewComponent } from './sinks/view/sink.view.component'; SinkViewComponent, // DEV SHOWCASE ShowcaseComponent, + // Delete Selected + DeleteSelectedComponent, ], providers: [NbDialogService, NbWindowService, SortPipe], entryComponents: [ConfirmationComponent], diff --git a/ui/src/app/pages/sinks/add/sink-add.component.html b/ui/src/app/pages/sinks/add/sink-add.component.html new file mode 100644 index 000000000..4cb00676b --- /dev/null +++ b/ui/src/app/pages/sinks/add/sink-add.component.html @@ -0,0 +1,43 @@ +
+
+ + + {{ + breadcrumb + }} + + +

{{ strings.sink.add.header }}

+
+
+ + + +
+ +
+
+ + + + +
\ No newline at end of file diff --git a/ui/src/app/pages/sinks/add/sink-add.component.scss b/ui/src/app/pages/sinks/add/sink-add.component.scss new file mode 100644 index 000000000..ca46de6ca --- /dev/null +++ b/ui/src/app/pages/sinks/add/sink-add.component.scss @@ -0,0 +1,51 @@ + +button { + margin: 0 3px; + float: left; + color: #fff !important; + font-family: "Montserrat", sans-serif; + font-weight: 500; + text-transform: none !important; +} +.sink-create { + &.btn-disabled { + background: #2b3148; + } + + &:not(.btn-disabled) { + background-color: #3089fc !important; + } +} + +.sink-cancel { + background-color: #3089fc !important; +} + ngx-sink-details { + flex: 0 1 22rem; + } + + ngx-sink-config { + flex: 2 1 auto; + min-height: 30rem !important; + + nb-card { + height: 30rem !important; + } + } + + .row { + gap: 1rem; + } + + header { + justify-content: space-between; + } + + h4 { + font-family: 'Montserrat', sans-serif; + font-size: 1.5rem; + font-style: normal; + font-weight: normal; + line-height: 2rem; + margin-bottom: 1.5rem; + } \ No newline at end of file diff --git a/ui/src/app/pages/sinks/add/sink-add.component.ts b/ui/src/app/pages/sinks/add/sink-add.component.ts new file mode 100644 index 000000000..3e487c0a9 --- /dev/null +++ b/ui/src/app/pages/sinks/add/sink-add.component.ts @@ -0,0 +1,78 @@ +import { Component, ViewChild } from '@angular/core'; +import { Router } from '@angular/router'; +import { Sink } from 'app/common/interfaces/orb/sink.interface'; +import { NotificationsService } from 'app/common/services/notifications/notifications.service'; +import { SinksService } from 'app/common/services/sinks/sinks.service'; +import { SinkConfigComponent } from 'app/shared/components/orb/sink/sink-config/sink-config.component'; +import { SinkDetailsComponent } from 'app/shared/components/orb/sink/sink-details/sink-details.component'; +import { STRINGS } from 'assets/text/strings'; + + +@Component({ + selector: 'ngx-sink-add-component', + templateUrl: './sink-add.component.html', + styleUrls: ['./sink-add.component.scss'], +}) + +export class SinkAddComponent { + + @ViewChild(SinkDetailsComponent) detailsComponent: SinkDetailsComponent; + + @ViewChild(SinkConfigComponent) configComponent: SinkConfigComponent; + + strings = STRINGS; + + createMode: boolean; + + sinkBackend: any; + + constructor( + private sinksService: SinksService, + private notificationsService: NotificationsService, + private router: Router, + ) { + this.createMode = true; + } + + canCreate() { + const detailsValid = this.createMode + ? this.detailsComponent?.formGroup?.status === 'VALID' + : true; + return detailsValid; + } + + createSink() { + + const sinkDetails = this.detailsComponent.formGroup?.value; + const tags = this.detailsComponent.selectedTags; + const configSink = this.configComponent.code; + + const details = { ...sinkDetails}; + + let configs = JSON.parse(configSink); + + const config = { + ...configs + } + + const payload = { + ...details, + tags, + config, + + } as Sink; + + this.sinksService.addSink(payload).subscribe(() => { + this.notificationsService.success('Sink successfully created', ''); + this.goBack(); + }); + } + + goBack() { + this.router.navigateByUrl('/pages/sinks'); + } + + getBackendEmit(backend: any) { + this.sinkBackend = backend; + } +} diff --git a/ui/src/app/pages/sinks/add/sink.add.component.html b/ui/src/app/pages/sinks/add/sink.add.component.html deleted file mode 100644 index dc592e627..000000000 --- a/ui/src/app/pages/sinks/add/sink.add.component.html +++ /dev/null @@ -1,317 +0,0 @@ -
-
- - - {{ breadcrumb }} - - -

{{strings.sink[isEdit ? 'edit' : 'add']['header']}}

-
-
-
- - - -
- Sink Details -

Provide a name and description for the Sink

-
-
-
- -
-
- - * -
- - -
- Name is required. -
-
- Name must start with a letter or "_" and contain only letters, numbers, "-" or "_" -
-
- Name must be less than {{firstFormGroup.controls.name.errors?.maxlength.requiredLength}} characters -
-
-
-
-
- -
- -
- - -
- Description must be less than {{firstFormGroup.controls.description.errors?.maxlength.requiredLength}} characters -
-
-
-
- -
- - * -
- - {{ type | titlecase }} - -
-
-
- - -
-
-
- - -
- Sink Destination -

Configure your Sink settings

-
-
-
- - - * - - - - - {{ type }} - - - -
- - - -
-
-
- - -
- Sink Tags -

Enter tags for this Sink

-
-
-
- - -
- -
- - - -
-
-
- - -
- Review & Confirm -
-
-
-
-
- -

{{firstFormGroup?.controls.name.value}}

-
-
-
-
- -

{{firstFormGroup?.controls.description.value}}

-
-
-
-
- -

{{firstFormGroup?.controls.backend.value}}

-
-
-
-
-
-
-
- -

{{secondFormGroup.controls[control.prop].value}}

-

*******

-
-
-
-
-
-
- - -
-
-
- - - -
-
-
-
-
-
diff --git a/ui/src/app/pages/sinks/add/sink.add.component.scss b/ui/src/app/pages/sinks/add/sink.add.component.scss deleted file mode 100644 index ffb2a7c35..000000000 --- a/ui/src/app/pages/sinks/add/sink.add.component.scss +++ /dev/null @@ -1,119 +0,0 @@ -nb-card { - width: 400px; -} - -button { - margin: 10px; -} - -textarea { - width: 100%; - height: 100%; -} - -nb-card-footer { - text-align: center; -} - -mat-chip nb-icon { - font-size: 1rem; -} - -::ng-deep { - .header { - align-items: flex-end !important; - margin-right: 10px !important; - height: calc(100vh - 337px) !important; - } - - .label-index { - background-color: #969fb9; - transition: box-shadow 0.3s 0s ease-in-out; - color: #969fb9; - border: none !important; - //box-shadow: 0 0 8px -2px #ffffff; - background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDExIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0wLjUgMy45OTY1MUw0LjE4NjgyIDcuNjgzMzNMMTAuODIzMSAxLjA0NzA2TDkuNzgzNDEgMEw0LjE4NjgyIDUuNTk2NTlMMS41Mzk2OCAyLjk1NjgzTDAuNSAzLjk5NjUxWiIgZmlsbD0iIzE1MWEzMCIvPgo8L3N2Zz4K'); - background-size: 20px 14px; - background-repeat: no-repeat; - background-position: center; - //background-blend-mode: exclusion; - z-index: 3; - :first-child { - visibility: hidden; - } - } - - .step { - flex-direction: row-reverse !important; - align-items: start !important; - } - - .step.selected { - -webkit-text-stroke-color: rgba(255, 255, 255, 0.15); - -webkit-text-stroke-width: 1px; - text-shadow: 0 0 20px #ffffff8f; - - & > .label-index { - box-shadow: 0 0 12px 1px #df316f; - background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDExIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0wLjUgMy45OTY1MUw0LjE4NjgyIDcuNjgzMzNMMTAuODIzMSAxLjA0NzA2TDkuNzgzNDEgMEw0LjE4NjgyIDUuNTk2NTlMMS41Mzk2OCAyLjk1NjgzTDAuNSAzLjk5NjUxWiIgZmlsbD0iI2ZmZiIvPgo8L3N2Zz4K'); - background-color: #df316f; - } - } - - .step-content { - width: 500px !important; - } - - .connector { - width: 1px !important; - transform: translateX(1px); - - &::after { - content: ''; - height: 40px; - transform: translateY(-40px); - width: 1px; - border-left: solid 1px #969fb9b3; - position: absolute; - z-index: 1; - } - } - - .connector.connector-past { - transition: background 0.5s 0s ease-in-out; - &::after { - transition: border 0.5s 0s ease-in-out; - border-left: solid 1px #df316f; - } - } -} - -.required { - color: #df316f; - padding-left: 2px; -} - -.step-label { - width: 214px; - text-align: end; -} - -::ng-deep .orb-breadcrumb { - align-items: center; - - display: flex; - - font-family: 'Montserrat', sans-serif; - font-style: normal; - font-weight: 500; - font-size: 12px; - line-height: 12px; - - ::ng-deep .xng-breadcrumb-trail { - color: #ffffff !important; - - &::before { - color: #969fb9 !important; - } - } -} diff --git a/ui/src/app/pages/sinks/add/sink.add.component.ts b/ui/src/app/pages/sinks/add/sink.add.component.ts deleted file mode 100644 index 985302196..000000000 --- a/ui/src/app/pages/sinks/add/sink.add.component.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { Component } from '@angular/core'; - -import { NotificationsService } from 'app/common/services/notifications/notifications.service'; -import { SinksService } from 'app/common/services/sinks/sinks.service'; -import { ActivatedRoute, Router } from '@angular/router'; -import { Sink } from 'app/common/interfaces/orb/sink.interface'; -import { STRINGS } from 'assets/text/strings'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { SinkConfig } from 'app/common/interfaces/orb/sink/sink.config.interface'; -import { SinkFeature } from 'app/common/interfaces/orb/sink/sink.feature.interface'; - -@Component({ - selector: 'ngx-sink-add-component', - templateUrl: './sink.add.component.html', - styleUrls: ['./sink.add.component.scss'], -}) -export class SinkAddComponent { - strings = STRINGS; - - // stepper vars - firstFormGroup: FormGroup; - - secondFormGroup: FormGroup; - - customSinkSettings: {}; - - selectedSinkSetting: any[]; - - selectedTags: { [propName: string]: string }; - - sink: Sink; - - sinkID: string; - - sinkTypesList = []; - - isEdit: boolean; - - isLoading = false; - - constructor( - private sinksService: SinksService, - private notificationsService: NotificationsService, - private router: Router, - private route: ActivatedRoute, - private _formBuilder: FormBuilder, - ) { - this.isLoading = true; - this.sinkID = this.route.snapshot.paramMap.get('id'); - this.isEdit = this.router.getCurrentNavigation().extras.state?.edit as boolean || !!this.sinkID; - - Promise.all([this.getSink(), this.getSinkBackends()]).then((responses) => { - const { backend } = this.sink = responses[0]; - const backends = responses[1]; - - this.sinkTypesList = backends.map(entry => entry.backend); - this.customSinkSettings = this.sinkTypesList.reduce((accumulator, curr) => { - const index = backends.findIndex(entry => entry.backend === curr); - accumulator[curr] = backends[index].config.map(entry => ({ - type: entry.type, - label: entry.title, - prop: entry.name, - input: entry.input, - required: entry.required, - })); - return accumulator; - }, {}); - - this.initializeForms(); - - if (backend !== '') this.onSinkTypeSelected(backend); - this.isLoading = false; - }).catch(reason => console.warn(`Couldn't retrieve data. Reason: ${ reason }`)); - } - - newSink() { - return { - name: '', - description: '', - backend: 'prometheus', // default sink - tags: {}, - } as Sink; - } - - initializeForms() { - const { name, description, backend, tags } = this.sink; - - this.firstFormGroup = this._formBuilder.group({ - name: [name, [Validators.required, Validators.pattern('^[a-zA-Z_][a-zA-Z0-9_-]*$'), Validators.maxLength(64)]], - description: [description, [Validators.maxLength(64)]], - backend: [backend, Validators.required], - }); - - this.selectedTags = { ...tags }; - } - - getSink() { - return new Promise(resolve => { - if (this.sinkID) { - this.sinksService.getSinkById(this.sinkID).subscribe(resp => { - resolve(resp); - }); - } else { - resolve(this.newSink()); - } - }); - } - - getSinkBackends() { - return new Promise(resolve => { - this.sinksService.getSinkBackends().subscribe(backends => { - resolve(backends); - }); - }); - } - - goBack() { - this.router.navigateByUrl('/pages/sinks'); - } - - onFormSubmit() { - const payload = { - name: this.firstFormGroup.controls.name.value, - backend: this.firstFormGroup.controls.backend.value, - description: this.firstFormGroup.controls.description.value, - config: this.selectedSinkSetting.reduce((accumulator, current) => { - const value = this.secondFormGroup.controls[current.prop].value; - if (current.input === 'checkbox') { - if (value) - accumulator[current.prop] = 'enabled'; - } else { - accumulator[current.prop] = value; - } - return accumulator; - }, {}), - tags: { ...this.selectedTags }, - }; - - if (this.isEdit) { - // updating existing sink - this.sinksService.editSink({ ...payload, id: this.sinkID }).subscribe(() => { - this.notificationsService.success('Sink successfully updated', ''); - this.goBack(); - }); - } else { - this.sinksService.addSink(payload).subscribe(() => { - this.notificationsService.success('Sink successfully created', ''); - this.goBack(); - }); - } - - } - - onSinkTypeSelected(selectedValue) { - // SinkConfig being the generic of all other `sinkTypes`. - const conf = !!this.sink && - this.isEdit && - (selectedValue === this.sink.backend) && - this.sink?.config && - this.sink.config as SinkConfig || null; - - this.selectedSinkSetting = this.customSinkSettings[selectedValue]; - - const dynamicFormControls = this.selectedSinkSetting.reduce((accumulator, curr) => { - accumulator[curr.prop] = [ - !!conf && (curr.prop in conf) && conf[curr.prop] || - '', - curr.required ? Validators.required : null, - ]; - return accumulator; - }, {}); - - this.secondFormGroup = this._formBuilder.group(dynamicFormControls); - } -} diff --git a/ui/src/app/pages/sinks/delete/sink.delete.component.html b/ui/src/app/pages/sinks/delete/sink.delete.component.html index 92a0fb5a9..f00e24ff3 100644 --- a/ui/src/app/pages/sinks/delete/sink.delete.component.html +++ b/ui/src/app/pages/sinks/delete/sink.delete.component.html @@ -10,7 +10,9 @@ -

{{strings.delete.body}}*

+

Are you sure you want to delete Sink {{sink?.name}}? + This may cause Datasets which use this Sink to become invalid. + This action cannot be undone.*

{{strings.delete.warning}}

diff --git a/ui/src/app/pages/sinks/details/sink.details.component.html b/ui/src/app/pages/sinks/details/sink.details.component.html index 84d206594..7a261a158 100644 --- a/ui/src/app/pages/sinks/details/sink.details.component.html +++ b/ui/src/app/pages/sinks/details/sink.details.component.html @@ -18,6 +18,7 @@

{{strings.propNames.description}}

{{ sink.description }}

+

No Description Added

@@ -26,14 +27,15 @@

{{ sink.backend }}

-

{{strings.propNames.config_remote_host}}

-

{{ sink.config.remote_host }}

+

{{ exporterField }}

+

{{ sink.config.exporter.remote_host }}

+

{{ sink.config.exporter.endpoint }}

{{strings.propNames.config_username}}

-

{{ sink.config.username }}

+

{{ sink.config.authentication.username }}

@@ -49,6 +51,7 @@
+

No tags Added

@@ -60,6 +63,6 @@ - + diff --git a/ui/src/app/pages/sinks/details/sink.details.component.ts b/ui/src/app/pages/sinks/details/sink.details.component.ts index e30321a88..28c66ed29 100644 --- a/ui/src/app/pages/sinks/details/sink.details.component.ts +++ b/ui/src/app/pages/sinks/details/sink.details.component.ts @@ -1,15 +1,20 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { NbDialogRef } from '@nebular/theme'; import { STRINGS } from 'assets/text/strings'; import { ActivatedRoute, Router } from '@angular/router'; -import { Sink, SinkStates } from 'app/common/interfaces/orb/sink.interface'; +import { Sink, SinkBackends, SinkStates } from 'app/common/interfaces/orb/sink.interface'; @Component({ selector: 'ngx-sink-details-component', templateUrl: './sink.details.component.html', styleUrls: ['./sink.details.component.scss'], }) -export class SinkDetailsComponent { +export class SinkDetailsComponent implements OnInit { + + exporterField: string; + + sinkBackends = SinkBackends; + strings = STRINGS.sink; @Input() sink: Sink = {}; @@ -22,13 +27,24 @@ export class SinkDetailsComponent { protected router: Router, ) { !this.sink.tags ? this.sink.tags = {} : null; + this.exporterField = ""; } onOpenEdit(sink: any) { - this.dialogRef.close(true); + this.router.navigateByUrl(`/pages/sinks/edit/${sink.id}`); + this.dialogRef.close(); } onClose() { this.dialogRef.close(false); } + + onOpenView(sink: any) { + this.router.navigateByUrl(`/pages/sinks/view/${sink.id}`); + this.dialogRef.close(); + } + ngOnInit() { + const exporter = this.sink.config.exporter; + this.exporterField = exporter.remote_host !== undefined ? "Remote Host URL" : "Endpoint URL"; + } } diff --git a/ui/src/app/pages/sinks/list/sink.list.component.html b/ui/src/app/pages/sinks/list/sink.list.component.html index 3bdddacdf..dbd7be2bc 100644 --- a/ui/src/app/pages/sinks/list/sink.list.component.html +++ b/ui/src/app/pages/sinks/list/sink.list.component.html @@ -16,7 +16,16 @@

{{ strings.list.header }}

- +
+ + +
{{ strings.list.header }} [rowHeight]="50" [rows]="filteredSinks$ | async" [scrollbarV]="true" + [scrollbarH]="true" [sorts]="tableSorts" class="orb" style="height: calc(62vh);" + [selected]="selected" + [selectionType]="'checkbox'" >
- - + + @@ -69,7 +85,7 @@

{{ strings.list.header }}

{{tag | tagchip}} No tags were created @@ -95,7 +111,7 @@

{{ strings.list.header }}

+ + + + + + + + diff --git a/ui/src/app/pages/sinks/list/sink.list.component.scss b/ui/src/app/pages/sinks/list/sink.list.component.scss index c498ee772..6687613c0 100644 --- a/ui/src/app/pages/sinks/list/sink.list.component.scss +++ b/ui/src/app/pages/sinks/list/sink.list.component.scss @@ -143,12 +143,12 @@ tr div p { color: #6fcf97; } &unknown { - color: #f2994a; + color: #9b51e0; } &error { color: #df316f; } - &warning { + &idle { color: #f2dc4a; } } @@ -202,3 +202,30 @@ mat-chip-list { transform: translateX(-70%); } } +.options { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +} +.delete-selected { + color: #ffffff !important; + font-family: 'Montserrat', sans-serif; + font-weight: 600; + text-transform: none !important; + width: 90px; + background-color: #df316f !important; +} +input[type=checkbox] { + margin-left: 10px; +} +.view-sink-button { + background-color: transparent !important; + background-repeat: no-repeat !important; + border: none !important; + cursor: pointer !important; + outline: none !important; + overflow: hidden !important; + color: #ffffff; + text-align: left; +} diff --git a/ui/src/app/pages/sinks/list/sink.list.component.ts b/ui/src/app/pages/sinks/list/sink.list.component.ts index 8b8ceb039..3078de602 100644 --- a/ui/src/app/pages/sinks/list/sink.list.component.ts +++ b/ui/src/app/pages/sinks/list/sink.list.component.ts @@ -33,7 +33,8 @@ import { SinksService } from 'app/common/services/sinks/sinks.service'; import { SinkDeleteComponent } from 'app/pages/sinks/delete/sink.delete.component'; import { SinkDetailsComponent } from 'app/pages/sinks/details/sink.details.component'; import { STRINGS } from 'assets/text/strings'; -import { Observable } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; +import { DeleteSelectedComponent } from 'app/shared/components/delete/delete.selected.component'; @Component({ selector: 'ngx-sink-list-component', @@ -49,6 +50,10 @@ export class SinkListComponent implements AfterViewInit, AfterViewChecked, OnDes loading = false; + selected: any[] = []; + + private sinksSubscription: Subscription; + // templates @ViewChild('sinkNameTemplateCell') sinkNameTemplateCell: TemplateRef; @@ -58,6 +63,10 @@ export class SinkListComponent implements AfterViewInit, AfterViewChecked, OnDes @ViewChild('sinkActionsTemplateCell') actionsTemplateCell: TemplateRef; + @ViewChild('checkboxTemplateCell') checkboxTemplateCell: TemplateRef; + + @ViewChild('checkboxTemplateHeader') checkboxTemplateHeader: TemplateRef; + tableSorts = [ { prop: 'name', @@ -86,6 +95,7 @@ export class SinkListComponent implements AfterViewInit, AfterViewChecked, OnDes private orb: OrbService, private filters: FilterService, ) { + this.selected = []; this.sinks$ = this.orb.getSinkListView(); this.filters$ = this.filters.getFilters(); @@ -133,6 +143,9 @@ export class SinkListComponent implements AfterViewInit, AfterViewChecked, OnDes } ngOnDestroy(): void { + if (this.sinksSubscription) { + this.sinksSubscription.unsubscribe(); + } this.orb.killPolling.next(); } @@ -152,43 +165,53 @@ export class SinkListComponent implements AfterViewInit, AfterViewChecked, OnDes ngAfterViewInit() { this.orb.refreshNow(); this.columns = [ + { + name: '', + prop: 'checkbox', + width: 1, + minWidth: 62, + canAutoResize: true, + sortable: false, + cellTemplate: this.checkboxTemplateCell, + headerTemplate: this.checkboxTemplateHeader, + }, { prop: 'name', name: 'Name', canAutoResize: true, - resizeable: false, - flexGrow: 2, + resizeable: true, + width: 220, minWidth: 150, cellTemplate: this.sinkNameTemplateCell, }, { - prop: 'description', - name: 'Description', - resizeable: false, - minWidth: 150, - flexGrow: 2, - cellTemplate: this.sinkNameTemplateCell, + prop: 'state', + name: 'Status', + resizeable: true, + width: 150, + cellTemplate: this.sinkStateTemplateCell, }, { prop: 'backend', name: 'Backend', - resizeable: false, + resizeable: true, minWidth: 120, - flexGrow: 1, + width: 150, cellTemplate: this.sinkNameTemplateCell, }, { - prop: 'state', - name: 'Status', - resizeable: false, - flexGrow: 1, - cellTemplate: this.sinkStateTemplateCell, + prop: 'description', + name: 'Description', + resizeable: true, + minWidth: 150, + width: 350, + cellTemplate: this.sinkNameTemplateCell, }, { prop: 'tags', name: 'Tags', - flexGrow: 2, - resizeable: false, + width: 350, + resizeable: true, cellTemplate: this.sinkTagsTemplateCell, comparator: (a, b) => Object.entries(a) @@ -204,9 +227,9 @@ export class SinkListComponent implements AfterViewInit, AfterViewChecked, OnDes name: '', prop: 'actions', minWidth: 150, - resizeable: false, + resizeable: true, sortable: false, - flexGrow: 2, + width: 150, cellTemplate: this.actionsTemplateCell, }, ]; @@ -247,7 +270,30 @@ export class SinkListComponent implements AfterViewInit, AfterViewChecked, OnDes } }); } + onOpenDeleteSelected() { + const selected = this.selected; + const elementName = "Sinks" + this.dialogService + .open(DeleteSelectedComponent, { + context: { selected, elementName }, + autoFocus: true, + closeOnEsc: true, + }) + .onClose.subscribe((confirm) => { + if (confirm) { + this.deleteSelectedSinks(); + this.selected = []; + this.orb.refreshNow(); + } + }); + } + deleteSelectedSinks() { + this.selected.forEach((sink) => { + this.sinkService.deleteSink(sink.id).subscribe(); + }) + this.notificationsService.success('All selected Sinks delete requests succeeded', ''); + } openDetailsModal(row: any) { this.dialogService .open(SinkDetailsComponent, { @@ -263,4 +309,48 @@ export class SinkListComponent implements AfterViewInit, AfterViewChecked, OnDes } filterByInactive = (sink) => sink.state === 'inactive'; + + public onCheckboxChange(event: any, row: any): void { + const sinkSelected = { + id: row.id, + name: row.name, + state: row.state, + } + if (this.getChecked(row) === false) { + this.selected.push(sinkSelected); + } + else { + for (let i = 0; i < this.selected.length; i++) { + if (this.selected[i].id === row.id) { + this.selected.splice(i, 1); + break; + } + } + } + } + + public getChecked(row: any): boolean { + const item = this.selected.filter((e) => e.id === row.id); + return item.length > 0 ? true : false; + } + onHeaderCheckboxChange(event: any) { + if (event.target.checked && this.filteredSinks$) { + this.sinksSubscription = this.filteredSinks$.subscribe(rows => { + this.selected = []; + rows.forEach(row => { + const sinkSelected = { + id: row.id, + name: row.name, + state: row.state, + } + this.selected.push(sinkSelected); + }); + }); + } else { + if (this.sinksSubscription) { + this.sinksSubscription.unsubscribe(); + } + this.selected = []; + } + } } diff --git a/ui/src/app/pages/sinks/view/sink.view.component.scss b/ui/src/app/pages/sinks/view/sink.view.component.scss index a02108a74..e8f1b6e00 100644 --- a/ui/src/app/pages/sinks/view/sink.view.component.scss +++ b/ui/src/app/pages/sinks/view/sink.view.component.scss @@ -64,9 +64,18 @@ ngx-sink-config { } .row { - gap: 1rem; + gap: 10px; } header { justify-content: space-between; } + +h4 { + font-family: 'Montserrat', sans-serif; + font-size: 1.5rem; + font-style: normal; + font-weight: normal; + line-height: 2rem; + margin-bottom: 1.5rem; +} \ No newline at end of file diff --git a/ui/src/app/pages/sinks/view/sink.view.component.ts b/ui/src/app/pages/sinks/view/sink.view.component.ts index b5201a5ca..fc5cb1410 100644 --- a/ui/src/app/pages/sinks/view/sink.view.component.ts +++ b/ui/src/app/pages/sinks/view/sink.view.component.ts @@ -79,23 +79,38 @@ export class SinkViewComponent implements OnInit, OnChanges, OnDestroy { } save() { - const { name, description, id, backend } = this.sink; + const { id, backend } = this.sink; const sinkDetails = this.detailsComponent.formGroup?.value; const tags = this.detailsComponent.selectedTags; - const config = this.configComponent.code; - - const detailsPartial = (!!this.editMode.details && { ...sinkDetails, id, backend}) - || { name, description, id, backend }; - - let configPartial = (!!this.editMode.config && JSON.parse(config)) || {}; - - const payload = { - ...configPartial, - ...detailsPartial, - tags, - - } as Sink; + const configSink = this.configComponent.code; + + let payload = {}; + const details = { ...sinkDetails, tags}; + const config = JSON.parse(configSink); + + if (this.editMode.details && !this.editMode.config) { + payload = { + id, + backend, + ...details, + } as Sink; + } + else if (!this.editMode.details && this.editMode.config) { + payload = { + id, + backend, + config, + } as Sink; + } + else { + payload = { + id, + backend, + details, + config, + } as Sink; + } try { this.sinks.editSink(payload).subscribe((resp) => { diff --git a/ui/src/app/shared/components/delete/delete.selected.component.html b/ui/src/app/shared/components/delete/delete.selected.component.html new file mode 100644 index 000000000..61300a02a --- /dev/null +++ b/ui/src/app/shared/components/delete/delete.selected.component.html @@ -0,0 +1,37 @@ + + + Delete {{ elementName }} Confirmation + + + +

Are you sure you want to delete a total of {{ selected?.length }} {{ elementName }}? This action cannot be undone.

+
+ {{ item.name }} {{ item.state | titlecase }} {{ item.usage | titlecase }} +
+

*To confirm, type the amount of {{ elementName }} to be delete.

+ + + {{selected?.length}} + +
+ + + +
\ No newline at end of file diff --git a/ui/src/app/shared/components/delete/delete.selected.component.scss b/ui/src/app/shared/components/delete/delete.selected.component.scss new file mode 100644 index 000000000..1ff0c3cd0 --- /dev/null +++ b/ui/src/app/shared/components/delete/delete.selected.component.scss @@ -0,0 +1,66 @@ +nb-card { + max-width: 38rem !important; + max-height: 50rem !important; + nb-card-header { + background: #232940 !important; + color: #969fb9 !important; + } + + nb-card-body { + margin: 2rem 3rem !important; + padding: 0 !important; + + p { + color: #969fb9 !important; + } + + + } + + nb-card-footer { + text-align: center !important; + padding: 1.5rem !important; + } + } + + // ORB + ::ng-deep { + .orb-close-dialog { + background-color: #23294000 !important; + border-radius: 4px !important; + display: contents !important; + float: right !important; + + > span { + float: right !important; + font-size: 1.5rem !important; + color: #3089fc !important; + font-weight: 900 !important; + } + } + + .orb-sink-delete-warning-button:not(.btn-disabled) { + background-color: #df316f !important; + color: #ffffff !important; + } + } + .orb-service- { + &new { + color: #9b51e0; + } + &online, &healthy, &in, &active { + color: #6fcf97; + } + &stale, &none, &unknown, ¬ { + color: #f2994a; + } + &error, &failure { + color: #df316f; + } + &offline { + color: #969fb9; + } + &warning { + color: #f2dc4a; + } + } diff --git a/ui/src/app/shared/components/delete/delete.selected.component.ts b/ui/src/app/shared/components/delete/delete.selected.component.ts new file mode 100644 index 000000000..16d55fd32 --- /dev/null +++ b/ui/src/app/shared/components/delete/delete.selected.component.ts @@ -0,0 +1,34 @@ +import { Component, Input } from '@angular/core'; +import { NbDialogRef } from '@nebular/theme'; +import { STRINGS } from 'assets/text/strings'; + +@Component({ + selector: 'ngx-delete-selected-component', + templateUrl: './delete.selected.component.html', + styleUrls: ['./delete.selected.component.scss'], +}) + +export class DeleteSelectedComponent { + strings = STRINGS.agents; + @Input() selected: any[] = []; + @Input() elementName: String; + + validationInput: Number; + + constructor( + protected dialogRef: NbDialogRef, + ) { + } + + onDelete() { + this.dialogRef.close(true); + } + + onClose() { + this.dialogRef.close(false); + } + + isEnabled(): boolean { + return this.validationInput === this.selected.length; + } +} \ No newline at end of file diff --git a/ui/src/app/shared/components/orb/agent/agent-backends/agent-backends.component.html b/ui/src/app/shared/components/orb/agent/agent-backends/agent-backends.component.html index 3c9d13419..a02c69369 100644 --- a/ui/src/app/shared/components/orb/agent/agent-backends/agent-backends.component.html +++ b/ui/src/app/shared/components/orb/agent/agent-backends/agent-backends.component.html @@ -6,8 +6,14 @@ tabTitle="{{backend.key}}">

{{backend.value.version}}

-

{{agent.last_hb_data.backend_state[backend.key].state}}

-

{{agent.last_hb_data.backend_state[backend.key].last_restart_ts | date:'full'}}

+

{{agent.last_hb_data.backend_state[backend.key].state}}

+

{{agent.last_hb_data.backend_state[backend.key].last_restart_ts | date:'full'}}

+
+

{{ agent.last_hb_data.backend_state[backend.key].error }}

+
+
+

{{ agent.last_hb_data.backend_state[backend.key].last_error }}

+
diff --git a/ui/src/app/shared/components/orb/agent/agent-backends/agent-backends.component.scss b/ui/src/app/shared/components/orb/agent/agent-backends/agent-backends.component.scss index 3c58c543e..fa3cff9f0 100644 --- a/ui/src/app/shared/components/orb/agent/agent-backends/agent-backends.component.scss +++ b/ui/src/app/shared/components/orb/agent/agent-backends/agent-backends.component.scss @@ -64,6 +64,20 @@ nb-card { } } } +.orb-service- { + &running { + color: #6fcf97; + } + &unknown { + color: #f2994a; + } + &backend_error, &agent_error { + color: #df316f; + } + &offline { + color: #969fb9; + } +} ::ng-deep { @@ -81,7 +95,7 @@ nb-card { color: #6fcf97 !important; } - .agent-offline, .dataset-invalid, .policy-error { + .agent-offline, .dataset-invalid, .policy-error, .error { color: #df316f !important; } diff --git a/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.html b/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.html index 4ef531ba8..04a2a26cc 100644 --- a/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.html +++ b/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.html @@ -10,16 +10,18 @@ Policy:  - -   Status:  - {{ policy?.state }} -    Version:  +  Status:  + {{ policy?.state }} +   Version:  {{ policy?.version }} +   Backend:  + + {{ policy?.backend }} diff --git a/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.scss b/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.scss index 461b01a77..e1c6e53eb 100644 --- a/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.scss +++ b/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.scss @@ -189,8 +189,20 @@ nb-list-item { outline: none !important; overflow: hidden !important; line-height: 1; + max-width: 360px !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } - +// nb-accordion-item-header { +// padding-right: 30px!important; +// } .scroll { max-height: 20em; -} \ No newline at end of file +} +.field{ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 5ch; +} diff --git a/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.ts b/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.ts index 67f699c4b..45f7f651c 100644 --- a/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.ts +++ b/ui/src/app/shared/components/orb/agent/agent-policies-datasets/agent-policies-datasets.component.ts @@ -15,7 +15,7 @@ import { AgentPolicyStates, } from 'app/common/interfaces/orb/agent.policy.interface'; import { Dataset } from 'app/common/interfaces/orb/dataset.policy.interface'; -import { DatasetFromComponent } from 'app/pages/datasets/dataset-from/dataset-from.component'; +import { DatasetFromComponent, DATASET_RESPONSE } from 'app/pages/datasets/dataset-from/dataset-from.component'; @Component({ selector: 'ngx-agent-policies-datasets', @@ -52,7 +52,7 @@ export class AgentPoliciesDatasetsComponent implements OnInit, OnChanges { } ngOnInit(): void { - this.getAmountRunningPolicies() + this.getAmountRunningPolicies(); } getAmountRunningPolicies() { @@ -113,7 +113,7 @@ export class AgentPoliciesDatasetsComponent implements OnInit, OnChanges { closeOnBackdropClick: true, }) .onClose.subscribe((resp) => { - if (resp === 'changed' || 'deleted') { + if (resp === DATASET_RESPONSE.EDITED || resp === DATASET_RESPONSE.DELETED) { this.refreshAgent.emit('refresh-from-dataset'); } }); diff --git a/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.html b/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.html index 92cc66eb9..5cf951b72 100644 --- a/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.html +++ b/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.html @@ -13,7 +13,7 @@
- - - + + + + + diff --git a/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.scss b/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.scss index b5818fb13..8da3a1ec3 100644 --- a/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.scss +++ b/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.scss @@ -67,6 +67,11 @@ nb-card { overflow: hidden !important; color: #ffffff; } +.edit-dataset-button:hover { + + color: #969fb9; + font-size: 16px; +} .orb-service- { &true { @@ -78,3 +83,42 @@ nb-card { } } +mat-nav-list{ + display: flex !important; + flex-direction: row; + flex-wrap: nowrap !important; + align-items: flex-start; + align-content: flex-start; + justify-content: flex-start; + + ::ng-deep > .mat-nav-list-wrapper { + display: flex !important; + flex-direction: row; + flex-wrap: nowrap !important; + align-items: flex-start; + align-content: flex-start; + justify-content: flex-start; + } + + &:hover { + animation-duration: 8s; + animation-name: slidetween; + animation-timing-function: linear; + animation-iteration-count: infinite; + animation-direction: alternate; + } +} + +::ng-deep .mat-nav-list { + display: block; +} + +@keyframes slidetween { + from { + transform: translateX(0); + } + + to { + transform: translateX(-80%); + } +} \ No newline at end of file diff --git a/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.ts b/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.ts index 9062d94b7..4423530d9 100644 --- a/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.ts +++ b/ui/src/app/shared/components/orb/policy/policy-datasets/policy-datasets.component.ts @@ -103,7 +103,7 @@ export class PolicyDatasetsComponent name: 'Agent Group', resizeable: true, canAutoResize: true, - flexGrow: 1, + width: 250, cellTemplate: this.groupTemplateCell, }, { @@ -112,7 +112,7 @@ export class PolicyDatasetsComponent resizeable: true, canAutoResize: true, minWidth: 80, - flexGrow: 0, + width: 80, cellTemplate: this.validTemplateCell, }, { @@ -120,7 +120,7 @@ export class PolicyDatasetsComponent name: 'Sinks', resizeable: true, canAutoResize: true, - flexGrow: 1, + width: 450, cellTemplate: this.sinksTemplateCell, }, { @@ -128,7 +128,7 @@ export class PolicyDatasetsComponent prop: 'actions', resizeable: true, sortable: false, - flexGrow: 1, + width: 200, cellTemplate: this.actionsTemplateCell, }, ]; @@ -197,11 +197,10 @@ export class PolicyDatasetsComponent onOpenSinkDetails(sink) { this.dialogService .open(SinkDetailsComponent, { - autoFocus: true, + autoFocus: false, closeOnEsc: true, context: { sink }, hasScroll: false, - hasBackdrop: false, }) .onClose.subscribe((resp) => { if (resp) { diff --git a/ui/src/app/shared/components/orb/policy/policy-details/policy-details.component.html b/ui/src/app/shared/components/orb/policy/policy-details/policy-details.component.html index 546a226c6..2b1966485 100644 --- a/ui/src/app/shared/components/orb/policy/policy-details/policy-details.component.html +++ b/ui/src/app/shared/components/orb/policy/policy-details/policy-details.component.html @@ -76,6 +76,12 @@
Name does not match the pattern.
+
+ Name must not exceed 64 characters. +
+
+ Name too short. +
diff --git a/ui/src/app/shared/components/orb/policy/policy-details/policy-details.component.ts b/ui/src/app/shared/components/orb/policy/policy-details/policy-details.component.ts index bbc9d3867..6bbbf71a3 100644 --- a/ui/src/app/shared/components/orb/policy/policy-details/policy-details.component.ts +++ b/ui/src/app/shared/components/orb/policy/policy-details/policy-details.component.ts @@ -59,6 +59,8 @@ export class PolicyDetailsComponent implements OnInit, OnChanges { [ Validators.required, Validators.pattern('^[a-zA-Z_][a-zA-Z0-9_-]*$'), + Validators.maxLength(64), + Validators.minLength(2), ], ], description: [description], diff --git a/ui/src/app/shared/components/orb/sink/sink-config/sink-config.component.html b/ui/src/app/shared/components/orb/sink/sink-config/sink-config.component.html index a81842200..d99040b3a 100644 --- a/ui/src/app/shared/components/orb/sink/sink-config/sink-config.component.html +++ b/ui/src/app/shared/components/orb/sink/sink-config/sink-config.component.html @@ -2,7 +2,7 @@ Sink Backend Configuration