Pārlūkot izejas kodu

Merge branch 'develop' into regions

Matt Ray 2 gadi atpakaļ
vecāks
revīzija
cc6a849238
100 mainītis faili ar 10705 papildinājumiem un 662 dzēšanām
  1. 2 2
      .github/ISSUE_TEMPLATE/opencost-bug-report.md
  2. 137 0
      .github/workflows/build-and-publish-release.yml
  3. 48 41
      .github/workflows/build-test.yaml
  4. 26 6
      .github/workflows/sonar.yaml
  5. 1 1
      .github/workflows/stale.yml
  6. 3 5
      .gitignore
  7. 38 1
      CONTRIBUTING.md
  8. 16 4
      Dockerfile
  9. 12 0
      Dockerfile.cross
  10. 10 2
      Dockerfile.debug
  11. 7 1
      MAINTAINERS.md
  12. 1 1
      Makefile
  13. 1 1
      NOTICE
  14. 6 1
      README.md
  15. 7229 0
      THIRD_PARTY_LICENSES.txt
  16. 38 124
      Tiltfile
  17. 169 0
      Tiltfile.opencost
  18. 5 5
      configs/aws.json
  19. 9 0
      configs/oracle.json
  20. 5 0
      configs/pricing_schema_mixed_gpu_ondemand.csv
  21. 61 0
      core/go.mod
  22. 689 0
      core/go.sum
  23. 75 0
      core/pkg/clusters/clusterinfo.go
  24. 0 0
      core/pkg/collections/blockingqueue.go
  25. 1 1
      core/pkg/env/env.go
  26. 42 0
      core/pkg/filter/allocation/fields.go
  27. 11 11
      core/pkg/filter/allocation/parser.go
  28. 1 1
      core/pkg/filter/allocation/parser_test.go
  29. 39 0
      core/pkg/filter/asset/fields.go
  30. 9 9
      core/pkg/filter/asset/parser.go
  31. 139 0
      core/pkg/filter/ast/fields.go
  32. 0 0
      core/pkg/filter/ast/lexer.go
  33. 0 0
      core/pkg/filter/ast/lexer_test.go
  34. 9 0
      core/pkg/filter/ast/ops.go
  35. 0 0
      core/pkg/filter/ast/parser.go
  36. 0 0
      core/pkg/filter/ast/tree.go
  37. 2 2
      core/pkg/filter/ast/walker.go
  38. 0 0
      core/pkg/filter/ast/walker_test.go
  39. 18 0
      core/pkg/filter/cloudcost/fields.go
  40. 9 9
      core/pkg/filter/cloudcost/parser.go
  41. 35 0
      core/pkg/filter/fieldstrings/fieldstrings.go
  42. 1 1
      core/pkg/filter/filter.go
  43. 18 0
      core/pkg/filter/k8sobject/fields.go
  44. 43 0
      core/pkg/filter/k8sobject/parser.go
  45. 1 1
      core/pkg/filter/legacy/allcut.go
  46. 1 1
      core/pkg/filter/legacy/allpass.go
  47. 1 1
      core/pkg/filter/legacy/and.go
  48. 23 23
      core/pkg/filter/legacy/cloudcost/cloudcost.go
  49. 0 0
      core/pkg/filter/legacy/cloudcost/cloudcost_test.go
  50. 1 1
      core/pkg/filter/legacy/filter.go
  51. 237 237
      core/pkg/filter/legacy/filter_test.go
  52. 1 1
      core/pkg/filter/legacy/not.go
  53. 1 1
      core/pkg/filter/legacy/or.go
  54. 2 2
      core/pkg/filter/legacy/stringmapproperty.go
  55. 2 2
      core/pkg/filter/legacy/stringproperty.go
  56. 2 2
      core/pkg/filter/legacy/stringsliceproperty.go
  57. 3 3
      core/pkg/filter/legacy/window.go
  58. 12 12
      core/pkg/filter/legacy/window_test.go
  59. 0 0
      core/pkg/filter/matcher/allcut.go
  60. 0 0
      core/pkg/filter/matcher/allpass.go
  61. 0 0
      core/pkg/filter/matcher/and.go
  62. 3 3
      core/pkg/filter/matcher/compiler.go
  63. 0 0
      core/pkg/filter/matcher/matcher.go
  64. 4 4
      core/pkg/filter/matcher/matcher_test.go
  65. 0 0
      core/pkg/filter/matcher/not.go
  66. 0 0
      core/pkg/filter/matcher/or.go
  67. 2 2
      core/pkg/filter/matcher/stringmapmatcher.go
  68. 2 2
      core/pkg/filter/matcher/stringmatcher.go
  69. 2 2
      core/pkg/filter/matcher/stringslicematcher.go
  70. 13 5
      core/pkg/filter/ops/ops.go
  71. 5 5
      core/pkg/filter/ops/ops_test.go
  72. 1 4
      core/pkg/filter/transform/pass.go
  73. 1 1
      core/pkg/filter/transform/promlabels.go
  74. 1 1
      core/pkg/filter/transform/unallocated.go
  75. 0 0
      core/pkg/filter/util/stack.go
  76. 0 0
      core/pkg/log/counter.go
  77. 0 0
      core/pkg/log/counter_test.go
  78. 8 0
      core/pkg/log/log.go
  79. 0 0
      core/pkg/log/profiler.go
  80. 1049 0
      core/pkg/model/pb/messages.pb.go
  81. 109 0
      core/pkg/model/pb/messages_grpc.pb.go
  82. 25 15
      core/pkg/opencost/allocation.go
  83. 2 2
      core/pkg/opencost/allocation_json.go
  84. 2 2
      core/pkg/opencost/allocation_json_test.go
  85. 13 14
      core/pkg/opencost/allocation_test.go
  86. 5 5
      core/pkg/opencost/allocationfilter_test.go
  87. 6 6
      core/pkg/opencost/allocationmatcher.go
  88. 4 4
      core/pkg/opencost/allocationmatcher_test.go
  89. 111 29
      core/pkg/opencost/allocationprops.go
  90. 1 1
      core/pkg/opencost/allocationprops_test.go
  91. 9 9
      core/pkg/opencost/asset.go
  92. 2 2
      core/pkg/opencost/asset_json.go
  93. 2 2
      core/pkg/opencost/asset_json_test.go
  94. 2 2
      core/pkg/opencost/asset_test.go
  95. 5 5
      core/pkg/opencost/assetmatcher.go
  96. 45 1
      core/pkg/opencost/assetprops.go
  97. 3 3
      core/pkg/opencost/bingen.go
  98. 9 8
      core/pkg/opencost/cloudcost.go
  99. 2 2
      core/pkg/opencost/cloudcost_test.go
  100. 5 5
      core/pkg/opencost/cloudcostmatcher.go

+ 2 - 2
.github/ISSUE_TEMPLATE/opencost-bug-report.md

@@ -8,7 +8,7 @@ assignees: ''
 ---
 
 **Describe the bug**
-A clear and concise description of what the OpenCost bug is. Please ensure this is an issue related to the OpenCost cost model, API, UI or specification. Public Kubecost bugs may be opened at https://github.com/kubecost/cost-analyzer-helm-chart/ 
+A clear and concise description of what the OpenCost bug is. Please ensure this is an issue related to the OpenCost cost model, API or specification. UI issues may be opened in the [OpenCost UI repository](https://github.com/opencost/opencost-ui).
 
 **To Reproduce**
 Steps to reproduce the behavior:
@@ -24,7 +24,7 @@ A clear and concise description of what you expected to happen.
 If applicable, add screenshots to help explain your problem.
 
 **Which version of OpenCost are you using?**
-This may be the Kubecost release.
+You can find the version from the container's startup logging or from the bottom of the page in the UI.
 
 **Additional context**
 Add any other context about the problem here. Kubernetes versions and which public clouds you are working with are especially important.

+ 137 - 0
.github/workflows/build-and-publish-release.yml

@@ -0,0 +1,137 @@
+name: Build and Publish Release
+
+on:
+  push:
+    tags:
+      - 'v[0-9]+.[0-9]+.[0-9]+'
+  workflow_dispatch:
+    inputs:
+      release_version:
+        description: "Version of the release"
+        required: true
+
+concurrency:
+  group: build-opencost
+  cancel-in-progress: true
+
+env:
+  # Use docker.io for Docker Hub if empty
+  REGISTRY: ghcr.io
+  # github.repository as <account>/<repo>
+  IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+  build-and-publish-opencost:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+    steps:
+      - name: Get Version From Tag
+        id: tag
+        if: ${{ github.event_name }} == 'push'
+        run: |
+          echo "TRIGGERED_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
+
+      - name: Determine Version Number
+        id: version_number
+        run: |
+          if [ -z "${TRIGGERED_TAG}" ];
+          then
+            version=${{ inputs.release_version }}
+          else
+            version=$TRIGGERED_TAG
+          fi
+          if [[ ${version:0:1} == "v" ]];
+          then
+            echo "RELEASE_VERSION=${version:1}" >> $GITHUB_OUTPUT
+          else
+            echo "RELEASE_VERSION=$version" >> $GITHUB_OUTPUT
+          fi
+
+      - name: Show Input Values
+        run: |
+          echo "release version: ${{ inputs.release_version }}"
+
+      - name: Make Branch Name
+        id: branch
+        run: |
+          VERSION_NUMBER=${{ steps.version_number.outputs.RELEASE_VERSION }}
+          echo "BRANCH_NAME=v${VERSION_NUMBER%.*}" >> $GITHUB_ENV
+
+      - name: Checkout Repo
+        uses: actions/checkout@v4
+        with:
+          repository: 'opencost/opencost'
+          ref: '${{ steps.branch.outputs.BRANCH_NAME }}'
+          path: ./opencost
+
+      - name: Set SHA
+        id: sha
+        run: |
+          pushd ./opencost
+          echo "OC_SHORTHASH=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
+          popd
+
+      # Login against a Docker registry except on PR
+      # https://github.com/docker/login-action
+      - name: Log into registry ${{ env.REGISTRY }}
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Set OpenCost Image Tags
+        id: tags
+        run: |
+          echo "IMAGE_TAG=ghcr.io/opencost/opencost:${{ steps.sha.outputs.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
+          echo "IMAGE_TAG_LATEST=ghcr.io/opencost/opencost:latest" >> $GITHUB_OUTPUT
+          echo "IMAGE_TAG_VERSION=ghcr.io/opencost/opencost:${{ steps.version_number.outputs.RELEASE_VERSION }}" >> $GITHUB_OUTPUT
+        #  echo "IMAGE_TAG_QUAY=quay.io/kubecost1/kubecost-cost-model:${{ steps.sha.outputs.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
+        #  echo "IMAGE_TAG_LATEST_QUAY=quay.io/kubecost1/kubecost-cost-model:latest" >> $GITHUB_OUTPUT
+        #  echo "IMAGE_TAG_VERSION_QUAY=quay.io/kubecost1/kubecost-cost-model:prod-${{ steps.version_number.outputs.RELEASE_VERSION }}" >> $GITHUB_OUTPUT
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+        with:
+          buildkitd-flags: --debug
+
+      - name: Install Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: 'stable'
+
+      - name: Set up just
+        uses: extractions/setup-just@v1
+
+      - name: Install crane
+        uses: imjasonh/setup-crane@v0.3
+
+      ## Install manifest-tool, which is required to combine multi-arch images
+      ## https://github.com/estesp/manifest-tool
+      - name: Install manifest-tool
+        run: |
+          mkdir -p manifest-tool
+          pushd manifest-tool
+          wget -q https://github.com/estesp/manifest-tool/releases/download/v2.0.8/binaries-manifest-tool-2.0.8.tar.gz
+          tar -xzf binaries-manifest-tool-2.0.8.tar.gz
+          cp manifest-tool-linux-amd64 manifest-tool
+          echo "$(pwd)" >> $GITHUB_PATH
+
+    #  - name: Login to Quay
+    #    uses: docker/login-action@v3
+    #    with:
+    #      registry: quay.io
+    #      username: ${{ secrets.QUAY_USERNAME }}
+    #      password: ${{ secrets.QUAY_PASSWORD }}
+
+      - name: Build and push (multiarch) OpenCost
+        working-directory: ./opencost
+        run: |
+          just build '${{ steps.tags.outputs.IMAGE_TAG }}' '${{ steps.version_number.outputs.RELEASE_VERSION }}'
+          crane copy '${{ steps.tags.outputs.IMAGE_TAG }}' '${{ steps.tags.outputs.IMAGE_TAG_LATEST }}'
+          crane copy '${{ steps.tags.outputs.IMAGE_TAG }}' '${{ steps.tags.outputs.IMAGE_TAG_VERSION }}'
+        #  crane copy '${{ steps.tags.outputs.IMAGE_TAG }}' '${steps.tags.outputs.IMAGE_TAG_QUAY}'
+        #  crane copy '${{ steps.tags.outputs.IMAGE_TAG }}' '${steps.tags.outputs.IMAGE_TAG_LATEST_QUAY}'
+        #  crane copy '${{ steps.tags.outputs.IMAGE_TAG }}' '${steps.tags.outputs.IMAGE_TAG_VERSION_QUAY}'

+ 48 - 41
.github/workflows/build-test.yaml

@@ -10,10 +10,40 @@ on:
       - develop
 
 jobs:
+  validate-protobuf:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          path: ./
+      -
+        name: Install Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: 'stable'
+
+      -
+        name: Install protoc
+        uses: arduino/setup-protoc@v3
+        with:
+          version: '25.3'
+      -
+        name: Install just
+        uses: extractions/setup-just@v1
+
+      - name: install protobuf-go
+        run: |
+          go install github.com/golang/protobuf/protoc-gen-go@latest
+          go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
+          which protoc-gen-go-grpc
+      -
+        name: Validate
+        run: |
+          just validate-protobuf
   backend:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           path: ./
 
@@ -23,13 +53,13 @@ jobs:
 
       -
         name: Install Go
-        uses: actions/setup-go@v4
+        uses: actions/setup-go@v5
         with:
           go-version: 'stable'
 
       # Saves us from having to redownload all modules
       - name: Go Mod cache
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: |
             ~/.cache/go-build
@@ -45,44 +75,21 @@ jobs:
         name: Build
         run: |
           just build-local
-      - name: Upload code coverage
-        uses: actions/upload-artifact@v3
-        with:
-          name: oc-code-coverage
-          path: coverage.out
-
-  frontend:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-        with:
-          path: ./
-
-      -
-        name: Install just
-        uses: extractions/setup-just@v1
-
-      -
-        name: Install node
-        uses: actions/setup-node@v3
-        with:
-          node-version: '18.3.0'
-
-      - name: Get npm cache directory
-        id: npm-cache-dir
+      - name: get-pr-info
         shell: bash
-        run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT}
+        env:
+          PR_NUM: ${{ github.event.number }}
+        run: |
+          echo $PR_NUM > pr_num.txt
+          echo $GITHUB_BASE_REF > base.txt
+          echo $GITHUB_HEAD_REF > head.txt
 
-      - uses: actions/cache@v3
-        id: npm-cache # use this to check for `cache-hit` ==> if: steps.npm-cache.outputs.cache-hit != 'true'
+      - name: Upload code coverage
+        uses: actions/upload-artifact@v4
         with:
-          path: ${{ steps.npm-cache-dir.outputs.dir }}
-          key: ${{ runner.os }}-node-${{ hashFiles('./ui/**/package-lock.json') }}
-          restore-keys: |
-            ${{ runner.os }}-node-
-
-      -
-        name: Build
-        working-directory: ./ui
-        run: |
-          just build-local
+          name: oc-code-coverage
+          path: |
+           coverage.out
+           pr_num.txt
+           base.txt
+           head.txt

+ 26 - 6
.github/workflows/sonar.yaml

@@ -9,13 +9,13 @@ jobs:
     runs-on: ubuntu-latest
     if: github.event.workflow_run.conclusion == 'success'
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           repository: ${{ github.event.workflow_run.head_repository.full_name }}
           ref: ${{ github.event.workflow_run.head_branch }}
           fetch-depth: 0
       - name: 'Download code coverage'
-        uses: actions/github-script@v6
+        uses: actions/github-script@v7
         with:
           script: |
             let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
@@ -36,16 +36,36 @@ jobs:
             fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/oc-code-coverage.zip`, Buffer.from(download.data));
       - name: 'Unzip code coverage'
         run: unzip oc-code-coverage.zip -d coverage
-      - name: SonarCloud Scan
+      - name: set env vars 
+        run: | 
+          echo "SONAR_PR_NUM=$(cat coverage/pr_num.txt)" >> $GITHUB_ENV
+          echo "SONAR_BASE=$(cat coverage/base.txt)" >> $GITHUB_ENV
+          echo "SONAR_HEAD=$(cat coverage/head.txt)" >> $GITHUB_ENV
+      # on develop branch, only run a baseline scan
+      - name: SonarCloud Scan (Baseline)
         uses: sonarsource/sonarcloud-github-action@master
+        if: env.SONAR_HEAD == 'develop'
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
         with:
           args: >
             -Dsonar.scm.revision=${{ github.event.workflow_run.head_sha }}
-            -Dsonar.pullrequest.key=${{ github.event.workflow_run.pull_requests[0].number }}
-            -Dsonar.pullrequest.branch=${{ github.event.workflow_run.pull_requests[0].head.ref }}
-            -Dsonar.pullrequest.base=${{ github.event.workflow_run.pull_requests[0].base.ref }}
+            -Dsonar.projectKey=opencost_opencost
+            -Dsonar.organization=opencost
+            -Dsonar.branch.name=develop
+            -Dsonar.branch.target=develop
+      - name: SonarCloud Scan (PR)
+        uses: sonarsource/sonarcloud-github-action@master
+        if: env.SONAR_HEAD != 'develop'
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+        with:
+          args: >
+            -Dsonar.scm.revision=${{ github.event.workflow_run.head_sha }}
+            -Dsonar.pullrequest.key=${{ env.SONAR_PR_NUM }}
+            -Dsonar.pullrequest.branch=${{ env.SONAR_HEAD }}
+            -Dsonar.pullrequest.base=${{ env.SONAR_BASE }}
             -Dsonar.projectKey=opencost_opencost
             -Dsonar.organization=opencost

+ 1 - 1
.github/workflows/stale.yml

@@ -7,7 +7,7 @@ jobs:
   stale:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/stale@v8
+      - uses: actions/stale@v9
         with:
           stale-issue-message: 'This issue has been marked as stale because it has been open for 360 days with no activity. Please remove the stale label or comment or this issue will be closed in 5 days.'
           close-issue-message: 'This issue was closed because it has been inactive for 365 days with no activity.'

+ 3 - 5
.gitignore

@@ -2,11 +2,6 @@
 .idea
 *.iml
 
-ui/.parcel-cache
-ui/.cache
-ui/dist
-ui/.env
-ui/node_modules/
 cmd/costmodel/costmodel
 cmd/costmodel/costmodel-amd64
 cmd/costmodel/costmodel-arm64
@@ -19,3 +14,6 @@ pkg/cloud/azureorphan_test.go
 
 #Apple
 *.DS_Store
+
+# tilt
+tilt_config.json

+ 38 - 1
CONTRIBUTING.md

@@ -42,7 +42,7 @@ Dependencies:
 3. Set [this environment variable](https://github.com/opencost/opencost/blob/develop/kubernetes/opencost.yaml#L155) to the address of your Prometheus server
 
 ### Build the frontend
-1. `cd ui && just build-ui "<repo>/opencost-ui:<tag>"`
+1. `cd ui && just build "<repo>/opencost-ui:<tag>"`
 2. Edit the [pulled image](https://github.com/opencost/opencost/blob/develop/kubernetes/opencost.yaml#L162) in the `kubernetes/opencost.yaml` to `<repo>/opencost-ui:<tag>`
 
 ### Deploy to a cluster
@@ -89,6 +89,12 @@ An example of the full command:
 PROMETHEUS_SERVER_ENDPOINT="http://127.0.0.1:9090" go run main.go
 ```
 
+## Testing code
+
+Testing is provided by the `just test` command which runs
+- [go test](https://go.dev/doc/tutorial/add-a-test) unit tests
+- [go vet](https://pkg.go.dev/cmd/vet) code quality checks
+
 ## Running the integration tests
 
 To run these tests:
@@ -111,3 +117,34 @@ Please write a commit message with Fixes Issue # if there is an outstanding issu
 Please run `go fmt` on the project directory. Lint can be okay (for example, comments on exported functions are nice but not required on the server).
 
 Please reach us on [CNCF Slack](https://slack.cncf.io/) in the [#opencost](https://cloud-native.slack.com/archives/C03D56FPD4G) channel or attend the biweekly [OpenCost Working Group community meeting](https://bit.ly/opencost-meeting) from the [Community Calendar](https://bit.ly/opencost-calendar) to discuss OpenCost development.
+
+## Third Party Attributions
+
+The THIRD_PARTY_LICENSES.txt file should contain the up-to-date license, copyright, and notice information for dependencies used by Opencost.
+When adding, updating, or removing dependencies, please update the associate section(s) of the [THIRD_PARTY_LICENSES.txt](THIRD_PARTY_LICENSES.txt) file. 
+
+For example, the `github.com/opencost/opencost/core` dependency contains the following third party attributions.
+The license associated with the SPDX identifier should also be included in the attributions file, if it is not already present.
+
+```
+== Dependency
+github.com/opencost/opencost/core
+
+== License Type
+SPDX:Apache-2.0
+
+== Copyright
+Copyright 2019 - 2022 Stackwatch Incorporated. All Rights Reserved.
+Copyright 2022 - 2024 Cloud Native Computing Foundation
+
+== Notices
+OpenCost
+Copyright 2022 - 2024 Cloud Native Computing Foundation
+
+This product includes software developed at
+The Cloud Native Computing Foundation (http://www.cncf.io).
+
+The Initial Developer of some parts of the specification and project is
+Kubecost (http://www.kubecost.com).
+Copyright 2019 - 2022 Stackwatch Incorporated. All Rights Reserved.
+```

+ 16 - 4
Dockerfile

@@ -14,10 +14,12 @@ ARG CGO_ENABLED=0
 ARG version=dev
 ARG	commit=HEAD
 
-# Get dependencies - will also be cached if we won't change mod/sum
-RUN go mod download
 # COPY the source code as the last step
 COPY . .
+
+# Get dependencies - will also be cached if we won't change mod/sum
+RUN go mod download
+
 # Build the binary
 RUN set -e ;\
     go test ./test/*.go;\
@@ -26,17 +28,27 @@ RUN set -e ;\
     GOOS=linux \
     go build -a -installsuffix cgo \
     -ldflags \
-    "-X github.com/opencost/opencost/pkg/version.Version=${version} \
-    -X github.com/opencost/opencost/pkg/version.GitCommit=${commit}" \
+    "-X github.com/opencost/opencost/core/pkg/version.Version=${version} \
+    -X github.com/opencost/opencost/core/pkg/version.GitCommit=${commit}" \
     -o /go/bin/app
 
 FROM alpine:latest
+
+LABEL org.opencontainers.image.description="Cross-cloud cost allocation models for Kubernetes workloads"
+LABEL org.opencontainers.image.documentation=https://opencost.io/docs/
+LABEL org.opencontainers.image.licenses=Apache-2.0
+LABEL org.opencontainers.image.source=https://github.com/opencost/opencost
+LABEL org.opencontainers.image.title=kubecost-cost-model
+LABEL org.opencontainers.image.url=https://opencost.io
+
 RUN apk add --update --no-cache ca-certificates
 COPY --from=build-env /go/bin/app /go/bin/app
+ADD --chmod=644 ./THIRD_PARTY_LICENSES.txt /THIRD_PARTY_LICENSES.txt
 ADD --chmod=644 ./configs/default.json /models/default.json
 ADD --chmod=644 ./configs/azure.json /models/azure.json
 ADD --chmod=644 ./configs/aws.json /models/aws.json
 ADD --chmod=644 ./configs/gcp.json /models/gcp.json
 ADD --chmod=644 ./configs/alibaba.json /models/alibaba.json
+ADD --chmod=644 ./configs/oracle.json /models/oracle.json
 USER 1001
 ENTRYPOINT ["/go/bin/app"]

+ 12 - 0
Dockerfile.cross

@@ -1,16 +1,28 @@
 FROM alpine:latest
 
+ARG version=dev
+ARG	commit=HEAD
+
+LABEL org.opencontainers.image.description="Cross-cloud cost allocation models for Kubernetes workloads"
+LABEL org.opencontainers.image.documentation=https://opencost.io/docs/
+LABEL org.opencontainers.image.licenses=Apache-2.0
+LABEL org.opencontainers.image.source=https://github.com/opencost/opencost
+LABEL org.opencontainers.image.title=kubecost-cost-model
+LABEL org.opencontainers.image.url=https://opencost.io
+
 # The prebuilt binary path. This Dockerfile assumes the binary will be built
 # outside of Docker.
 ARG binarypath
 
 RUN apk add --update --no-cache ca-certificates
 
+ADD --chmod=644 ./THIRD_PARTY_LICENSES.txt /THIRD_PARTY_LICENSES.txt
 ADD --chmod=644 ./configs/default.json /models/default.json
 ADD --chmod=644 ./configs/azure.json /models/azure.json
 ADD --chmod=644 ./configs/aws.json /models/aws.json
 ADD --chmod=644 ./configs/gcp.json /models/gcp.json
 ADD --chmod=644 ./configs/alibaba.json /models/alibaba.json
+ADD --chmod=644 ./configs/oracle.json /models/oracle.json
 
 COPY ${binarypath} /go/bin/app
 

+ 10 - 2
Dockerfile.debug

@@ -4,18 +4,26 @@ FROM golang:alpine
 # outside of Docker.
 ARG binary_path
 
+LABEL org.opencontainers.image.description="Cross-cloud cost allocation models for Kubernetes workloads"
+LABEL org.opencontainers.image.documentation=https://opencost.io/docs/
+LABEL org.opencontainers.image.licenses=Apache-2.0
+LABEL org.opencontainers.image.source=https://github.com/opencost/opencost
+LABEL org.opencontainers.image.title=kubecost-cost-model
+LABEL org.opencontainers.image.url=https://opencost.io
+
 WORKDIR /app
 RUN apk add --update --no-cache ca-certificates
 RUN go install github.com/go-delve/delve/cmd/dlv@latest
 
+ADD --chmod=644 ./THIRD_PARTY_LICENSES.txt /THIRD_PARTY_LICENSES.txt
 ADD --chmod=644 ./configs/default.json /models/default.json
 ADD --chmod=644 ./configs/azure.json /models/azure.json
 ADD --chmod=644 ./configs/aws.json /models/aws.json
 ADD --chmod=644 ./configs/gcp.json /models/gcp.json
 ADD --chmod=644 ./configs/alibaba.json /models/alibaba.json
+ADD --chmod=644 ./configs/oracle.json /models/oracle.json
 
-RUN echo "binary_path"
 COPY ${binary_path} main
 
 ENTRYPOINT ["/go/bin/dlv exec --listen=:40000 --api-version=2 --headless=true --accept-multiclient --log --continue /app/main"]
-EXPOSE 9003 40000
+EXPOSE 9003 40000

+ 7 - 1
MAINTAINERS.md

@@ -7,10 +7,16 @@ Official list of [OpenCost Maintainers](https://github.com/orgs/opencost/teams/o
 | Maintainer | GitHub ID | Affiliation | Email |
 | --------------- | --------- | ----------- | ----------- |
 | Ajay Tripathy | @AjayTripathy | Kubecost | <Ajay@kubecost.com> |
+| Alex Meijer | @ameijer | Kubecost | <ameijer@kubecost.com> |
 | Artur Khantimirov | @r2k1 | Microsoft | |
 | Matt Bolt | @​mbolt35 | Kubecost | <matt@kubecost.com> |
 | Matt Ray | @mattray | Kubecost | <mattray@kubecost.com> |
-| Michael Dresser | @michaelmdresser | Kubecost | <michael@kubecost.com> |
 | Niko Kovacevic | @nikovacevic | Kubecost | <niko@kubecost.com> |
 | Sean Holcomb | @Sean-Holcomb | Kubecost | <Sean@kubecost.com> |
 | Thomas Evans | @teevans | Kubecost | <thomas@kubecost.com> |
+
+## Opencost Emeritus Committers
+We would like to acknowledge previous committers and their huge contributions to our collective success:
+| Maintainer | GitHub ID | Affiliation | Email |
+| --------------- | --------- | ----------- | ----------- |
+| Michael Dresser | @michaelmdresser | Kubecost | <michaelmdresser@gmail.com> |

+ 1 - 1
Makefile

@@ -6,7 +6,7 @@ GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
 GIT_LAST_COMMIT_DATE := $(shell git log -1 --date=iso-strict --format=%cd)
 
 # Build flags
-VPREFIX := github.com/opencost/opencost/pkg/version
+VPREFIX := github.com/opencost/opencost/core/pkg/version
 GO_LDFLAGS   := -X $(VPREFIX).Version=$(IMAGE_TAG) -X $(VPREFIX).GitCommit=$(GIT_REVISION)
 GO_FLAGS     := -ldflags "-extldflags \"-static\" -s -w $(GO_LDFLAGS)"
 

+ 1 - 1
NOTICE

@@ -1,5 +1,5 @@
 OpenCost
-Copyright 2022 - 2023 Cloud Native Computing Foundation
+Copyright 2022 - 2024 Cloud Native Computing Foundation
 
 This product includes software developed at
 The Cloud Native Computing Foundation (http://www.cncf.io).

+ 6 - 1
README.md

@@ -1,3 +1,6 @@
+[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
+[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/6219/badge)](https://www.bestpractices.dev/projects/6219)
+
 <img src="./opencost-header.png"/>
 
 # OpenCost — your favorite open source cost monitoring tool for Kubernetes and cloud spend
@@ -6,7 +9,7 @@ OpenCost give teams visibility into current and historical Kubernetes and cloud
 These models provide cost transparency in Kubernetes environments that support multiple applications, teams, departments, etc.
 It also provides visibility into the cloud costs across multiple providers.
 
-OpenCost was originally developed and open sourced by [Kubecost](https://kubecost.com). This project combines a [specification](/spec/) as well as a Golang implementation of these detailed requirements.
+OpenCost was originally developed and open sourced by [Kubecost](https://kubecost.com). This project combines a [specification](/spec/) as well as a Golang implementation of these detailed requirements. The web UI is available in the [opencost/opencost-ui](http://github.com/opencost/opencost-ui) repository.
 
 [![OpenCost UI Walkthrough](./ui/src/thumbnail.png)](https://youtu.be/lCP4Ci9Kcdg)
 *OpenCost UI Walkthrough*
@@ -19,6 +22,8 @@ To see the full functionality of OpenCost you can view [OpenCost features](https
 - Supports on-prem k8s clusters with custom CSV pricing
 - Allocation for in-cluster K8s resources like CPU, GPU, memory, and persistent volumes
 - Easily export pricing data to Prometheus with /metrics endpoint ([learn more](https://www.opencost.io/docs/installation/prometheus))
+- Carbon costs for cloud resources
+- Support for external costs like Datadog through [OpenCost Plugins](https://github.com/opencost/opencost-plugins)
 - Free and open source distribution ([Apache2 license](LICENSE))
 
 ## Getting Started

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 7229 - 0
THIRD_PARTY_LICENSES.txt


+ 38 - 124
Tiltfile

@@ -1,130 +1,44 @@
-load('ext://helm_resource', 'helm_resource', 'helm_repo')
-load('ext://restart_process', 'docker_build_with_restart')
+load('Tiltfile.opencost', 'run_opencost')
 
 # WARNING: this allows any k8s context for deployment
-#allow_k8s_contexts(k8s_context())
+# allow_k8s_contexts(k8s_context())
 # To allow a specific context for deployment:
 # allow_k8s_contexts('kubectl-context')
-# See https://docs.tilt.dev/api.html#api.allow_k8s_contexts for default allowed contexts
-
-config.define_string('arch', args=False, usage='amd64')
-config.define_string('docker-repo', args=False, usage='')
+# See https://docs.tilt.dev/api.html#api.allow_k8s_contexts for default
+# allowed contexts
+
+config.define_string('arch')
+config.define_string('cloud-integration')
+config.define_bool('delve-continue')
+config.define_string('docker-repo')
+config.define_string('helm-values')
+config.define_string('port-costmodel')
+config.define_string('port-debug')
+config.define_string('port-prometheus')
+config.define_string('port-ui')
+config.define_string('service-key')
 cfg = config.parse()
 
-arch = cfg.get('arch')
-
-docker_platform = "linux/amd64"
-go_arch = "amd64"
-if arch == "arm64":
-    docker_platform = "linux/aarch64"
-    go_arch = "arm64"
-
-docker_repo = cfg.get('docker-repo')
-if docker_repo == None:
-    docker_repo = ''
-else:
-    docker_repo = docker_repo + "/"
-
-# Build and update opencost back end binary when code changes
-local_resource(
-    name='build-costmodel',
-    dir='.',
-    cmd='CGO_ENABLED=0 GOOS=linux GOARCH='+go_arch+' go build -o ./cmd/costmodel/costmodel-tilt ./cmd/costmodel/main.go',
-    deps=[
-        './cmd/costmodel/main.go',
-        './pkg',
-    ],
-    allow_parallel=True,
-    resource_deps=['build-go-mod-download'],
-)
-
-# Build back end docker container
-# If the binary is updated, update the running container and restart binary in dlv
-docker_build_with_restart(
-    ref=docker_repo+'opencost-costmodel',
-    context='.',
-    # remove --continue flag to make dlv wait until debugger is attached to start
-    entrypoint='/go/bin/dlv exec --listen=:40000 --api-version=2 --headless=true --accept-multiclient --log --continue /app/main',
-    dockerfile='Dockerfile.debug',
-    platform=docker_platform,
-
-    build_args={'binary_path':'./cmd/costmodel/costmodel-tilt'},
-    only=[
-        'cmd/costmodel/costmodel-tilt',
-        'configs',
-    ],
-    live_update=[
-       sync('./cmd/costmodel/costmodel-tilt', '/app/main'),
-    ],
-)
-
-# npm install if package.json changes
-local_resource(
-    name='build-npm-install',
-    dir='./ui',
-    cmd='npm install',
-    deps=[
-        './ui/package.json',
-    ],
-    allow_parallel=True,
-)
-
-# Build FE locally when code changes
-local_resource(
-    name='build-ui',
-    dir='./ui',
-    cmd='npx parcel build src/index.html',
-    deps=[
-        './ui/src',
-        './ui/package.json',
-    ],
-    allow_parallel=True,
-    resource_deps=['build-npm-install'],
-)
-
-# update container when relevant files change
-docker_build(
-    ref=docker_repo+'opencost-ui',
-    context='./ui',
-    dockerfile='./ui/Dockerfile.cross',
-    only=[
-        'dist',
-        'nginx.conf',
-        'default.nginx.conf.template',
-        'docker-entrypoint.sh',
-    ],
-    live_update=[
-       sync('./ui/dist', '/var/www'),
-    ],
-)
-
-# build yaml for deployment to k8s
-yaml = helm(
-    '../opencost-helm-chart/charts/opencost',
-    name='opencost',
-    values=['./tilt-values.yaml'],
-    # configuring opencost to also use the kubecost prometheus server below
-    set=[
-        'opencost.ui.image.fullImageName='+docker_repo+'opencost-ui',
-        'opencost.exporter.image.fullImageName='+docker_repo+'opencost-costmodel',
-        'opencost.prometheus.internal.namespaceName='+k8s_namespace(),
-    ]
-)
-k8s_yaml(yaml) # put resulting yaml into k8s
-k8s_resource(workload='opencost', port_forwards=['9003:9003','9090:9090','40000:40000'])
-
-helm_resource(
-    name='prometheus',
-    chart='prometheus-community/prometheus')
-k8s_resource(workload='prometheus', port_forwards=['9080:9090'])
-
-local_resource(
-    name='costmodel-test',
-    dir='.',
-    cmd='go test ./...',
-    deps=[
-        './pkg',
-    ],
-    allow_parallel=True,
-    resource_deps=['opencost'], # run tests after build to speed up deployment
-)
+docker_repo = cfg.get('docker-repo', '')
+if docker_repo != '':
+    docker_repo += "/"
+
+port_costmodel = cfg.get('port-costmodel', 9003)
+port_debug = cfg.get('port-debug', 40000)
+port_prometheus = cfg.get('port-prometheus', 9080)
+port_ui = cfg.get('port-ui', 9090)
+
+options = {
+    'arch': cfg.get('arch'),
+    'cloud_integration': cfg.get('cloud-integration', ''),
+    'delve_continue': cfg.get('delve-continue', True),
+    'docker_repo': docker_repo,
+    'helm_values': cfg.get('helm-values', './tilt-values.yaml'),
+    'port_costmodel': cfg.get('port-costmodel', '9003'),
+    'port_debug': cfg.get('port-debug', '40000'),
+    'port_prometheus': cfg.get('port-prometheus', '9080'),
+    'port_ui': cfg.get('port-ui', '9090'),
+    'service_key': cfg.get('service-key', ''),
+}
+
+run_opencost(options)

+ 169 - 0
Tiltfile.opencost

@@ -0,0 +1,169 @@
+load('ext://helm_resource', 'helm_resource', 'helm_repo')
+load('ext://restart_process', 'docker_build_with_restart')
+load('ext://secret', 'secret_create_generic')
+
+
+def get_docker_platform(arch):
+    if arch == "arm64":
+        return "linux/arm64"
+    else:
+        return "linux/amd64"
+
+
+def get_go_arch(arch):
+    if arch == "arm64":
+        return "arm64"
+    else:
+        return "amd64"
+
+
+# run_opencost is encapsulated as a function to make import easier for running alongside kubecost.
+# The `../opencost` pattern that repeats across this function is to deal with how multiple tilt
+# files work - the base directory (`.`) is always relative to the Tiltfile executed and not the
+# directory containing this file.
+def run_opencost(options):
+
+    docker_platform = get_docker_platform(options["arch"])
+    go_arch = get_go_arch(options["arch"])
+    is_cloud_integration = options["cloud_integration"] != '' and os.path.exists(options["cloud_integration"])
+    is_service_key = options["service_key"] != '' and os.path.exists(options["service_key"])
+    continue_flag = '--continue'
+    if options["delve_continue"] == False:
+        continue_flag = ''
+
+    # Build and update opencost back end binary when code changes
+    local_resource(
+        name='build-costmodel',
+        dir='.',
+        cmd='CGO_ENABLED=0 GOOS=linux GOARCH='+go_arch+' go build -o ../opencost/cmd/costmodel/costmodel-tilt ../opencost/cmd/costmodel/main.go',
+        deps=[
+            '../opencost/cmd/costmodel/main.go',
+            '../opencost/pkg',
+        ],
+        allow_parallel=True,
+    )
+
+    # Build back end docker container
+    # If the binary is updated, update the running container and restart binary in dlv
+    docker_build_with_restart(
+        ref=options["docker_repo"]+'opencost-costmodel',
+        context='../opencost',
+        # remove --continue flag to make dlv wait until debugger is attached to start
+        entrypoint='/go/bin/dlv exec --listen=:40000 --api-version=2 --headless=true --accept-multiclient --log '+continue_flag+' /app/main',
+        dockerfile='../opencost/Dockerfile.debug',
+        platform=docker_platform,
+        build_args={'binary_path': './cmd/costmodel/costmodel-tilt'},
+        only=[
+            'cmd/costmodel/costmodel-tilt',
+            'configs',
+            'THIRD_PARTY_LICENSES.txt',
+        ],
+        live_update=[
+            sync('../opencost/cmd/costmodel/costmodel-tilt', '/app/main'),
+        ],
+    )
+
+    # npm install if package.json changes
+    local_resource(
+        name='build-npm-install',
+        dir='../opencost/ui',
+        cmd='npm install',
+        deps=[
+            '../opencost/ui/package.json',
+        ],
+        allow_parallel=True,
+    )
+
+    # Build FE locally when code changes
+    local_resource(
+        name='build-ui',
+        dir='../opencost/ui',
+        cmd='npx parcel build src/index.html',
+        deps=[
+            '../opencost/ui/src',
+            '../opencost/ui/package.json',
+        ],
+        allow_parallel=True,
+        resource_deps=['build-npm-install'],
+    )
+
+    # update container when relevant files change
+    docker_build(
+        ref=options["docker_repo"]+'opencost-ui',
+        context='../opencost/ui',
+        dockerfile='../opencost/ui/Dockerfile.debug',
+        only=[
+            'dist',
+            'nginx.conf',
+            'default.nginx.conf.template',
+            'docker-entrypoint.sh',
+        ],
+        live_update=[
+            sync('../opencost/ui/dist', '/var/www'),
+        ],
+    )
+
+    values_set = [
+        'opencost.ui.image.fullImageName='+options["docker_repo"]+'opencost-ui',
+        'opencost.exporter.image.fullImageName='+options["docker_repo"]+'opencost-costmodel',
+        'opencost.prometheus.internal.namespaceName='+k8s_namespace(),
+        'opencost.exporter.debugPort=40000',
+    ]
+
+    if is_cloud_integration:
+        values_set.append('opencost.cloudIntegrationSecret=cloud-integration')
+        values_set.append('opencost.cloudCost.enabled=true')
+    else:
+        values_set.append('opencost.cloudCost.enabled=false')
+
+    if is_cloud_integration:
+        secret_create_generic(
+            name='cloud-integration',
+            namespace=k8s_namespace(),
+            from_file=options["cloud_integration"],
+            secret_type=None,
+            from_env_file=None
+        )
+
+    if is_service_key:
+        secret_create_generic(
+            name='service-key',
+            namespace=k8s_namespace(),
+            from_file=options["service_key"],
+            secret_type=None,
+            from_env_file=None
+        )
+
+    # build yaml for deployment to k8s
+    yaml = helm(
+        '../opencost-helm-chart/charts/opencost',
+        name='opencost',
+        values=[options["helm_values"]],
+        set=values_set
+    )
+    k8s_yaml(yaml)  # put resulting yaml into k8s
+
+    port_forwards = [
+        options['port_costmodel']+':9003',
+        options['port_ui']+':9090',
+        options['port_debug']+':40000',
+    ]
+    k8s_resource(workload='opencost', port_forwards=port_forwards)
+
+    helm_repo('prometheus-community', 'https://prometheus-community.github.io/helm-charts')
+    helm_resource(
+        name='prometheus',
+        chart='prometheus-community/prometheus',
+        resource_deps=['prometheus-community'])
+    k8s_resource(workload='prometheus', port_forwards=[options['port_prometheus']+':9090'])
+
+    local_resource(
+        name='costmodel-test',
+        dir='../opencost',
+        cmd='go test ./...',
+        deps=[
+            './pkg',
+        ],
+        allow_parallel=True,
+        resource_deps=['opencost'],  # run tests after build to speed up deployment
+    )

+ 5 - 5
configs/aws.json

@@ -14,12 +14,12 @@
     "spotLabelValue": "spotinstance-nodes",
     "awsServiceKeyName": "",
     "awsServiceKeySecret": "",
-    "awsSpotDataRegion":"us-east-2",
-    "awsSpotDataBucket": "x",
+    "awsSpotDataRegion":"",
+    "awsSpotDataBucket": "",
     "awsSpotDataPrefix": "",
-    "athenaBucketName": "s3://x",
-    "athenaRegion": "us-east-1",
+    "athenaBucketName": "",
+    "athenaRegion": "",
     "athenaDatabase": "",
     "athenaTable": "",
-    "projectID": "12345"
+    "projectID": ""
 }

+ 9 - 0
configs/oracle.json

@@ -0,0 +1,9 @@
+{
+  "provider":"Oracle",
+  "CPU": "0.015",
+  "RAM": "0.002",
+  "GPU": "2.00",
+  "storage": "0.00005479452",
+  "defaultLBPrice": "0.0113",
+  "internetNetworkEgress": "0.0085"
+}

+ 5 - 0
configs/pricing_schema_mixed_gpu_ondemand.csv

@@ -0,0 +1,5 @@
+EndTimestamp,InstanceID,Region,AssetClass,InstanceIDField,InstanceType,MarketPriceHourly,Version
+2028-01-06 23:34:45 UTC,Reserved,,node,metadata.labels.TestClusterUsage,,0.654795,
+2028-01-06 23:34:45 UTC,OnDemand,,node,metadata.labels.TestClusterUsage,,0.90411,
+2028-01-06 23:34:45 UTC,a100-ondemand,,gpu,nvidia.com/gpu_type,,0.5,
+2028-01-06 23:34:45 UTC,a100-reserved,,gpu,nvidia.com/gpu_type,,1,

+ 61 - 0
core/go.mod

@@ -0,0 +1,61 @@
+module github.com/opencost/opencost/core
+
+go 1.21.0
+
+require (
+	github.com/davecgh/go-spew v1.1.1
+	github.com/goccy/go-json v0.9.11
+	github.com/google/go-cmp v0.6.0
+	github.com/hashicorp/go-multierror v1.1.1
+	github.com/hashicorp/go-plugin v1.6.0
+	github.com/json-iterator/go v1.1.12
+	github.com/patrickmn/go-cache v2.1.0+incompatible
+	github.com/rs/zerolog v1.26.1
+	github.com/spf13/viper v1.8.1
+	golang.org/x/exp v0.0.0-20221031165847-c99f073a8326
+	golang.org/x/sync v0.6.0
+	golang.org/x/text v0.14.0
+	google.golang.org/grpc v1.62.0
+	google.golang.org/protobuf v1.32.0
+	k8s.io/api v0.25.3
+	k8s.io/apimachinery v0.25.3
+)
+
+require (
+	github.com/fatih/color v1.16.0 // indirect
+	github.com/fsnotify/fsnotify v1.6.0 // indirect
+	github.com/go-logr/logr v1.2.4 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/google/gofuzz v1.2.0 // indirect
+	github.com/hashicorp/errwrap v1.0.0 // indirect
+	github.com/hashicorp/go-hclog v1.6.2 // indirect
+	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/hashicorp/yamux v0.1.1 // indirect
+	github.com/magiconair/properties v1.8.5 // indirect
+	github.com/mattn/go-colorable v0.1.13 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/mitchellh/go-testing-interface v1.14.1 // indirect
+	github.com/mitchellh/mapstructure v1.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/oklog/run v1.1.0 // indirect
+	github.com/pelletier/go-toml v1.9.3 // indirect
+	github.com/spf13/afero v1.6.0 // indirect
+	github.com/spf13/cast v1.3.1 // indirect
+	github.com/spf13/jwalterweatherman v1.1.0 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
+	github.com/stretchr/testify v1.8.4 // indirect
+	github.com/subosito/gotenv v1.2.0 // indirect
+	golang.org/x/net v0.21.0 // indirect
+	golang.org/x/sys v0.17.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect
+	gopkg.in/inf.v0 v0.9.1 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+	k8s.io/klog/v2 v2.80.0 // indirect
+	k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
+	sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
+	sigs.k8s.io/yaml v1.3.0 // indirect
+)

+ 689 - 0
core/go.sum

@@ -0,0 +1,689 @@
+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=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
+cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
+cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+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/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/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=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
+github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
+github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+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/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=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
+github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+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-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
+github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+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/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=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+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/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+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=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+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/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I=
+github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+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.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.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A=
+github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+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=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+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/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=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
+github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
+github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
+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=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+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/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
+github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+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.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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+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-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+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=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+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=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
+github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/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/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
+github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+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/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
+github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+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/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
+github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
+github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+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.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
+github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+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.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+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=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+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=
+go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE=
+golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/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-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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+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=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+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/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=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+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-20210510120138-977fb7262007/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-20210927094055-39ccf1dd6fa6/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-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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+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=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+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/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=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+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-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-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+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/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=
+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=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
+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/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=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
+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.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
+google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
+google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+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.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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=
+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=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+k8s.io/api v0.25.3 h1:Q1v5UFfYe87vi5H7NU0p4RXC26PPMT8KOpr1TLQbCMQ=
+k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI=
+k8s.io/apimachinery v0.25.3 h1:7o9ium4uyUOM76t6aunP0nZuex7gDf8VGwkR5RcJnQc=
+k8s.io/apimachinery v0.25.3/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo=
+k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
+k8s.io/klog/v2 v2.80.0 h1:lyJt0TWMPaGoODa8B8bUuxgHS3W/m/bNr2cca3brA/g=
+k8s.io/klog/v2 v2.80.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
+k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
+sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

+ 75 - 0
core/pkg/clusters/clusterinfo.go

@@ -0,0 +1,75 @@
+package clusters
+
+// The following constants are used as keys into the cluster info map data structure
+const (
+	ClusterInfoIdKey               = "id"
+	ClusterInfoNameKey             = "name"
+	ClusterInfoProviderKey         = "provider"
+	ClusterInfoProjectKey          = "project"
+	ClusterInfoAccountKey          = "account"
+	ClusterInfoRegionKey           = "region"
+	ClusterInfoProvisionerKey      = "provisioner"
+	ClusterInfoProfileKey          = "clusterProfile"
+	ClusterInfoLogCollectionKey    = "logCollection"
+	ClusterInfoProductAnalyticsKey = "productAnalytics"
+	ClusterInfoErrorReportingKey   = "errorReporting"
+	ClusterInfoValuesReportingKey  = "valuesReporting"
+	ClusterInfoThanosEnabledKey    = "thanosEnabled"
+	ClusterInfoThanosOffsetKey     = "thanosOffset"
+	ClusterInfoVersionKey          = "version"
+)
+
+// ClusterInfo holds attributes of Cluster from metrics pulled from Prometheus
+type ClusterInfo struct {
+	ID          string `json:"id"`
+	Name        string `json:"name"`
+	Profile     string `json:"profile"`
+	Provider    string `json:"provider"`
+	Account     string `json:"account"`
+	Project     string `json:"project"`
+	Region      string `json:"region"`
+	Provisioner string `json:"provisioner"`
+}
+
+// Clone creates a copy of ClusterInfo and returns it
+func (ci *ClusterInfo) Clone() *ClusterInfo {
+	if ci == nil {
+		return nil
+	}
+
+	return &ClusterInfo{
+		ID:          ci.ID,
+		Name:        ci.Name,
+		Profile:     ci.Profile,
+		Provider:    ci.Provider,
+		Account:     ci.Account,
+		Project:     ci.Project,
+		Region:      ci.Region,
+		Provisioner: ci.Provisioner,
+	}
+}
+
+type ClusterMap interface {
+	// GetClusterIDs returns a slice containing all of the cluster identifiers.
+	GetClusterIDs() []string
+
+	// AsMap returns the cluster map as a standard go map
+	AsMap() map[string]*ClusterInfo
+
+	// InfoFor returns the ClusterInfo entry for the provided clusterID or nil if it
+	// doesn't exist
+	InfoFor(clusterID string) *ClusterInfo
+
+	// NameFor returns the name of the cluster provided the clusterID.
+	NameFor(clusterID string) string
+
+	// NameIDFor returns an identifier in the format "<clusterName>/<clusterID>" if the cluster has an
+	// assigned name. Otherwise, just the clusterID is returned.
+	NameIDFor(clusterID string) string
+}
+
+// ClusterInfoProvider is a contract which is capable of performing cluster info lookups.
+type ClusterInfoProvider interface {
+	// GetClusterInfo returns a string map containing the local/remote connected cluster info
+	GetClusterInfo() map[string]string
+}

+ 0 - 0
pkg/collections/blockingqueue.go → core/pkg/collections/blockingqueue.go


+ 1 - 1
pkg/env/env.go → core/pkg/env/env.go

@@ -4,7 +4,7 @@ import (
 	"os"
 	"time"
 
-	"github.com/opencost/opencost/pkg/util/mapper"
+	"github.com/opencost/opencost/core/pkg/util/mapper"
 )
 
 //--------------------------------------------------------------------------

+ 42 - 0
core/pkg/filter/allocation/fields.go

@@ -0,0 +1,42 @@
+package allocation
+
+import (
+	"github.com/opencost/opencost/core/pkg/filter/fieldstrings"
+)
+
+// AllocationField is an enum that represents Allocation-specific fields that can be
+// filtered on (namespace, label, etc.)
+type AllocationField string
+
+// If you add a AllocationFilterField, make sure to update field maps to return the correct
+// Allocation value
+// does not enforce exhaustive pattern matching on "enum" types.
+const (
+	FieldClusterID      AllocationField = AllocationField(fieldstrings.FieldClusterID)
+	FieldNode           AllocationField = AllocationField(fieldstrings.FieldNode)
+	FieldNamespace      AllocationField = AllocationField(fieldstrings.FieldNamespace)
+	FieldControllerKind AllocationField = AllocationField(fieldstrings.FieldControllerKind)
+	FieldControllerName AllocationField = AllocationField(fieldstrings.FieldControllerName)
+	FieldPod            AllocationField = AllocationField(fieldstrings.FieldPod)
+	FieldContainer      AllocationField = AllocationField(fieldstrings.FieldContainer)
+	FieldProvider       AllocationField = AllocationField(fieldstrings.FieldProvider)
+	FieldServices       AllocationField = AllocationField(fieldstrings.FieldServices)
+	FieldLabel          AllocationField = AllocationField(fieldstrings.FieldLabel)
+	FieldAnnotation     AllocationField = AllocationField(fieldstrings.FieldAnnotation)
+)
+
+// AllocationAlias represents an alias field type for allocations.
+// Filtering based on label aliases (team, department, etc.) should be a
+// responsibility of the query handler. By the time it reaches this
+// structured representation, we shouldn't have to be aware of what is
+// aliased to what. The aliases correspond to either a label or annotation,
+// defined by the user.
+type AllocationAlias string
+
+const (
+	AliasDepartment  AllocationAlias = AllocationAlias(fieldstrings.AliasDepartment)
+	AliasEnvironment AllocationAlias = AllocationAlias(fieldstrings.AliasEnvironment)
+	AliasOwner       AllocationAlias = AllocationAlias(fieldstrings.AliasOwner)
+	AliasProduct     AllocationAlias = AllocationAlias(fieldstrings.AliasProduct)
+	AliasTeam        AllocationAlias = AllocationAlias(fieldstrings.AliasTeam)
+)

+ 11 - 11
pkg/filter21/allocation/parser.go → core/pkg/filter/allocation/parser.go

@@ -1,6 +1,6 @@
 package allocation
 
-import "github.com/opencost/opencost/pkg/filter21/ast"
+import "github.com/opencost/opencost/core/pkg/filter/ast"
 
 // a slice of all the allocation field instances the lexer should recognize as
 // valid left-hand comparators
@@ -8,8 +8,8 @@ var allocationFilterFields []*ast.Field = []*ast.Field{
 	ast.NewField(FieldClusterID),
 	ast.NewField(FieldNode),
 	ast.NewField(FieldNamespace),
-	ast.NewField(FieldControllerName),
-	ast.NewField(FieldControllerKind),
+	ast.NewField(FieldControllerName, ast.FieldAttributeNilable),
+	ast.NewField(FieldControllerKind, ast.FieldAttributeNilable),
 	ast.NewField(FieldContainer),
 	ast.NewField(FieldPod),
 	ast.NewField(FieldProvider),
@@ -26,16 +26,16 @@ var allocationFilterFields []*ast.Field = []*ast.Field{
 // fieldMap is a lazily loaded mapping from AllocationField to ast.Field
 var fieldMap map[AllocationField]*ast.Field
 
-// DefaultFieldByName returns only default allocation filter fields by name.
-func DefaultFieldByName(field AllocationField) *ast.Field {
-	if fieldMap == nil {
-		fieldMap = make(map[AllocationField]*ast.Field, len(allocationFilterFields))
-		for _, f := range allocationFilterFields {
-			ff := *f
-			fieldMap[AllocationField(ff.Name)] = &ff
-		}
+func init() {
+	fieldMap = make(map[AllocationField]*ast.Field, len(allocationFilterFields))
+	for _, f := range allocationFilterFields {
+		ff := *f
+		fieldMap[AllocationField(ff.Name)] = &ff
 	}
+}
 
+// DefaultFieldByName returns only default allocation filter fields by name.
+func DefaultFieldByName(field AllocationField) *ast.Field {
 	if af, ok := fieldMap[field]; ok {
 		afcopy := *af
 		return &afcopy

+ 1 - 1
pkg/filter21/allocation/parser_test.go → core/pkg/filter/allocation/parser_test.go

@@ -6,7 +6,7 @@ import (
 	"testing"
 
 	"github.com/hashicorp/go-multierror"
-	"github.com/opencost/opencost/pkg/filter21/ast"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
 )
 
 var parser ast.FilterParser = NewAllocationFilterParser()

+ 39 - 0
core/pkg/filter/asset/fields.go

@@ -0,0 +1,39 @@
+package asset
+
+import (
+	"github.com/opencost/opencost/core/pkg/filter/fieldstrings"
+)
+
+// AssetField is an enum that represents Asset-specific fields that can be
+// filtered on (namespace, label, etc.)
+type AssetField string
+
+// If you add a AssetField, make sure to update field maps to return the correct
+// Asset value does not enforce exhaustive pattern matching on "enum" types.
+const (
+	FieldName       AssetField = AssetField(fieldstrings.FieldName)
+	FieldType       AssetField = AssetField(fieldstrings.FieldType)
+	FieldCategory   AssetField = AssetField(fieldstrings.FieldCategory)
+	FieldClusterID  AssetField = AssetField(fieldstrings.FieldClusterID)
+	FieldProject    AssetField = AssetField(fieldstrings.FieldProject)
+	FieldProvider   AssetField = AssetField(fieldstrings.FieldProvider)
+	FieldProviderID AssetField = AssetField(fieldstrings.FieldProviderID)
+	FieldAccount    AssetField = AssetField(fieldstrings.FieldAccount)
+	FieldService    AssetField = AssetField(fieldstrings.FieldService)
+	FieldLabel      AssetField = AssetField(fieldstrings.FieldLabel)
+)
+
+// AssetAlias represents an alias field type for assets.
+// Filtering based on label aliases (team, department, etc.) should be a
+// responsibility of the query handler. By the time it reaches this
+// structured representation, we shouldn't have to be aware of what is
+// aliased to what.
+type AssetAlias string
+
+const (
+	DepartmentProp  AssetAlias = AssetAlias(fieldstrings.AliasDepartment)
+	EnvironmentProp AssetAlias = AssetAlias(fieldstrings.AliasEnvironment)
+	OwnerProp       AssetAlias = AssetAlias(fieldstrings.AliasOwner)
+	ProductProp     AssetAlias = AssetAlias(fieldstrings.AliasProduct)
+	TeamProp        AssetAlias = AssetAlias(fieldstrings.AliasTeam)
+)

+ 9 - 9
pkg/filter21/asset/parser.go → core/pkg/filter/asset/parser.go

@@ -1,6 +1,6 @@
 package asset
 
-import "github.com/opencost/opencost/pkg/filter21/ast"
+import "github.com/opencost/opencost/core/pkg/filter/ast"
 
 // a slice of all the asset field instances the lexer should recognize as
 // valid left-hand comparators
@@ -25,16 +25,16 @@ var assetFilterFields []*ast.Field = []*ast.Field{
 // fieldMap is a lazily loaded mapping from AllocationField to ast.Field
 var fieldMap map[AssetField]*ast.Field
 
-// DefaultFieldByName returns only default allocation filter fields by name.
-func DefaultFieldByName(field AssetField) *ast.Field {
-	if fieldMap == nil {
-		fieldMap = make(map[AssetField]*ast.Field, len(assetFilterFields))
-		for _, f := range assetFilterFields {
-			ff := *f
-			fieldMap[AssetField(ff.Name)] = &ff
-		}
+func init() {
+	fieldMap = make(map[AssetField]*ast.Field, len(assetFilterFields))
+	for _, f := range assetFilterFields {
+		ff := *f
+		fieldMap[AssetField(ff.Name)] = &ff
 	}
+}
 
+// DefaultFieldByName returns only default allocation filter fields by name.
+func DefaultFieldByName(field AssetField) *ast.Field {
 	if af, ok := fieldMap[field]; ok {
 		afcopy := *af
 		return &afcopy

+ 139 - 0
core/pkg/filter/ast/fields.go

@@ -0,0 +1,139 @@
+package ast
+
+// FieldType is an enumeration of specific types relevant to lexing and
+// parsing a filter.
+type FieldType int
+
+const (
+	FieldTypeDefault FieldType = 1 << iota
+	FieldTypeSlice
+	FieldTypeMap
+	FieldTypeAlias
+)
+
+// FieldAttribute is an enumeration of specific attributes that can be set
+// on each type of field.
+type FieldAttribute int
+
+const (
+	FieldAttributeNilable FieldAttribute = 1 << (iota + 4)
+)
+
+// fieldType with attributes is a convenience function for creating a field type with
+// attributes flags set.
+func fieldTypeWithAttributes(ft FieldType, attrs ...FieldAttribute) FieldType {
+	for _, attr := range attrs {
+		ft.Set(attr)
+	}
+
+	return ft
+}
+
+// Set updates the field type with the provided attribute.
+func (ft *FieldType) Set(attr FieldAttribute) {
+	*ft |= FieldType(attr)
+}
+
+// Unset removes the provided attribute from the field type.
+func (ft *FieldType) Unset(attr FieldAttribute) {
+	*ft &= ^FieldType(attr)
+}
+
+// Is returns true if the field type has the provided attribute.
+func (ft FieldType) Is(attr FieldAttribute) bool {
+	return ft&FieldType(attr) != 0
+}
+
+// IsDefault returns true if the type is the default/base type.
+func (ft FieldType) IsDefault() bool {
+	return ft&FieldTypeDefault != 0
+}
+
+// IsSlice returns true if the type is a slice type.
+func (ft FieldType) IsSlice() bool {
+	return ft&FieldTypeSlice != 0
+}
+
+// IsMap returns true if the type is a map type.
+func (ft FieldType) IsMap() bool {
+	return ft&FieldTypeMap != 0
+}
+
+// IsAlias returns true if the type is an alias type.
+func (ft FieldType) IsAlias() bool {
+	return ft&FieldTypeAlias != 0
+}
+
+// Field is a Lexer input which acts as a mapping of identifiers used to lex/parse filters.
+type Field struct {
+	// Name contains the name of the specific field as it appears in language.
+	Name string
+
+	fieldType FieldType
+}
+
+// Field equivalence is determined by name and type.
+func (f *Field) Equal(other *Field) bool {
+	if f == nil || other == nil {
+		return false
+	}
+
+	return f.Name == other.Name && f.fieldType == other.fieldType
+}
+
+// IsSlice returns true if the field is a slice. This instructs the lexer that the field
+// should allow contains operations.
+func (f *Field) IsSlice() bool {
+	return f.fieldType.IsSlice()
+}
+
+// IsMap returns true if the field is a map. This instructs the lexer that the field should
+// allow keyed-access operations.
+func (f *Field) IsMap() bool {
+	return f.fieldType.IsMap()
+}
+
+// IsAlias returns true if the field is an alias type. This instructs the lexer that the field
+// is an alias for custom logical resolution by an external compiler.
+func (f *Field) IsAlias() bool {
+	return f.fieldType.IsAlias()
+}
+
+// IsNilable returns true if the field is an default field type that can contain a nil value. Only
+// specific compilers will need to know this information. ie: Go does not have a nil value for strings,
+// but SQL does.
+func (f *Field) IsNilable() bool {
+	return f.fieldType.Is(FieldAttributeNilable)
+}
+
+// NewField creates a default string field using the provided name.
+func NewField[T ~string](name T, attrs ...FieldAttribute) *Field {
+	return &Field{
+		Name:      string(name),
+		fieldType: fieldTypeWithAttributes(FieldTypeDefault, attrs...),
+	}
+}
+
+// NewSliceField creates a slice field using the provided name.
+func NewSliceField[T ~string](name T, attrs ...FieldAttribute) *Field {
+	return &Field{
+		Name:      string(name),
+		fieldType: fieldTypeWithAttributes(FieldTypeSlice, attrs...),
+	}
+}
+
+// NewMapField creates a new map field using the provided name.
+func NewMapField[T ~string](name T, attrs ...FieldAttribute) *Field {
+	return &Field{
+		Name:      string(name),
+		fieldType: fieldTypeWithAttributes(FieldTypeMap, attrs...),
+	}
+}
+
+// NewAliasField creates a new alias field using the provided name.
+func NewAliasField[T ~string](name T, attrs ...FieldAttribute) *Field {
+	return &Field{
+		Name:      string(name),
+		fieldType: fieldTypeWithAttributes(FieldTypeAlias, attrs...),
+	}
+}

+ 0 - 0
pkg/filter21/ast/lexer.go → core/pkg/filter/ast/lexer.go


+ 0 - 0
pkg/filter21/ast/lexer_test.go → core/pkg/filter/ast/lexer_test.go


+ 9 - 0
pkg/filter21/ast/ops.go → core/pkg/filter/ast/ops.go

@@ -191,3 +191,12 @@ type ContainsSuffixOp struct {
 func (_ *ContainsSuffixOp) Op() FilterOp {
 	return FilterOpContainsSuffix
 }
+
+func Not(fn FilterNode) FilterNode {
+	return &NotOp{Operand: fn}
+}
+
+func IsVoid(fn FilterNode) bool {
+	_, ok := fn.(*VoidOp)
+	return ok
+}

+ 0 - 0
pkg/filter21/ast/parser.go → core/pkg/filter/ast/parser.go


+ 0 - 0
pkg/filter21/ast/tree.go → core/pkg/filter/ast/tree.go


+ 2 - 2
pkg/filter21/ast/walker.go → core/pkg/filter/ast/walker.go

@@ -5,7 +5,7 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/opencost/opencost/pkg/filter21/util"
+	"github.com/opencost/opencost/core/pkg/filter/util"
 	"golang.org/x/text/cases"
 	"golang.org/x/text/language"
 )
@@ -167,7 +167,7 @@ func OpStringFor(node FilterNode, traversalState TraversalState, depth int) stri
 
 	switch n := node.(type) {
 	case *VoidOp:
-		open += ")"
+		open += "Empty }\n"
 	case *EqualOp:
 		open += fmt.Sprintf("Left: %s, Right: %s }\n", n.Left.String(), n.Right)
 	case *ContainsOp:

+ 0 - 0
pkg/filter21/ast/walker_test.go → core/pkg/filter/ast/walker_test.go


+ 18 - 0
core/pkg/filter/cloudcost/fields.go

@@ -0,0 +1,18 @@
+package cloudcost
+
+import (
+	"github.com/opencost/opencost/core/pkg/filter/fieldstrings"
+)
+
+// CloudCostField is an enum that represents CloudCost specific fields that can be filtered
+type CloudCostField string
+
+const (
+	FieldInvoiceEntityID CloudCostField = CloudCostField(fieldstrings.FieldInvoiceEntityID)
+	FieldAccountID       CloudCostField = CloudCostField(fieldstrings.FieldAccountID)
+	FieldProvider        CloudCostField = CloudCostField(fieldstrings.FieldProvider)
+	FieldProviderID      CloudCostField = CloudCostField(fieldstrings.FieldProviderID)
+	FieldCategory        CloudCostField = CloudCostField(fieldstrings.FieldCategory)
+	FieldService         CloudCostField = CloudCostField(fieldstrings.FieldService)
+	FieldLabel           CloudCostField = CloudCostField(fieldstrings.FieldLabel)
+)

+ 9 - 9
pkg/filter21/cloudcost/parser.go → core/pkg/filter/cloudcost/parser.go

@@ -1,6 +1,6 @@
 package cloudcost
 
-import "github.com/opencost/opencost/pkg/filter21/ast"
+import "github.com/opencost/opencost/core/pkg/filter/ast"
 
 // a slice of all the cloud costs field instances the lexer should recognize as
 // valid left-hand comparators
@@ -17,16 +17,16 @@ var cloudCostFilterFields []*ast.Field = []*ast.Field{
 // fieldMap is a lazily loaded mapping from CloudAggregationField to ast.Field
 var fieldMap map[CloudCostField]*ast.Field
 
-// DefaultFieldByName returns only default cloud cost filter fields by name.
-func DefaultFieldByName(field CloudCostField) *ast.Field {
-	if fieldMap == nil {
-		fieldMap = make(map[CloudCostField]*ast.Field, len(cloudCostFilterFields))
-		for _, f := range cloudCostFilterFields {
-			ff := *f
-			fieldMap[CloudCostField(ff.Name)] = &ff
-		}
+func init() {
+	fieldMap = make(map[CloudCostField]*ast.Field, len(cloudCostFilterFields))
+	for _, f := range cloudCostFilterFields {
+		ff := *f
+		fieldMap[CloudCostField(ff.Name)] = &ff
 	}
+}
 
+// DefaultFieldByName returns only default cloud cost filter fields by name.
+func DefaultFieldByName(field CloudCostField) *ast.Field {
 	if af, ok := fieldMap[field]; ok {
 		afcopy := *af
 		return &afcopy

+ 35 - 0
core/pkg/filter/fieldstrings/fieldstrings.go

@@ -0,0 +1,35 @@
+package fieldstrings
+
+// These strings are the central source of filter fields across all types of
+// filters. Many filter types share fields; defining common consts means that
+// there should be no drift between types.
+const (
+	FieldClusterID      string = "cluster"
+	FieldNode           string = "node"
+	FieldNamespace      string = "namespace"
+	FieldControllerKind string = "controllerKind"
+	FieldControllerName string = "controllerName"
+	FieldPod            string = "pod"
+	FieldContainer      string = "container"
+	FieldProvider       string = "provider"
+	FieldServices       string = "services"
+	FieldLabel          string = "label"
+	FieldAnnotation     string = "annotation"
+
+	FieldName       string = "name"
+	FieldType       string = "assetType"
+	FieldCategory   string = "category"
+	FieldProject    string = "project"
+	FieldProviderID string = "providerID"
+	FieldAccount    string = "account"
+	FieldService    string = "service"
+
+	FieldInvoiceEntityID string = "invoiceEntityID"
+	FieldAccountID       string = "accountID"
+
+	AliasDepartment  string = "department"
+	AliasEnvironment string = "environment"
+	AliasOwner       string = "owner"
+	AliasProduct     string = "product"
+	AliasTeam        string = "team"
+)

+ 1 - 1
pkg/filter21/filter.go → core/pkg/filter/filter.go

@@ -1,6 +1,6 @@
 package filter
 
-import "github.com/opencost/opencost/pkg/filter21/ast"
+import "github.com/opencost/opencost/core/pkg/filter/ast"
 
 // Filter is just the root node of an AST. There are various compiler implementations
 // available to create data source specific filtering from the AST.

+ 18 - 0
core/pkg/filter/k8sobject/fields.go

@@ -0,0 +1,18 @@
+package k8sobject
+
+import (
+	"github.com/opencost/opencost/core/pkg/filter/fieldstrings"
+)
+
+// K8sObjectField is an enum that represents K8sObject-specific fields that can
+// be filtered on.
+type K8sObjectField string
+
+const (
+	FieldNamespace      K8sObjectField = K8sObjectField(fieldstrings.FieldNamespace)
+	FieldControllerKind K8sObjectField = K8sObjectField(fieldstrings.FieldControllerKind)
+	FieldControllerName K8sObjectField = K8sObjectField(fieldstrings.FieldControllerName)
+	FieldPod            K8sObjectField = K8sObjectField(fieldstrings.FieldPod)
+	FieldLabel          K8sObjectField = K8sObjectField(fieldstrings.FieldLabel)
+	FieldAnnotation     K8sObjectField = K8sObjectField(fieldstrings.FieldAnnotation)
+)

+ 43 - 0
core/pkg/filter/k8sobject/parser.go

@@ -0,0 +1,43 @@
+package k8sobject
+
+import (
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+)
+
+// a slice of all the allocation field instances the lexer should recognize as
+// valid left-hand comparators
+var k8sObjectFilterFields []*ast.Field = []*ast.Field{
+	ast.NewField(FieldNamespace),
+	ast.NewField(FieldControllerName, ast.FieldAttributeNilable),
+	ast.NewField(FieldControllerKind, ast.FieldAttributeNilable),
+	ast.NewField(FieldPod),
+	ast.NewMapField(FieldLabel),
+	ast.NewMapField(FieldAnnotation),
+}
+
+// fieldMap is a lazily loaded mapping from AllocationField to ast.Field
+var fieldMap map[K8sObjectField]*ast.Field
+
+func init() {
+	fieldMap = make(map[K8sObjectField]*ast.Field, len(k8sObjectFilterFields))
+	for _, f := range k8sObjectFilterFields {
+		ff := *f
+		fieldMap[K8sObjectField(ff.Name)] = &ff
+	}
+}
+
+// DefaultFieldByName returns only default allocation filter fields by name.
+func DefaultFieldByName(field K8sObjectField) *ast.Field {
+	if af, ok := fieldMap[field]; ok {
+		afcopy := *af
+		return &afcopy
+	}
+
+	return nil
+}
+
+// NewK8sObjectFilterParser creates a new `ast.FilterParser` implementation for
+// K8s runtime.Objects.
+func NewK8sObjectFilterParser() ast.FilterParser {
+	return ast.NewFilterParser(k8sObjectFilterFields)
+}

+ 1 - 1
pkg/filter/allcut.go → core/pkg/filter/legacy/allcut.go

@@ -1,4 +1,4 @@
-package filter
+package legacy
 
 // AllCut is a filter that matches nothing. This is useful
 // for applications like authorization, where a user/group/role may be disallowed

+ 1 - 1
pkg/filter/allpass.go → core/pkg/filter/legacy/allpass.go

@@ -1,4 +1,4 @@
-package filter
+package legacy
 
 // AllPass is a filter that matches everything and is the same as no filter. It is implemented here as a guard
 // against universal operations occurring in the absence of filters.

+ 1 - 1
pkg/filter/and.go → core/pkg/filter/legacy/and.go

@@ -1,4 +1,4 @@
-package filter
+package legacy
 
 import (
 	"fmt"

+ 23 - 23
pkg/filter/cloudcost/cloudcost.go → core/pkg/filter/legacy/cloudcost/cloudcost.go

@@ -4,10 +4,10 @@ import (
 	"reflect"
 	"strings"
 
-	"github.com/opencost/opencost/pkg/filter"
-	"github.com/opencost/opencost/pkg/kubecost"
-	"github.com/opencost/opencost/pkg/log"
-	"github.com/opencost/opencost/pkg/util/mapper"
+	filter "github.com/opencost/opencost/core/pkg/filter/legacy"
+	"github.com/opencost/opencost/core/pkg/log"
+	"github.com/opencost/opencost/core/pkg/opencost"
+	"github.com/opencost/opencost/core/pkg/util/mapper"
 )
 
 type CloudCostFilter struct {
@@ -33,7 +33,7 @@ func parseWildcardEnd(rawFilterValue string) (string, bool) {
 	return strings.TrimSuffix(rawFilterValue, "*"), strings.HasSuffix(rawFilterValue, "*")
 }
 
-func CloudCostFilterFromParams(pmr mapper.PrimitiveMapReader) filter.Filter[*kubecost.CloudCost] {
+func CloudCostFilterFromParams(pmr mapper.PrimitiveMapReader) filter.Filter[*opencost.CloudCost] {
 	ccFilter := convertFilterQueryParams(pmr)
 	return ParseCloudCostFilter(ccFilter)
 }
@@ -49,37 +49,37 @@ func convertFilterQueryParams(pmr mapper.PrimitiveMapReader) CloudCostFilter {
 		Services:         pmr.GetList("filterServices", ","),
 	}
 }
-func ParseCloudCostFilter(filters CloudCostFilter) filter.Filter[*kubecost.CloudCost] {
-	result := filter.And[*kubecost.CloudCost]{
-		Filters: []filter.Filter[*kubecost.CloudCost]{},
+func ParseCloudCostFilter(filters CloudCostFilter) filter.Filter[*opencost.CloudCost] {
+	result := filter.And[*opencost.CloudCost]{
+		Filters: []filter.Filter[*opencost.CloudCost]{},
 	}
 
 	if len(filters.InvoiceEntityIDs) > 0 {
-		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.InvoiceEntityIDs, kubecost.CloudCostInvoiceEntityIDProp))
+		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.InvoiceEntityIDs, opencost.CloudCostInvoiceEntityIDProp))
 	}
 
 	if len(filters.AccountIDs) > 0 {
-		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.AccountIDs, kubecost.CloudCostAccountIDProp))
+		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.AccountIDs, opencost.CloudCostAccountIDProp))
 	}
 
 	if len(filters.Providers) > 0 {
-		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.Providers, kubecost.CloudCostProviderProp))
+		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.Providers, opencost.CloudCostProviderProp))
 	}
 
 	if len(filters.ProviderIDs) > 0 {
-		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.ProviderIDs, kubecost.CloudCostProviderIDProp))
+		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.ProviderIDs, opencost.CloudCostProviderIDProp))
 	}
 
 	if len(filters.Services) > 0 {
-		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.Services, kubecost.CloudCostServiceProp))
+		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.Services, opencost.CloudCostServiceProp))
 	}
 
 	if len(filters.Categories) > 0 {
-		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.Categories, kubecost.CloudCostCategoryProp))
+		result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.Categories, opencost.CloudCostCategoryProp))
 	}
 
 	if len(filters.Labels) > 0 {
-		result.Filters = append(result.Filters, filterV1DoubleValueFromList(filters.Labels, kubecost.CloudCostLabelProp))
+		result.Filters = append(result.Filters, filterV1DoubleValueFromList(filters.Labels, opencost.CloudCostLabelProp))
 	}
 
 	if len(result.Filters) == 0 {
@@ -89,16 +89,16 @@ func ParseCloudCostFilter(filters CloudCostFilter) filter.Filter[*kubecost.Cloud
 	return result
 }
 
-func filterV1SingleValueFromList(rawFilterValues []string, field string) filter.Filter[*kubecost.CloudCost] {
-	result := filter.Or[*kubecost.CloudCost]{
-		Filters: []filter.Filter[*kubecost.CloudCost]{},
+func filterV1SingleValueFromList(rawFilterValues []string, field string) filter.Filter[*opencost.CloudCost] {
+	result := filter.Or[*opencost.CloudCost]{
+		Filters: []filter.Filter[*opencost.CloudCost]{},
 	}
 
 	for _, filterValue := range rawFilterValues {
 		filterValue = strings.TrimSpace(filterValue)
 		filterValue, wildcard := parseWildcardEnd(filterValue)
 
-		subFilter := filter.StringProperty[*kubecost.CloudCost]{
+		subFilter := filter.StringProperty[*opencost.CloudCost]{
 			Field: field,
 			Op:    filter.StringEquals,
 			Value: filterValue,
@@ -119,9 +119,9 @@ func filterV1SingleValueFromList(rawFilterValues []string, field string) filter.
 //
 // The v1 query language (e.g. "filterLabels=app:foo,l2:bar") uses OR within
 // a field (e.g. label[app] = foo OR label[l2] = bar)
-func filterV1DoubleValueFromList(rawFilterValuesUnsplit []string, filterField string) filter.Filter[*kubecost.CloudCost] {
-	result := filter.Or[*kubecost.CloudCost]{
-		Filters: []filter.Filter[*kubecost.CloudCost]{},
+func filterV1DoubleValueFromList(rawFilterValuesUnsplit []string, filterField string) filter.Filter[*opencost.CloudCost] {
+	result := filter.Or[*opencost.CloudCost]{
+		Filters: []filter.Filter[*opencost.CloudCost]{},
 	}
 
 	for _, unsplit := range rawFilterValuesUnsplit {
@@ -135,7 +135,7 @@ func filterV1DoubleValueFromList(rawFilterValuesUnsplit []string, filterField st
 			val := strings.TrimSpace(split[1])
 			val, wildcard := parseWildcardEnd(val)
 
-			subFilter := filter.StringMapProperty[*kubecost.CloudCost]{
+			subFilter := filter.StringMapProperty[*opencost.CloudCost]{
 				Field: filterField,
 				// All v1 filters are equality comparisons
 				Op:    filter.StringMapEquals,

+ 0 - 0
pkg/filter/cloudcost/cloudcost_test.go → core/pkg/filter/legacy/cloudcost/cloudcost_test.go


+ 1 - 1
pkg/filter/filter.go → core/pkg/filter/legacy/filter.go

@@ -1,4 +1,4 @@
-package filter
+package legacy
 
 // Filter represents anything that can be used to filter given generic type T.
 //

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 237 - 237
core/pkg/filter/legacy/filter_test.go


+ 1 - 1
pkg/filter/not.go → core/pkg/filter/legacy/not.go

@@ -1,4 +1,4 @@
-package filter
+package legacy
 
 import "fmt"
 

+ 1 - 1
pkg/filter/or.go → core/pkg/filter/legacy/or.go

@@ -1,4 +1,4 @@
-package filter
+package legacy
 
 import (
 	"fmt"

+ 2 - 2
pkg/filter/stringmapproperty.go → core/pkg/filter/legacy/stringmapproperty.go

@@ -1,10 +1,10 @@
-package filter
+package legacy
 
 import (
 	"fmt"
 	"strings"
 
-	"github.com/opencost/opencost/pkg/log"
+	"github.com/opencost/opencost/core/pkg/log"
 )
 
 const unallocatedSuffix = "__unallocated__"

+ 2 - 2
pkg/filter/stringproperty.go → core/pkg/filter/legacy/stringproperty.go

@@ -1,10 +1,10 @@
-package filter
+package legacy
 
 import (
 	"fmt"
 	"strings"
 
-	"github.com/opencost/opencost/pkg/log"
+	"github.com/opencost/opencost/core/pkg/log"
 )
 
 // StringPropertied is used to validate the name of a property field and return its value

+ 2 - 2
pkg/filter/stringsliceproperty.go → core/pkg/filter/legacy/stringsliceproperty.go

@@ -1,10 +1,10 @@
-package filter
+package legacy
 
 import (
 	"fmt"
 	"strings"
 
-	"github.com/opencost/opencost/pkg/log"
+	"github.com/opencost/opencost/core/pkg/log"
 )
 
 type StringSlicePropertied interface {

+ 3 - 3
pkg/filter/window.go → core/pkg/filter/legacy/window.go

@@ -1,4 +1,4 @@
-package filter
+package legacy
 
 //
 //import (
@@ -8,7 +8,7 @@ package filter
 //)
 //
 //type Windowed interface {
-//	GetWindow() kubecost.Window
+//	GetWindow() opencost.Window
 //}
 //
 //// WindowOperation are operations that can be performed on types that have windows
@@ -20,7 +20,7 @@ package filter
 //
 //// WindowCondition is a filter can be used on any type that has a window and implements GetWindow()
 //type WindowCondition[T Windowed] struct {
-//	Window kubecost.Window
+//	Window opencost.Window
 //	Op     WindowOperation
 //}
 //

+ 12 - 12
pkg/filter/window_test.go → core/pkg/filter/legacy/window_test.go

@@ -1,4 +1,4 @@
-package filter_test
+package legacy_test
 
 // import (
 // 	"github.com/opencost/opencost/pkg/kubecost"
@@ -7,15 +7,15 @@ package filter_test
 // )
 
 // type windowedImpl struct {
-// 	kubecost.Window
+// 	opencost.Window
 // }
 
-// func (w *windowedImpl) GetWindow() kubecost.Window {
+// func (w *windowedImpl) GetWindow() opencost.Window {
 // 	return w.Window
 // }
 
 // func newWindowedImpl(start, end *time.Time) *windowedImpl {
-// 	return &windowedImpl{kubecost.NewWindow(start, end)}
+// 	return &windowedImpl{opencost.NewWindow(start, end)}
 // }
 
 // func Test_WindowContains_Matches(t *testing.T) {
@@ -31,7 +31,7 @@ package filter_test
 // 		"fully contains": {
 // 			windowed: newWindowedImpl(&one, &two),
 // 			filter: WindowCondition[*windowedImpl]{
-// 				Window: kubecost.NewWindow(&noon, &three),
+// 				Window: opencost.NewWindow(&noon, &three),
 // 				Op:     WindowContains,
 // 			},
 
@@ -40,7 +40,7 @@ package filter_test
 // 		"window matches": {
 // 			windowed: newWindowedImpl(&one, &two),
 // 			filter: WindowCondition[*windowedImpl]{
-// 				Window: kubecost.NewWindow(&one, &two),
+// 				Window: opencost.NewWindow(&one, &two),
 // 				Op:     WindowContains,
 // 			},
 
@@ -49,7 +49,7 @@ package filter_test
 // 		"contains start": {
 // 			windowed: newWindowedImpl(&one, &three),
 // 			filter: WindowCondition[*windowedImpl]{
-// 				Window: kubecost.NewWindow(&noon, &two),
+// 				Window: opencost.NewWindow(&noon, &two),
 // 				Op:     WindowContains,
 // 			},
 
@@ -58,7 +58,7 @@ package filter_test
 // 		"contains end": {
 // 			windowed: newWindowedImpl(&noon, &two),
 // 			filter: WindowCondition[*windowedImpl]{
-// 				Window: kubecost.NewWindow(&one, &three),
+// 				Window: opencost.NewWindow(&one, &three),
 // 				Op:     WindowContains,
 // 			},
 
@@ -67,7 +67,7 @@ package filter_test
 // 		"window start = filter end": {
 // 			windowed: newWindowedImpl(&one, &two),
 // 			filter: WindowCondition[*windowedImpl]{
-// 				Window: kubecost.NewWindow(&noon, &one),
+// 				Window: opencost.NewWindow(&noon, &one),
 // 				Op:     WindowContains,
 // 			},
 
@@ -76,7 +76,7 @@ package filter_test
 // 		"window end = filter start": {
 // 			windowed: newWindowedImpl(&noon, &one),
 // 			filter: WindowCondition[*windowedImpl]{
-// 				Window: kubecost.NewWindow(&one, &two),
+// 				Window: opencost.NewWindow(&one, &two),
 // 				Op:     WindowContains,
 // 			},
 
@@ -85,7 +85,7 @@ package filter_test
 // 		"window before": {
 // 			windowed: newWindowedImpl(&noon, &one),
 // 			filter: WindowCondition[*windowedImpl]{
-// 				Window: kubecost.NewWindow(&two, &three),
+// 				Window: opencost.NewWindow(&two, &three),
 // 				Op:     WindowContains,
 // 			},
 
@@ -94,7 +94,7 @@ package filter_test
 // 		"window after": {
 // 			windowed: newWindowedImpl(&two, &three),
 // 			filter: WindowCondition[*windowedImpl]{
-// 				Window: kubecost.NewWindow(&noon, &one),
+// 				Window: opencost.NewWindow(&noon, &one),
 // 				Op:     WindowContains,
 // 			},
 

+ 0 - 0
pkg/filter21/matcher/allcut.go → core/pkg/filter/matcher/allcut.go


+ 0 - 0
pkg/filter21/matcher/allpass.go → core/pkg/filter/matcher/allpass.go


+ 0 - 0
pkg/filter21/matcher/and.go → core/pkg/filter/matcher/and.go


+ 3 - 3
pkg/filter21/matcher/compiler.go → core/pkg/filter/matcher/compiler.go

@@ -3,9 +3,9 @@ package matcher
 import (
 	"fmt"
 
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/filter21/transform"
-	"github.com/opencost/opencost/pkg/filter21/util"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/filter/transform"
+	"github.com/opencost/opencost/core/pkg/filter/util"
 )
 
 // FieldMapper is the adapter which can fetch actual T instance data of type U

+ 0 - 0
pkg/filter21/matcher/matcher.go → core/pkg/filter/matcher/matcher.go


+ 4 - 4
pkg/filter21/matcher/matcher_test.go → core/pkg/filter/matcher/matcher_test.go

@@ -5,10 +5,10 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/opencost/opencost/pkg/filter21/allocation"
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/filter21/matcher"
-	"github.com/opencost/opencost/pkg/filter21/transform"
+	"github.com/opencost/opencost/core/pkg/filter/allocation"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/filter/matcher"
+	"github.com/opencost/opencost/core/pkg/filter/transform"
 )
 
 // MatcherCompiler for Allocation instances providing functions which map identifers

+ 0 - 0
pkg/filter21/matcher/not.go → core/pkg/filter/matcher/not.go


+ 0 - 0
pkg/filter21/matcher/or.go → core/pkg/filter/matcher/or.go


+ 2 - 2
pkg/filter21/matcher/stringmapmatcher.go → core/pkg/filter/matcher/stringmapmatcher.go

@@ -4,8 +4,8 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/log"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/log"
 )
 
 // StringMapMatcherFactory leverages a single MapFieldMapper[T] to generate instances of

+ 2 - 2
pkg/filter21/matcher/stringmatcher.go → core/pkg/filter/matcher/stringmatcher.go

@@ -4,8 +4,8 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/log"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/log"
 )
 
 // StringMatcherFactory leverages a single StringFieldMapper[T] to generate instances of

+ 2 - 2
pkg/filter21/matcher/stringslicematcher.go → core/pkg/filter/matcher/stringslicematcher.go

@@ -4,8 +4,8 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/log"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/log"
 )
 
 // StringMatcherFactory leverages a single StringSliceFieldMapper[T] to generate instances of

+ 13 - 5
pkg/filter21/ops/ops.go → core/pkg/filter/ops/ops.go

@@ -9,10 +9,12 @@ import (
 	"reflect"
 	"strings"
 
-	"github.com/opencost/opencost/pkg/filter21/allocation"
-	"github.com/opencost/opencost/pkg/filter21/asset"
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/util/typeutil"
+	"github.com/opencost/opencost/core/pkg/filter/allocation"
+	"github.com/opencost/opencost/core/pkg/filter/asset"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/filter/cloudcost"
+	"github.com/opencost/opencost/core/pkg/filter/k8sobject"
+	"github.com/opencost/opencost/core/pkg/util/typeutil"
 )
 
 // keyFieldType is used to extract field, key, and field type
@@ -25,12 +27,18 @@ type keyFieldType interface {
 // This is somewhat of a fancy solution, but allows us to "register" DefaultFieldByName funcs
 // funcs by Field type.
 var defaultFieldByType = map[string]any{
-	// typeutil.TypeOf[cloud.CloudAggregationField]():        cloud.DefaultFieldByName,
 	typeutil.TypeOf[allocation.AllocationField](): allocation.DefaultFieldByName,
 	typeutil.TypeOf[asset.AssetField]():           asset.DefaultFieldByName,
+	typeutil.TypeOf[cloudcost.CloudCostField]():   cloudcost.DefaultFieldByName,
+	typeutil.TypeOf[k8sobject.K8sObjectField]():   k8sobject.DefaultFieldByName,
 	// typeutil.TypeOf[containerstats.ContainerStatsField](): containerstats.DefaultFieldByName,
 }
 
+// RegisterDefaultFieldLookup registers a function that can be used to lookup a specific field type.
+func RegisterDefaultFieldLookup[T ~string](lookup func(T) *ast.Field) {
+	defaultFieldByType[typeutil.TypeOf[T]()] = lookup
+}
+
 // asField looks up a specific T field instance by name and returns the default
 // ast.Field value for that type.
 func asField[T ~string](field T) *ast.Field {

+ 5 - 5
pkg/filter21/ops/ops_test.go → core/pkg/filter/ops/ops_test.go

@@ -4,9 +4,9 @@ import (
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
-	"github.com/opencost/opencost/pkg/filter21/allocation"
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/filter21/ops"
+	"github.com/opencost/opencost/core/pkg/filter/allocation"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/filter/ops"
 )
 
 func TestBasicOpsBuilder(t *testing.T) {
@@ -23,8 +23,8 @@ func TestBasicOpsBuilder(t *testing.T) {
 	)
 
 	otherTree, err := parser.Parse(`
-		(namespace: "kubecost" | cluster: "cluster-one") +
-		services!~:"service-a" +
+		(namespace: "kubecost" | cluster: "cluster-one") + 
+		services!~:"service-a" + 
 		label[app]!: "cost-analyzer" +
 		label~:"foo"
 	`)

+ 1 - 4
pkg/filter21/transform/pass.go → core/pkg/filter/transform/pass.go

@@ -3,7 +3,7 @@ package transform
 import (
 	"fmt"
 
-	"github.com/opencost/opencost/pkg/filter21/ast"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
 )
 
 // CompilerPass is an interface which defines an implementation capable of
@@ -16,9 +16,6 @@ type CompilerPass interface {
 	Exec(filter ast.FilterNode) (ast.FilterNode, error)
 }
 
-// func CompilerPass(transformFunc func(ast.FilterNode) (ast.FilterNode, error)) (ast.FilterNode, error) {
-// }
-
 // ApplyAll applies all the compiler passes serially and returns the resulting
 // tree. This method copies the passes AST before executing the compiler passes.
 func ApplyAll(filter ast.FilterNode, passes []CompilerPass) (ast.FilterNode, error) {

+ 1 - 1
pkg/filter21/transform/promlabels.go → core/pkg/filter/transform/promlabels.go

@@ -3,7 +3,7 @@ package transform
 import (
 	"regexp"
 
-	"github.com/opencost/opencost/pkg/filter21/ast"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
 )
 
 // regex for invalid prometheus label characters

+ 1 - 1
pkg/filter21/transform/unallocated.go → core/pkg/filter/transform/unallocated.go

@@ -1,6 +1,6 @@
 package transform
 
-import "github.com/opencost/opencost/pkg/filter21/ast"
+import "github.com/opencost/opencost/core/pkg/filter/ast"
 
 const unallocatedSuffix = "__unallocated__"
 

+ 0 - 0
pkg/filter21/util/stack.go → core/pkg/filter/util/stack.go


+ 0 - 0
pkg/log/counter.go → core/pkg/log/counter.go


+ 0 - 0
pkg/log/counter_test.go → core/pkg/log/counter_test.go


+ 8 - 0
pkg/log/log.go → core/pkg/log/log.go

@@ -126,6 +126,14 @@ func Debugf(format string, a ...interface{}) {
 	log.Debug().Msgf(format, a...)
 }
 
+func Trace(msg string) {
+	log.Trace().Msg(msg)
+}
+
+func Tracef(format string, a ...interface{}) {
+	log.Trace().Msgf(format, a...)
+}
+
 func Fatalf(format string, a ...interface{}) {
 	log.Fatal().Msgf(format, a...)
 }

+ 0 - 0
pkg/log/profiler.go → core/pkg/log/profiler.go


+ 1049 - 0
core/pkg/model/pb/messages.pb.go

@@ -0,0 +1,1049 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.33.0
+// 	protoc        v4.25.3
+// source: protos/customcost/messages.proto
+
+package pb
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	durationpb "google.golang.org/protobuf/types/known/durationpb"
+	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type CustomCostRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// the window of the returned objects
+	Start *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"`
+	End   *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=end,proto3" json:"end,omitempty"`
+	// resolution of steps to return
+	Resolution *durationpb.Duration `protobuf:"bytes,3,opt,name=resolution,proto3" json:"resolution,omitempty"`
+}
+
+func (x *CustomCostRequest) Reset() {
+	*x = CustomCostRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_protos_customcost_messages_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CustomCostRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CustomCostRequest) ProtoMessage() {}
+
+func (x *CustomCostRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_protos_customcost_messages_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CustomCostRequest.ProtoReflect.Descriptor instead.
+func (*CustomCostRequest) Descriptor() ([]byte, []int) {
+	return file_protos_customcost_messages_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *CustomCostRequest) GetStart() *timestamppb.Timestamp {
+	if x != nil {
+		return x.Start
+	}
+	return nil
+}
+
+func (x *CustomCostRequest) GetEnd() *timestamppb.Timestamp {
+	if x != nil {
+		return x.End
+	}
+	return nil
+}
+
+func (x *CustomCostRequest) GetResolution() *durationpb.Duration {
+	if x != nil {
+		return x.Resolution
+	}
+	return nil
+}
+
+type CustomCostResponseSet struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Resps []*CustomCostResponse `protobuf:"bytes,1,rep,name=resps,proto3" json:"resps,omitempty"`
+}
+
+func (x *CustomCostResponseSet) Reset() {
+	*x = CustomCostResponseSet{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_protos_customcost_messages_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CustomCostResponseSet) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CustomCostResponseSet) ProtoMessage() {}
+
+func (x *CustomCostResponseSet) ProtoReflect() protoreflect.Message {
+	mi := &file_protos_customcost_messages_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CustomCostResponseSet.ProtoReflect.Descriptor instead.
+func (*CustomCostResponseSet) Descriptor() ([]byte, []int) {
+	return file_protos_customcost_messages_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *CustomCostResponseSet) GetResps() []*CustomCostResponse {
+	if x != nil {
+		return x.Resps
+	}
+	return nil
+}
+
+type CustomCostResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// provides metadata on the Custom CostResponse
+	// deliberately left unstructured
+	Metadata map[string]string `protobuf:"bytes,1,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	// declared by plugin
+	// eg snowflake == "data management",
+	// datadog == "observability" etc
+	// intended for top level agg
+	CostSource string `protobuf:"bytes,2,opt,name=cost_source,json=costSource,proto3" json:"cost_source,omitempty"`
+	// the name of the custom cost source
+	// e.g., "datadog"
+	Domain string `protobuf:"bytes,3,opt,name=domain,proto3" json:"domain,omitempty"`
+	// the version of the Custom Cost response
+	// is set by the plugin, will vary between
+	// different plugins
+	Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"`
+	// FOCUS billing currency
+	Currency string `protobuf:"bytes,5,opt,name=currency,proto3" json:"currency,omitempty"`
+	// the window of the returned objects
+	Start *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=start,proto3" json:"start,omitempty"`
+	End   *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=end,proto3" json:"end,omitempty"`
+	// array of CustomCosts
+	Costs []*CustomCost `protobuf:"bytes,8,rep,name=costs,proto3" json:"costs,omitempty"`
+	// any errors in processing
+	Errors []string `protobuf:"bytes,9,rep,name=errors,proto3" json:"errors,omitempty"`
+}
+
+func (x *CustomCostResponse) Reset() {
+	*x = CustomCostResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_protos_customcost_messages_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CustomCostResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CustomCostResponse) ProtoMessage() {}
+
+func (x *CustomCostResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_protos_customcost_messages_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CustomCostResponse.ProtoReflect.Descriptor instead.
+func (*CustomCostResponse) Descriptor() ([]byte, []int) {
+	return file_protos_customcost_messages_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *CustomCostResponse) GetMetadata() map[string]string {
+	if x != nil {
+		return x.Metadata
+	}
+	return nil
+}
+
+func (x *CustomCostResponse) GetCostSource() string {
+	if x != nil {
+		return x.CostSource
+	}
+	return ""
+}
+
+func (x *CustomCostResponse) GetDomain() string {
+	if x != nil {
+		return x.Domain
+	}
+	return ""
+}
+
+func (x *CustomCostResponse) GetVersion() string {
+	if x != nil {
+		return x.Version
+	}
+	return ""
+}
+
+func (x *CustomCostResponse) GetCurrency() string {
+	if x != nil {
+		return x.Currency
+	}
+	return ""
+}
+
+func (x *CustomCostResponse) GetStart() *timestamppb.Timestamp {
+	if x != nil {
+		return x.Start
+	}
+	return nil
+}
+
+func (x *CustomCostResponse) GetEnd() *timestamppb.Timestamp {
+	if x != nil {
+		return x.End
+	}
+	return nil
+}
+
+func (x *CustomCostResponse) GetCosts() []*CustomCost {
+	if x != nil {
+		return x.Costs
+	}
+	return nil
+}
+
+func (x *CustomCostResponse) GetErrors() []string {
+	if x != nil {
+		return x.Errors
+	}
+	return nil
+}
+
+type CustomCost struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// provides metadata on the Custom CostResponse
+	// deliberately left unstructured
+	Metadata map[string]string `protobuf:"bytes,1,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	// the region that the resource was incurred
+	// corresponds to 'availability zone' of FOCUS
+	Zone string `protobuf:"bytes,2,opt,name=zone,proto3" json:"zone,omitempty"`
+	// FOCUS billing account name
+	AccountName string `protobuf:"bytes,3,opt,name=account_name,json=accountName,proto3" json:"account_name,omitempty"`
+	// FOCUS charge category
+	ChargeCategory string `protobuf:"bytes,4,opt,name=charge_category,json=chargeCategory,proto3" json:"charge_category,omitempty"`
+	// FOCUS charge description
+	Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty"`
+	// FOCUS Resource Name
+	ResourceName string `protobuf:"bytes,6,opt,name=resource_name,json=resourceName,proto3" json:"resource_name,omitempty"`
+	// FOCUS Resource type
+	// if not set, assumed to be domain
+	ResourceType string `protobuf:"bytes,7,opt,name=resource_type,json=resourceType,proto3" json:"resource_type,omitempty"`
+	// ID of the individual cost. should be globally
+	// unique. Assigned by plugin on read
+	Id string `protobuf:"bytes,8,opt,name=id,proto3" json:"id,omitempty"`
+	// the provider's ID for the cost, if
+	// available
+	// FOCUS resource ID
+	ProviderId string `protobuf:"bytes,9,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"`
+	// FOCUS billed Cost
+	BilledCost float32 `protobuf:"fixed32,10,opt,name=billed_cost,json=billedCost,proto3" json:"billed_cost,omitempty"`
+	// FOCUS List Cost
+	ListCost float32 `protobuf:"fixed32,11,opt,name=list_cost,json=listCost,proto3" json:"list_cost,omitempty"`
+	// FOCUS List Unit Price
+	ListUnitPrice float32 `protobuf:"fixed32,12,opt,name=list_unit_price,json=listUnitPrice,proto3" json:"list_unit_price,omitempty"`
+	// FOCUS usage quantity
+	UsageQuantity float32 `protobuf:"fixed32,13,opt,name=usage_quantity,json=usageQuantity,proto3" json:"usage_quantity,omitempty"`
+	// FOCUS usage Unit
+	UsageUnit string `protobuf:"bytes,14,opt,name=usage_unit,json=usageUnit,proto3" json:"usage_unit,omitempty"`
+	// Returns key/value sets of labels
+	// equivalent to Tags in focus spec
+	Labels map[string]string `protobuf:"bytes,15,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	// Optional struct to implement other focus
+	// spec attributes
+	ExtendedAttributes *CustomCostExtendedAttributes `protobuf:"bytes,16,opt,name=extended_attributes,json=extendedAttributes,proto3,oneof" json:"extended_attributes,omitempty"`
+}
+
+func (x *CustomCost) Reset() {
+	*x = CustomCost{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_protos_customcost_messages_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CustomCost) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CustomCost) ProtoMessage() {}
+
+func (x *CustomCost) ProtoReflect() protoreflect.Message {
+	mi := &file_protos_customcost_messages_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CustomCost.ProtoReflect.Descriptor instead.
+func (*CustomCost) Descriptor() ([]byte, []int) {
+	return file_protos_customcost_messages_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *CustomCost) GetMetadata() map[string]string {
+	if x != nil {
+		return x.Metadata
+	}
+	return nil
+}
+
+func (x *CustomCost) GetZone() string {
+	if x != nil {
+		return x.Zone
+	}
+	return ""
+}
+
+func (x *CustomCost) GetAccountName() string {
+	if x != nil {
+		return x.AccountName
+	}
+	return ""
+}
+
+func (x *CustomCost) GetChargeCategory() string {
+	if x != nil {
+		return x.ChargeCategory
+	}
+	return ""
+}
+
+func (x *CustomCost) GetDescription() string {
+	if x != nil {
+		return x.Description
+	}
+	return ""
+}
+
+func (x *CustomCost) GetResourceName() string {
+	if x != nil {
+		return x.ResourceName
+	}
+	return ""
+}
+
+func (x *CustomCost) GetResourceType() string {
+	if x != nil {
+		return x.ResourceType
+	}
+	return ""
+}
+
+func (x *CustomCost) GetId() string {
+	if x != nil {
+		return x.Id
+	}
+	return ""
+}
+
+func (x *CustomCost) GetProviderId() string {
+	if x != nil {
+		return x.ProviderId
+	}
+	return ""
+}
+
+func (x *CustomCost) GetBilledCost() float32 {
+	if x != nil {
+		return x.BilledCost
+	}
+	return 0
+}
+
+func (x *CustomCost) GetListCost() float32 {
+	if x != nil {
+		return x.ListCost
+	}
+	return 0
+}
+
+func (x *CustomCost) GetListUnitPrice() float32 {
+	if x != nil {
+		return x.ListUnitPrice
+	}
+	return 0
+}
+
+func (x *CustomCost) GetUsageQuantity() float32 {
+	if x != nil {
+		return x.UsageQuantity
+	}
+	return 0
+}
+
+func (x *CustomCost) GetUsageUnit() string {
+	if x != nil {
+		return x.UsageUnit
+	}
+	return ""
+}
+
+func (x *CustomCost) GetLabels() map[string]string {
+	if x != nil {
+		return x.Labels
+	}
+	return nil
+}
+
+func (x *CustomCost) GetExtendedAttributes() *CustomCostExtendedAttributes {
+	if x != nil {
+		return x.ExtendedAttributes
+	}
+	return nil
+}
+
+type CustomCostExtendedAttributes struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// FOCUS billing period start
+	BillingPeriodStart *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=billing_period_start,json=billingPeriodStart,proto3,oneof" json:"billing_period_start,omitempty"`
+	// FOCUS billing period end
+	BillingPeriodEnd *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=billing_period_end,json=billingPeriodEnd,proto3,oneof" json:"billing_period_end,omitempty"`
+	// FOCUS Billing Account ID
+	AccountId *string `protobuf:"bytes,3,opt,name=account_id,json=accountId,proto3,oneof" json:"account_id,omitempty"`
+	// FOCUS Charge Frequency
+	ChargeFrequency *string `protobuf:"bytes,4,opt,name=charge_frequency,json=chargeFrequency,proto3,oneof" json:"charge_frequency,omitempty"`
+	// FOCUS Charge Subcategory
+	Subcategory *string `protobuf:"bytes,5,opt,name=subcategory,proto3,oneof" json:"subcategory,omitempty"`
+	// FOCUS Commitment Discount Category
+	CommitmentDiscountCategory *string `protobuf:"bytes,6,opt,name=commitment_discount_category,json=commitmentDiscountCategory,proto3,oneof" json:"commitment_discount_category,omitempty"`
+	// FOCUS Commitment Discount ID
+	CommitmentDiscountId *string `protobuf:"bytes,7,opt,name=commitment_discount_id,json=commitmentDiscountId,proto3,oneof" json:"commitment_discount_id,omitempty"`
+	// FOCUS Commitment Discount Name
+	CommitmentDiscountName *string `protobuf:"bytes,8,opt,name=commitment_discount_name,json=commitmentDiscountName,proto3,oneof" json:"commitment_discount_name,omitempty"`
+	// FOCUS Commitment Discount Type
+	CommitmentDiscountType *string `protobuf:"bytes,9,opt,name=commitment_discount_type,json=commitmentDiscountType,proto3,oneof" json:"commitment_discount_type,omitempty"`
+	// FOCUS Effective Cost
+	EffectiveCost *float32 `protobuf:"fixed32,10,opt,name=effective_cost,json=effectiveCost,proto3,oneof" json:"effective_cost,omitempty"`
+	// FOCUS Invoice Issuer
+	InvoiceIssuer *string `protobuf:"bytes,11,opt,name=invoice_issuer,json=invoiceIssuer,proto3,oneof" json:"invoice_issuer,omitempty"`
+	// FOCUS Provider
+	// if unset, assumed to be domain
+	Provider *string `protobuf:"bytes,12,opt,name=provider,proto3,oneof" json:"provider,omitempty"`
+	// FOCUS Publisher
+	// if unset, assumed to be domain
+	Publisher *string `protobuf:"bytes,13,opt,name=publisher,proto3,oneof" json:"publisher,omitempty"`
+	// FOCUS Service Category
+	// if unset, assumed to be cost source
+	ServiceCategory *string `protobuf:"bytes,14,opt,name=service_category,json=serviceCategory,proto3,oneof" json:"service_category,omitempty"`
+	// FOCUS Service Name
+	// if unset, assumed to be cost source
+	ServiceName *string `protobuf:"bytes,15,opt,name=service_name,json=serviceName,proto3,oneof" json:"service_name,omitempty"`
+	// FOCUS SKU ID
+	SkuId *string `protobuf:"bytes,16,opt,name=sku_id,json=skuId,proto3,oneof" json:"sku_id,omitempty"`
+	// FOCUS SKU Price ID
+	SkuPriceId *string `protobuf:"bytes,17,opt,name=sku_price_id,json=skuPriceId,proto3,oneof" json:"sku_price_id,omitempty"`
+	// FOCUS Sub Account ID
+	SubAccountId *string `protobuf:"bytes,18,opt,name=sub_account_id,json=subAccountId,proto3,oneof" json:"sub_account_id,omitempty"`
+	// FOCUS Sub Account Name
+	SubAccountName *string `protobuf:"bytes,19,opt,name=sub_account_name,json=subAccountName,proto3,oneof" json:"sub_account_name,omitempty"`
+	// FOCUS Pricing Quantity
+	PricingQuantity *float32 `protobuf:"fixed32,20,opt,name=pricing_quantity,json=pricingQuantity,proto3,oneof" json:"pricing_quantity,omitempty"`
+	// FOCUS Pricing Unit
+	PricingUnit *string `protobuf:"bytes,21,opt,name=pricing_unit,json=pricingUnit,proto3,oneof" json:"pricing_unit,omitempty"`
+	// FOCUS Pricing Category
+	PricingCategory *string `protobuf:"bytes,22,opt,name=pricing_category,json=pricingCategory,proto3,oneof" json:"pricing_category,omitempty"`
+}
+
+func (x *CustomCostExtendedAttributes) Reset() {
+	*x = CustomCostExtendedAttributes{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_protos_customcost_messages_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CustomCostExtendedAttributes) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CustomCostExtendedAttributes) ProtoMessage() {}
+
+func (x *CustomCostExtendedAttributes) ProtoReflect() protoreflect.Message {
+	mi := &file_protos_customcost_messages_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CustomCostExtendedAttributes.ProtoReflect.Descriptor instead.
+func (*CustomCostExtendedAttributes) Descriptor() ([]byte, []int) {
+	return file_protos_customcost_messages_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *CustomCostExtendedAttributes) GetBillingPeriodStart() *timestamppb.Timestamp {
+	if x != nil {
+		return x.BillingPeriodStart
+	}
+	return nil
+}
+
+func (x *CustomCostExtendedAttributes) GetBillingPeriodEnd() *timestamppb.Timestamp {
+	if x != nil {
+		return x.BillingPeriodEnd
+	}
+	return nil
+}
+
+func (x *CustomCostExtendedAttributes) GetAccountId() string {
+	if x != nil && x.AccountId != nil {
+		return *x.AccountId
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetChargeFrequency() string {
+	if x != nil && x.ChargeFrequency != nil {
+		return *x.ChargeFrequency
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetSubcategory() string {
+	if x != nil && x.Subcategory != nil {
+		return *x.Subcategory
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetCommitmentDiscountCategory() string {
+	if x != nil && x.CommitmentDiscountCategory != nil {
+		return *x.CommitmentDiscountCategory
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetCommitmentDiscountId() string {
+	if x != nil && x.CommitmentDiscountId != nil {
+		return *x.CommitmentDiscountId
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetCommitmentDiscountName() string {
+	if x != nil && x.CommitmentDiscountName != nil {
+		return *x.CommitmentDiscountName
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetCommitmentDiscountType() string {
+	if x != nil && x.CommitmentDiscountType != nil {
+		return *x.CommitmentDiscountType
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetEffectiveCost() float32 {
+	if x != nil && x.EffectiveCost != nil {
+		return *x.EffectiveCost
+	}
+	return 0
+}
+
+func (x *CustomCostExtendedAttributes) GetInvoiceIssuer() string {
+	if x != nil && x.InvoiceIssuer != nil {
+		return *x.InvoiceIssuer
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetProvider() string {
+	if x != nil && x.Provider != nil {
+		return *x.Provider
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetPublisher() string {
+	if x != nil && x.Publisher != nil {
+		return *x.Publisher
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetServiceCategory() string {
+	if x != nil && x.ServiceCategory != nil {
+		return *x.ServiceCategory
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetServiceName() string {
+	if x != nil && x.ServiceName != nil {
+		return *x.ServiceName
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetSkuId() string {
+	if x != nil && x.SkuId != nil {
+		return *x.SkuId
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetSkuPriceId() string {
+	if x != nil && x.SkuPriceId != nil {
+		return *x.SkuPriceId
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetSubAccountId() string {
+	if x != nil && x.SubAccountId != nil {
+		return *x.SubAccountId
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetSubAccountName() string {
+	if x != nil && x.SubAccountName != nil {
+		return *x.SubAccountName
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetPricingQuantity() float32 {
+	if x != nil && x.PricingQuantity != nil {
+		return *x.PricingQuantity
+	}
+	return 0
+}
+
+func (x *CustomCostExtendedAttributes) GetPricingUnit() string {
+	if x != nil && x.PricingUnit != nil {
+		return *x.PricingUnit
+	}
+	return ""
+}
+
+func (x *CustomCostExtendedAttributes) GetPricingCategory() string {
+	if x != nil && x.PricingCategory != nil {
+		return *x.PricingCategory
+	}
+	return ""
+}
+
+var File_protos_customcost_messages_proto protoreflect.FileDescriptor
+
+var file_protos_customcost_messages_proto_rawDesc = []byte{
+	0x0a, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x63,
+	0x6f, 0x73, 0x74, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x12, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x63, 0x6f, 0x73, 0x74, 0x2e, 0x6d,
+	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
+	0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xae, 0x01, 0x0a, 0x11, 0x43, 0x75, 0x73,
+	0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30,
+	0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
+	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
+	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74,
+	0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
+	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
+	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x39,
+	0x0a, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x72,
+	0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56, 0x0a, 0x15, 0x43, 0x75, 0x73,
+	0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x53,
+	0x65, 0x74, 0x12, 0x3d, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x27, 0x2e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x63, 0x6f, 0x73, 0x74, 0x2e, 0x6d,
+	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f,
+	0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x72, 0x65, 0x73, 0x70,
+	0x73, 0x22, 0xc2, 0x03, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61,
+	0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x75, 0x73,
+	0x74, 0x6f, 0x6d, 0x63, 0x6f, 0x73, 0x74, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73,
+	0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72,
+	0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x0b, 0x63,
+	0x6f, 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0a, 0x63, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06,
+	0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f,
+	0x6d, 0x61, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
+	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a,
+	0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74,
+	0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
+	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
+	0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03,
+	0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
+	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
+	0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x35, 0x0a, 0x05, 0x63, 0x6f,
+	0x73, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x75, 0x73, 0x74,
+	0x6f, 0x6d, 0x63, 0x6f, 0x73, 0x74, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e,
+	0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74, 0x52, 0x05, 0x63, 0x6f, 0x73, 0x74,
+	0x73, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74,
+	0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
+	0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,
+	0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xbe, 0x06, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, 0x6f,
+	0x6d, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x49, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
+	0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
+	0x63, 0x6f, 0x73, 0x74, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x43, 0x75,
+	0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
+	0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x12, 0x12, 0x0a, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+	0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f,
+	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f,
+	0x75, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x72, 0x67,
+	0x65, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0e, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
+	0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
+	0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75,
+	0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75,
+	0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
+	0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02,
+	0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b,
+	0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a,
+	0x0b, 0x62, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x0a, 0x20, 0x01,
+	0x28, 0x02, 0x52, 0x0a, 0x62, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1b,
+	0x0a, 0x09, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28,
+	0x02, 0x52, 0x08, 0x6c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6c,
+	0x69, 0x73, 0x74, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0c,
+	0x20, 0x01, 0x28, 0x02, 0x52, 0x0d, 0x6c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x69, 0x74, 0x50, 0x72,
+	0x69, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x71, 0x75, 0x61,
+	0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0d, 0x75, 0x73, 0x61,
+	0x67, 0x65, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73,
+	0x61, 0x67, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
+	0x75, 0x73, 0x61, 0x67, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x43, 0x0a, 0x06, 0x6c, 0x61, 0x62,
+	0x65, 0x6c, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x63, 0x75, 0x73, 0x74,
+	0x6f, 0x6d, 0x63, 0x6f, 0x73, 0x74, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e,
+	0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c,
+	0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x67,
+	0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69,
+	0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x75,
+	0x73, 0x74, 0x6f, 0x6d, 0x63, 0x6f, 0x73, 0x74, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x73, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65,
+	0x6e, 0x64, 0x65, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x48, 0x00,
+	0x52, 0x12, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62,
+	0x75, 0x74, 0x65, 0x73, 0x88, 0x01, 0x01, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64,
+	0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
+	0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x3a, 0x02, 0x38, 0x01, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e,
+	0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42,
+	0x16, 0x0a, 0x14, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x74,
+	0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, 0x94, 0x0c, 0x0a, 0x1c, 0x43, 0x75, 0x73, 0x74,
+	0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x74,
+	0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x51, 0x0a, 0x14, 0x62, 0x69, 0x6c, 0x6c,
+	0x69, 0x6e, 0x67, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
+	0x6d, 0x70, 0x48, 0x00, 0x52, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x50, 0x65, 0x72,
+	0x69, 0x6f, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, 0x12, 0x4d, 0x0a, 0x12, 0x62,
+	0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x65, 0x6e,
+	0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
+	0x61, 0x6d, 0x70, 0x48, 0x01, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x50, 0x65,
+	0x72, 0x69, 0x6f, 0x64, 0x45, 0x6e, 0x64, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x61, 0x63,
+	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02,
+	0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x2e,
+	0x0a, 0x10, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e,
+	0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x72,
+	0x67, 0x65, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x88, 0x01, 0x01, 0x12, 0x25,
+	0x0a, 0x0b, 0x73, 0x75, 0x62, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x05, 0x20,
+	0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f,
+	0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x45, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d,
+	0x65, 0x6e, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x61, 0x74,
+	0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x05, 0x52, 0x1a, 0x63,
+	0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e,
+	0x74, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x39, 0x0a, 0x16,
+	0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f,
+	0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x06, 0x52, 0x14,
+	0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75,
+	0x6e, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x3d, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x69,
+	0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x07, 0x52, 0x16, 0x63, 0x6f, 0x6d,
+	0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e,
+	0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x3d, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
+	0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79,
+	0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x08, 0x52, 0x16, 0x63, 0x6f, 0x6d, 0x6d,
+	0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x79,
+	0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x2a, 0x0a, 0x0e, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69,
+	0x76, 0x65, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x02, 0x48, 0x09, 0x52,
+	0x0d, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x73, 0x74, 0x88, 0x01,
+	0x01, 0x12, 0x2a, 0x0a, 0x0e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x73, 0x73,
+	0x75, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0a, 0x52, 0x0d, 0x69, 0x6e, 0x76,
+	0x6f, 0x69, 0x63, 0x65, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a,
+	0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x48,
+	0x0b, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x21,
+	0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28,
+	0x09, 0x48, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x88, 0x01,
+	0x01, 0x12, 0x2e, 0x0a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x61, 0x74,
+	0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0d, 0x52, 0x0f, 0x73,
+	0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x88, 0x01,
+	0x01, 0x12, 0x26, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0e, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69,
+	0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, 0x06, 0x73, 0x6b, 0x75,
+	0x5f, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0f, 0x52, 0x05, 0x73, 0x6b, 0x75,
+	0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0c, 0x73, 0x6b, 0x75, 0x5f, 0x70, 0x72, 0x69,
+	0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x48, 0x10, 0x52, 0x0a, 0x73,
+	0x6b, 0x75, 0x50, 0x72, 0x69, 0x63, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x0e,
+	0x73, 0x75, 0x62, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x12,
+	0x20, 0x01, 0x28, 0x09, 0x48, 0x11, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x41, 0x63, 0x63, 0x6f, 0x75,
+	0x6e, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x5f, 0x61,
+	0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28,
+	0x09, 0x48, 0x12, 0x52, 0x0e, 0x73, 0x75, 0x62, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e,
+	0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x2e, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x63, 0x69, 0x6e,
+	0x67, 0x5f, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x02,
+	0x48, 0x13, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x63, 0x69, 0x6e, 0x67, 0x51, 0x75, 0x61, 0x6e, 0x74,
+	0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x12, 0x26, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x63, 0x69, 0x6e,
+	0x67, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x48, 0x14, 0x52, 0x0b,
+	0x70, 0x72, 0x69, 0x63, 0x69, 0x6e, 0x67, 0x55, 0x6e, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12, 0x2e,
+	0x0a, 0x10, 0x70, 0x72, 0x69, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f,
+	0x72, 0x79, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x48, 0x15, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x63,
+	0x69, 0x6e, 0x67, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, 0x42, 0x17,
+	0x0a, 0x15, 0x5f, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f,
+	0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x62, 0x69, 0x6c, 0x6c,
+	0x69, 0x6e, 0x67, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x42, 0x0d,
+	0x0a, 0x0b, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x13, 0x0a,
+	0x11, 0x5f, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e,
+	0x63, 0x79, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, 0x75, 0x62, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f,
+	0x72, 0x79, 0x42, 0x1f, 0x0a, 0x1d, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e,
+	0x74, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67,
+	0x6f, 0x72, 0x79, 0x42, 0x19, 0x0a, 0x17, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65,
+	0x6e, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x1b,
+	0x0a, 0x19, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x69,
+	0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x1b, 0x0a, 0x19, 0x5f,
+	0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f,
+	0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x65, 0x66, 0x66,
+	0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x42, 0x11, 0x0a, 0x0f, 0x5f,
+	0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x42, 0x0b,
+	0x0a, 0x09, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x42, 0x0c, 0x0a, 0x0a, 0x5f,
+	0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x73, 0x65,
+	0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x42, 0x0f,
+	0x0a, 0x0d, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42,
+	0x09, 0x0a, 0x07, 0x5f, 0x73, 0x6b, 0x75, 0x5f, 0x69, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x73,
+	0x6b, 0x75, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x42, 0x11, 0x0a, 0x0f, 0x5f,
+	0x73, 0x75, 0x62, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x13,
+	0x0a, 0x11, 0x5f, 0x73, 0x75, 0x62, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e,
+	0x61, 0x6d, 0x65, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x69, 0x6e, 0x67, 0x5f,
+	0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x70, 0x72, 0x69,
+	0x63, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x70, 0x72,
+	0x69, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x32, 0x79,
+	0x0a, 0x11, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74, 0x73, 0x53, 0x6f, 0x75,
+	0x72, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d,
+	0x43, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x26, 0x2e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x63, 0x6f,
+	0x73, 0x74, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x43, 0x75, 0x73, 0x74,
+	0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e,
+	0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x63, 0x6f, 0x73, 0x74, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61,
+	0x67, 0x65, 0x73, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x73, 0x74, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x53, 0x65, 0x74, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74,
+	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x73, 0x74,
+	0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70,
+	0x6b, 0x67, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x33,
+}
+
+var (
+	file_protos_customcost_messages_proto_rawDescOnce sync.Once
+	file_protos_customcost_messages_proto_rawDescData = file_protos_customcost_messages_proto_rawDesc
+)
+
+func file_protos_customcost_messages_proto_rawDescGZIP() []byte {
+	file_protos_customcost_messages_proto_rawDescOnce.Do(func() {
+		file_protos_customcost_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_customcost_messages_proto_rawDescData)
+	})
+	return file_protos_customcost_messages_proto_rawDescData
+}
+
+var file_protos_customcost_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
+var file_protos_customcost_messages_proto_goTypes = []interface{}{
+	(*CustomCostRequest)(nil),            // 0: customcost.messages.CustomCostRequest
+	(*CustomCostResponseSet)(nil),        // 1: customcost.messages.CustomCostResponseSet
+	(*CustomCostResponse)(nil),           // 2: customcost.messages.CustomCostResponse
+	(*CustomCost)(nil),                   // 3: customcost.messages.CustomCost
+	(*CustomCostExtendedAttributes)(nil), // 4: customcost.messages.CustomCostExtendedAttributes
+	nil,                                  // 5: customcost.messages.CustomCostResponse.MetadataEntry
+	nil,                                  // 6: customcost.messages.CustomCost.MetadataEntry
+	nil,                                  // 7: customcost.messages.CustomCost.LabelsEntry
+	(*timestamppb.Timestamp)(nil),        // 8: google.protobuf.Timestamp
+	(*durationpb.Duration)(nil),          // 9: google.protobuf.Duration
+}
+var file_protos_customcost_messages_proto_depIdxs = []int32{
+	8,  // 0: customcost.messages.CustomCostRequest.start:type_name -> google.protobuf.Timestamp
+	8,  // 1: customcost.messages.CustomCostRequest.end:type_name -> google.protobuf.Timestamp
+	9,  // 2: customcost.messages.CustomCostRequest.resolution:type_name -> google.protobuf.Duration
+	2,  // 3: customcost.messages.CustomCostResponseSet.resps:type_name -> customcost.messages.CustomCostResponse
+	5,  // 4: customcost.messages.CustomCostResponse.metadata:type_name -> customcost.messages.CustomCostResponse.MetadataEntry
+	8,  // 5: customcost.messages.CustomCostResponse.start:type_name -> google.protobuf.Timestamp
+	8,  // 6: customcost.messages.CustomCostResponse.end:type_name -> google.protobuf.Timestamp
+	3,  // 7: customcost.messages.CustomCostResponse.costs:type_name -> customcost.messages.CustomCost
+	6,  // 8: customcost.messages.CustomCost.metadata:type_name -> customcost.messages.CustomCost.MetadataEntry
+	7,  // 9: customcost.messages.CustomCost.labels:type_name -> customcost.messages.CustomCost.LabelsEntry
+	4,  // 10: customcost.messages.CustomCost.extended_attributes:type_name -> customcost.messages.CustomCostExtendedAttributes
+	8,  // 11: customcost.messages.CustomCostExtendedAttributes.billing_period_start:type_name -> google.protobuf.Timestamp
+	8,  // 12: customcost.messages.CustomCostExtendedAttributes.billing_period_end:type_name -> google.protobuf.Timestamp
+	0,  // 13: customcost.messages.CustomCostsSource.GetCustomCosts:input_type -> customcost.messages.CustomCostRequest
+	1,  // 14: customcost.messages.CustomCostsSource.GetCustomCosts:output_type -> customcost.messages.CustomCostResponseSet
+	14, // [14:15] is the sub-list for method output_type
+	13, // [13:14] is the sub-list for method input_type
+	13, // [13:13] is the sub-list for extension type_name
+	13, // [13:13] is the sub-list for extension extendee
+	0,  // [0:13] is the sub-list for field type_name
+}
+
+func init() { file_protos_customcost_messages_proto_init() }
+func file_protos_customcost_messages_proto_init() {
+	if File_protos_customcost_messages_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_protos_customcost_messages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CustomCostRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_protos_customcost_messages_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CustomCostResponseSet); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_protos_customcost_messages_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CustomCostResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_protos_customcost_messages_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CustomCost); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_protos_customcost_messages_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CustomCostExtendedAttributes); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	file_protos_customcost_messages_proto_msgTypes[3].OneofWrappers = []interface{}{}
+	file_protos_customcost_messages_proto_msgTypes[4].OneofWrappers = []interface{}{}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_protos_customcost_messages_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   8,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_protos_customcost_messages_proto_goTypes,
+		DependencyIndexes: file_protos_customcost_messages_proto_depIdxs,
+		MessageInfos:      file_protos_customcost_messages_proto_msgTypes,
+	}.Build()
+	File_protos_customcost_messages_proto = out.File
+	file_protos_customcost_messages_proto_rawDesc = nil
+	file_protos_customcost_messages_proto_goTypes = nil
+	file_protos_customcost_messages_proto_depIdxs = nil
+}

+ 109 - 0
core/pkg/model/pb/messages_grpc.pb.go

@@ -0,0 +1,109 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.3.0
+// - protoc             v4.25.3
+// source: protos/customcost/messages.proto
+
+package pb
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+const (
+	CustomCostsSource_GetCustomCosts_FullMethodName = "/customcost.messages.CustomCostsSource/GetCustomCosts"
+)
+
+// CustomCostsSourceClient is the client API for CustomCostsSource service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type CustomCostsSourceClient interface {
+	GetCustomCosts(ctx context.Context, in *CustomCostRequest, opts ...grpc.CallOption) (*CustomCostResponseSet, error)
+}
+
+type customCostsSourceClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewCustomCostsSourceClient(cc grpc.ClientConnInterface) CustomCostsSourceClient {
+	return &customCostsSourceClient{cc}
+}
+
+func (c *customCostsSourceClient) GetCustomCosts(ctx context.Context, in *CustomCostRequest, opts ...grpc.CallOption) (*CustomCostResponseSet, error) {
+	out := new(CustomCostResponseSet)
+	err := c.cc.Invoke(ctx, CustomCostsSource_GetCustomCosts_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// CustomCostsSourceServer is the server API for CustomCostsSource service.
+// All implementations must embed UnimplementedCustomCostsSourceServer
+// for forward compatibility
+type CustomCostsSourceServer interface {
+	GetCustomCosts(context.Context, *CustomCostRequest) (*CustomCostResponseSet, error)
+	mustEmbedUnimplementedCustomCostsSourceServer()
+}
+
+// UnimplementedCustomCostsSourceServer must be embedded to have forward compatible implementations.
+type UnimplementedCustomCostsSourceServer struct {
+}
+
+func (UnimplementedCustomCostsSourceServer) GetCustomCosts(context.Context, *CustomCostRequest) (*CustomCostResponseSet, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method GetCustomCosts not implemented")
+}
+func (UnimplementedCustomCostsSourceServer) mustEmbedUnimplementedCustomCostsSourceServer() {}
+
+// UnsafeCustomCostsSourceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to CustomCostsSourceServer will
+// result in compilation errors.
+type UnsafeCustomCostsSourceServer interface {
+	mustEmbedUnimplementedCustomCostsSourceServer()
+}
+
+func RegisterCustomCostsSourceServer(s grpc.ServiceRegistrar, srv CustomCostsSourceServer) {
+	s.RegisterService(&CustomCostsSource_ServiceDesc, srv)
+}
+
+func _CustomCostsSource_GetCustomCosts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CustomCostRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(CustomCostsSourceServer).GetCustomCosts(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: CustomCostsSource_GetCustomCosts_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(CustomCostsSourceServer).GetCustomCosts(ctx, req.(*CustomCostRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// CustomCostsSource_ServiceDesc is the grpc.ServiceDesc for CustomCostsSource service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var CustomCostsSource_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "customcost.messages.CustomCostsSource",
+	HandlerType: (*CustomCostsSourceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "GetCustomCosts",
+			Handler:    _CustomCostsSource_GetCustomCosts_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "protos/customcost/messages.proto",
+}

+ 25 - 15
pkg/kubecost/allocation.go → core/pkg/opencost/allocation.go

@@ -1,4 +1,4 @@
-package kubecost
+package opencost
 
 import (
 	"fmt"
@@ -7,12 +7,12 @@ import (
 	"strings"
 	"time"
 
-	filter21 "github.com/opencost/opencost/pkg/filter21"
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/filter21/matcher"
-	"github.com/opencost/opencost/pkg/log"
-	"github.com/opencost/opencost/pkg/util"
-	"github.com/opencost/opencost/pkg/util/timeutil"
+	"github.com/opencost/opencost/core/pkg/filter"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/filter/matcher"
+	"github.com/opencost/opencost/core/pkg/log"
+	"github.com/opencost/opencost/core/pkg/util"
+	"github.com/opencost/opencost/core/pkg/util/timeutil"
 	"golang.org/x/exp/slices"
 )
 
@@ -93,11 +93,11 @@ type Allocation struct {
 	// and appended to an Allocation, and so by default is is nil.
 	ProportionalAssetResourceCosts ProportionalAssetResourceCosts `json:"proportionalAssetResourceCosts"` //@bingen:field[ignore]
 	SharedCostBreakdown            SharedCostBreakdowns           `json:"sharedCostBreakdown"`            //@bingen:field[ignore]
-	LoadBalancers                  LbAllocations                  `json:"LoadBalancers"`                  // @bingen:field[version=18]
+	LoadBalancers                  LbAllocations                  `json:"LoadBalancers"`                  //@bingen:field[version=18]
 	// UnmountedPVCost is used to track how much of the cost in PVs is for an
 	// unmounted PV. It is not additive of PVCost() and need not be sent in API
 	// responses.
-	UnmountedPVCost float64 `json:"-"`
+	UnmountedPVCost float64 `json:"-"` //@bingen:field[ignore]
 }
 
 type LbAllocations map[string]*LbAllocation
@@ -115,16 +115,19 @@ func (orig LbAllocations) Clone() LbAllocations {
 			Cost:    lbAlloc.Cost,
 			Private: lbAlloc.Private,
 			Ip:      lbAlloc.Ip,
+			Hours:   lbAlloc.Hours,
 		}
 	}
 	return newAllocs
 }
 
 type LbAllocation struct {
-	Service string  `json:"service"`
-	Cost    float64 `json:"cost"`
-	Private bool    `json:"private"`
-	Ip      string  `json:"ip"` //@bingen:field[version=19]
+	Service    string  `json:"service"`
+	Cost       float64 `json:"cost"`
+	Private    bool    `json:"private"`
+	Ip         string  `json:"ip"`         //@bingen:field[version=19]
+	Hours      float64 `json:"hours"`      //@bingen:field[version=21]
+	Adjustment float64 `json:"adjustment"` //@bingen:field[ignore]
 }
 
 func (lba *LbAllocation) SanitizeNaN() {
@@ -135,6 +138,10 @@ func (lba *LbAllocation) SanitizeNaN() {
 		log.DedupedWarningf(5, "LBAllocation: Unexpected NaN found for Cost service:%s", lba.Service)
 		lba.Cost = 0
 	}
+	if math.IsNaN(lba.Hours) {
+		log.DedupedWarningf(5, "LBAllocation: Unexpected NaN found for Hours service:%s", lba.Service)
+		lba.Hours = 0
+	}
 }
 
 // RawAllocationOnlyData is information that only belong in "raw" Allocations,
@@ -306,6 +313,7 @@ type PVAllocation struct {
 	ByteHours  float64 `json:"byteHours"`
 	Cost       float64 `json:"cost"`
 	ProviderID string  `json:"providerID"` // @bingen:field[version=20]
+	Adjustment float64 `json:"adjustment"` //@bingen:field[ignore]
 }
 
 // Equal returns true if the two PVAllocation instances contain approximately the same
@@ -1249,10 +1257,12 @@ func (thisLbAllocs LbAllocations) Add(thatLbAllocs LbAllocations) LbAllocations
 				thisLbAlloc = &LbAllocation{
 					Service: thatlbAlloc.Service,
 					Cost:    thatlbAlloc.Cost,
+					Hours:   thatlbAlloc.Hours,
 				}
 				mergedLbAllocs[lbKey] = thisLbAlloc
 			} else {
 				thisLbAlloc.Cost += thatlbAlloc.Cost
+				thisLbAlloc.Hours += thatlbAlloc.Hours
 			}
 
 		}
@@ -1303,14 +1313,14 @@ func NewAllocationSet(start, end time.Time, allocs ...*Allocation) *AllocationSe
 // simple flag for sharing idle resources.
 type AllocationAggregationOptions struct {
 	AllocationTotalsStore                 AllocationTotalsStore
-	Filter                                filter21.Filter
+	Filter                                filter.Filter
 	IdleByNode                            bool
 	IncludeProportionalAssetResourceCosts bool
 	LabelConfig                           *LabelConfig
 	MergeUnallocated                      bool
 	Reconcile                             bool
 	ReconcileNetwork                      bool
-	Share                                 filter21.Filter
+	Share                                 filter.Filter
 	SharedNamespaces                      []string
 	SharedLabels                          map[string][]string
 	ShareIdle                             string

+ 2 - 2
pkg/kubecost/allocation_json.go → core/pkg/opencost/allocation_json.go

@@ -1,11 +1,11 @@
-package kubecost
+package opencost
 
 import (
 	"fmt"
 	"math"
 	"time"
 
-	"github.com/opencost/opencost/pkg/util/json"
+	"github.com/opencost/opencost/core/pkg/util/json"
 )
 
 // AllocationJSON  exists because there are expected JSON response fields

+ 2 - 2
pkg/kubecost/allocation_json_test.go → core/pkg/opencost/allocation_json_test.go

@@ -1,4 +1,4 @@
-package kubecost
+package opencost
 
 import (
 	"encoding/json"
@@ -6,7 +6,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/opencost/opencost/pkg/util/mathutil"
+	"github.com/opencost/opencost/core/pkg/util/mathutil"
 )
 
 func TestAllocation_MarshalJSON(t *testing.T) {

+ 13 - 14
pkg/kubecost/allocation_test.go → core/pkg/opencost/allocation_test.go

@@ -1,4 +1,4 @@
-package kubecost
+package opencost
 
 import (
 	"fmt"
@@ -9,23 +9,22 @@ import (
 	"time"
 
 	"github.com/davecgh/go-spew/spew"
-	filter21 "github.com/opencost/opencost/pkg/filter21"
-	"github.com/opencost/opencost/pkg/filter21/allocation"
-	afilter "github.com/opencost/opencost/pkg/filter21/allocation"
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/filter21/ops"
-	"github.com/opencost/opencost/pkg/log"
-	"github.com/opencost/opencost/pkg/util"
-	"github.com/opencost/opencost/pkg/util/json"
-	"github.com/opencost/opencost/pkg/util/timeutil"
+	"github.com/opencost/opencost/core/pkg/filter"
+	"github.com/opencost/opencost/core/pkg/filter/allocation"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/filter/ops"
+	"github.com/opencost/opencost/core/pkg/log"
+	"github.com/opencost/opencost/core/pkg/util"
+	"github.com/opencost/opencost/core/pkg/util/json"
+	"github.com/opencost/opencost/core/pkg/util/timeutil"
 )
 
-var filterParser = afilter.NewAllocationFilterParser()
+var filterParser = allocation.NewAllocationFilterParser()
 var matcherCompiler = NewAllocationMatchCompiler(nil)
 
 // useful for creating filters on the fly when testing. panics
 // on parse errors!
-func mustParseFilter(s string) filter21.Filter {
+func mustParseFilter(s string) filter.Filter {
 	filter, err := filterParser.Parse(s)
 	if err != nil {
 		panic(err)
@@ -3411,7 +3410,7 @@ func Test_AggregateByService_UnmountedLBs(t *testing.T) {
 	set.Insert(idle)
 
 	set.AggregateBy([]string{AllocationServiceProp}, &AllocationAggregationOptions{
-		Filter: ops.Contains(afilter.FieldServices, "nginx-plus-nginx-ingress"),
+		Filter: ops.Contains(allocation.FieldServices, "nginx-plus-nginx-ingress"),
 	})
 
 	for _, alloc := range set.Allocations {
@@ -3653,7 +3652,7 @@ func TestIsFilterEmptyTrue(t *testing.T) {
 
 func TestIsFilterEmptyFalse(t *testing.T) {
 	compiler := NewAllocationMatchCompiler(nil)
-	matcher, err := compiler.Compile(ops.Eq(afilter.FieldClusterID, "test"))
+	matcher, err := compiler.Compile(ops.Eq(allocation.FieldClusterID, "test"))
 	if err != nil {
 		t.Fatalf("compiling nil filter: %s", err)
 	}

+ 5 - 5
pkg/kubecost/allocationfilter_test.go → core/pkg/opencost/allocationfilter_test.go

@@ -1,12 +1,12 @@
-package kubecost
+package opencost
 
 import (
 	"testing"
 
-	filter21 "github.com/opencost/opencost/pkg/filter21"
-	afilter "github.com/opencost/opencost/pkg/filter21/allocation"
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/filter21/ops"
+	filter21 "github.com/opencost/opencost/core/pkg/filter"
+	afilter "github.com/opencost/opencost/core/pkg/filter/allocation"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/filter/ops"
 )
 
 func Test_AllocationFilterCondition_Matches(t *testing.T) {

+ 6 - 6
pkg/kubecost/allocationmatcher.go → core/pkg/opencost/allocationmatcher.go

@@ -1,13 +1,13 @@
-package kubecost
+package opencost
 
 import (
 	"fmt"
 
-	afilter "github.com/opencost/opencost/pkg/filter21/allocation"
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/filter21/matcher"
-	"github.com/opencost/opencost/pkg/filter21/ops"
-	"github.com/opencost/opencost/pkg/filter21/transform"
+	afilter "github.com/opencost/opencost/core/pkg/filter/allocation"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/filter/matcher"
+	"github.com/opencost/opencost/core/pkg/filter/ops"
+	"github.com/opencost/opencost/core/pkg/filter/transform"
 )
 
 // AllocationMatcher is a matcher implementation for Allocation instances,

+ 4 - 4
pkg/kubecost/allocationmatcher_test.go → core/pkg/opencost/allocationmatcher_test.go

@@ -1,12 +1,12 @@
-package kubecost
+package opencost
 
 import (
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
-	afilter "github.com/opencost/opencost/pkg/filter21/allocation"
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/filter21/ops"
+	afilter "github.com/opencost/opencost/core/pkg/filter/allocation"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/filter/ops"
 )
 
 func TestAliasPass(t *testing.T) {

+ 111 - 29
pkg/kubecost/allocationprops.go → core/pkg/opencost/allocationprops.go

@@ -1,39 +1,121 @@
-package kubecost
+package opencost
 
 import (
 	"fmt"
 	"sort"
 	"strings"
 
-	"github.com/opencost/opencost/pkg/log"
-	"github.com/opencost/opencost/pkg/prom"
+	"github.com/opencost/opencost/core/pkg/log"
+	"github.com/opencost/opencost/core/pkg/util/promutil"
 )
 
+// AllocationProperty represents a specific property on an allocation, which
+// provides utility for extracting custom property metadata.
+type AllocationProperty string
+
+// IsLabel returns true if the allocation property has a label prefix
+func (apt *AllocationProperty) IsLabel() bool {
+	return strings.HasPrefix(string(*apt), "label:")
+}
+
+// GetLabel returns the label string associated with the label property if it exists.
+// Otherwise, empty string is returned.
+func (apt *AllocationProperty) GetLabel() string {
+	if apt.IsLabel() {
+		return strings.TrimSpace(strings.TrimPrefix(string(*apt), "label:"))
+	}
+	return ""
+}
+
+// IsAnnotation returns true if the allocation property has an annotation prefix
+func (apt *AllocationProperty) IsAnnotation() bool {
+	return strings.HasPrefix(string(*apt), "annotation:")
+}
+
+// GetAnnotation returns the annotation string associated with the property if it exists.
+// Otherwise, empty string is returned.
+func (apt *AllocationProperty) GetAnnotation() string {
+	if apt.IsAnnotation() {
+		return strings.TrimSpace(strings.TrimPrefix(string(*apt), "annotation:"))
+	}
+	return ""
+}
+
+// IsAliasedLabel returns true if the allocation property corresponds to an aliased label
+func (apt *AllocationProperty) IsAliasedLabel() bool {
+	if apt == nil {
+		return false
+	}
+
+	return *apt == AllocationDepartmentProp ||
+		*apt == AllocationEnvironmentProp ||
+		*apt == AllocationOwnerProp ||
+		*apt == AllocationProductProp ||
+		*apt == AllocationTeamProp
+}
+
+// GetAliasedLabelDefault returns the corresponding default aliased label name
+func (apt *AllocationProperty) GetAliasedLabelDefault() string {
+	switch *apt {
+	case AllocationDepartmentProp:
+		return "department"
+	case AllocationEnvironmentProp:
+		return "env"
+	case AllocationOwnerProp:
+		return "owner"
+	case AllocationProductProp:
+		return "app"
+	case AllocationTeamProp:
+		return "team"
+	default:
+		return ""
+	}
+}
+
 const (
-	AllocationNilProp            string = ""
-	AllocationClusterProp        string = "cluster"
-	AllocationNodeProp           string = "node"
-	AllocationContainerProp      string = "container"
-	AllocationControllerProp     string = "controller"
-	AllocationControllerKindProp string = "controllerKind"
-	AllocationNamespaceProp      string = "namespace"
-	AllocationPodProp            string = "pod"
-	AllocationProviderIDProp     string = "providerID"
-	AllocationServiceProp        string = "service"
-	AllocationLabelProp          string = "label"
-	AllocationAnnotationProp     string = "annotation"
-	AllocationDeploymentProp     string = "deployment"
-	AllocationStatefulSetProp    string = "statefulset"
-	AllocationDaemonSetProp      string = "daemonset"
-	AllocationJobProp            string = "job"
-	AllocationDepartmentProp     string = "department"
-	AllocationEnvironmentProp    string = "environment"
-	AllocationOwnerProp          string = "owner"
-	AllocationProductProp        string = "product"
-	AllocationTeamProp           string = "team"
+	AllocationNilProp            AllocationProperty = ""
+	AllocationClusterProp                           = "cluster"
+	AllocationNodeProp                              = "node"
+	AllocationContainerProp                         = "container"
+	AllocationControllerProp                        = "controller"
+	AllocationControllerKindProp                    = "controllerKind"
+	AllocationNamespaceProp                         = "namespace"
+	AllocationPodProp                               = "pod"
+	AllocationProviderIDProp                        = "providerID"
+	AllocationServiceProp                           = "service"
+	AllocationLabelProp                             = "label"
+	AllocationAnnotationProp                        = "annotation"
+	AllocationDeploymentProp                        = "deployment"
+	AllocationStatefulSetProp                       = "statefulset"
+	AllocationDaemonSetProp                         = "daemonset"
+	AllocationJobProp                               = "job"
+	AllocationDepartmentProp                        = "department"
+	AllocationEnvironmentProp                       = "environment"
+	AllocationOwnerProp                             = "owner"
+	AllocationProductProp                           = "product"
+	AllocationTeamProp                              = "team"
 )
 
-func ParseProperty(text string) (string, error) {
+func ParseProperties(props []string) ([]AllocationProperty, error) {
+	properties := []AllocationProperty{}
+	added := make(map[AllocationProperty]struct{})
+
+	for _, prop := range props {
+		property, err := ParseProperty(prop)
+		if err != nil {
+			return nil, fmt.Errorf("Failed to parse property: %w", err)
+		}
+
+		if _, ok := added[property]; !ok {
+			added[property] = struct{}{}
+			properties = append(properties, property)
+		}
+	}
+
+	return properties, nil
+}
+
+func ParseProperty(text string) (AllocationProperty, error) {
 	switch strings.TrimSpace(strings.ToLower(text)) {
 	case "cluster":
 		return AllocationClusterProp, nil
@@ -78,13 +160,13 @@ func ParseProperty(text string) (string, error) {
 	}
 
 	if strings.HasPrefix(text, "label:") {
-		label := prom.SanitizeLabelName(strings.TrimSpace(strings.TrimPrefix(text, "label:")))
-		return fmt.Sprintf("label:%s", label), nil
+		label := promutil.SanitizeLabelName(strings.TrimSpace(strings.TrimPrefix(text, "label:")))
+		return AllocationProperty(fmt.Sprintf("label:%s", label)), nil
 	}
 
 	if strings.HasPrefix(text, "annotation:") {
-		annotation := prom.SanitizeLabelName(strings.TrimSpace(strings.TrimPrefix(text, "annotation:")))
-		return fmt.Sprintf("annotation:%s", annotation), nil
+		annotation := promutil.SanitizeLabelName(strings.TrimSpace(strings.TrimPrefix(text, "annotation:")))
+		return AllocationProperty(fmt.Sprintf("annotation:%s", annotation)), nil
 	}
 
 	return AllocationNilProp, fmt.Errorf("invalid allocation property: %s", text)

+ 1 - 1
pkg/kubecost/allocationprops_test.go → core/pkg/opencost/allocationprops_test.go

@@ -1,4 +1,4 @@
-package kubecost
+package opencost
 
 import (
 	"reflect"

+ 9 - 9
pkg/kubecost/asset.go → core/pkg/opencost/asset.go

@@ -1,4 +1,4 @@
-package kubecost
+package opencost
 
 import (
 	"encoding"
@@ -7,13 +7,13 @@ import (
 	"strings"
 	"time"
 
-	filter21 "github.com/opencost/opencost/pkg/filter21"
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/filter21/matcher"
-	"github.com/opencost/opencost/pkg/log"
-	"github.com/opencost/opencost/pkg/prom"
-	"github.com/opencost/opencost/pkg/util/json"
-	"github.com/opencost/opencost/pkg/util/timeutil"
+	filter21 "github.com/opencost/opencost/core/pkg/filter"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/filter/matcher"
+	"github.com/opencost/opencost/core/pkg/log"
+	"github.com/opencost/opencost/core/pkg/util/json"
+	"github.com/opencost/opencost/core/pkg/util/promutil"
+	"github.com/opencost/opencost/core/pkg/util/timeutil"
 )
 
 // UndefinedKey is used in composing Asset group keys if the group does not have that property defined.
@@ -4139,7 +4139,7 @@ func GetNodePoolName(provider string, labels map[string]string) string {
 }
 
 func getPoolNameHelper(label string, labels map[string]string) string {
-	sanitizedLabel := prom.SanitizeLabelName(label)
+	sanitizedLabel := promutil.SanitizeLabelName(label)
 	if poolName, found := labels[sanitizedLabel]; found {
 		return poolName
 	} else {

+ 2 - 2
pkg/kubecost/asset_json.go → core/pkg/opencost/asset_json.go

@@ -1,4 +1,4 @@
-package kubecost
+package opencost
 
 import (
 	"bytes"
@@ -6,7 +6,7 @@ import (
 	"reflect"
 	"time"
 
-	"github.com/opencost/opencost/pkg/util/json"
+	"github.com/opencost/opencost/core/pkg/util/json"
 )
 
 // Encoding and decoding logic for Asset types

+ 2 - 2
pkg/kubecost/asset_json_test.go → core/pkg/opencost/asset_json_test.go

@@ -1,7 +1,7 @@
-package kubecost
+package opencost
 
 import (
-	"github.com/opencost/opencost/pkg/util/json"
+	"github.com/opencost/opencost/core/pkg/util/json"
 
 	"testing"
 	"time"

+ 2 - 2
pkg/kubecost/asset_test.go → core/pkg/opencost/asset_test.go

@@ -1,4 +1,4 @@
-package kubecost
+package opencost
 
 import (
 	"encoding/json"
@@ -8,7 +8,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/opencost/opencost/pkg/util"
+	"github.com/opencost/opencost/core/pkg/util"
 )
 
 var start1 = time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)

+ 5 - 5
pkg/kubecost/assetmatcher.go → core/pkg/opencost/assetmatcher.go

@@ -1,13 +1,13 @@
-package kubecost
+package opencost
 
 import (
 	"fmt"
 	"strings"
 
-	afilter "github.com/opencost/opencost/pkg/filter21/asset"
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/filter21/matcher"
-	"github.com/opencost/opencost/pkg/filter21/transform"
+	afilter "github.com/opencost/opencost/core/pkg/filter/asset"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	"github.com/opencost/opencost/core/pkg/filter/matcher"
+	"github.com/opencost/opencost/core/pkg/filter/transform"
 )
 
 // AssetMatcher is a matcher implementation for Asset instances,

+ 45 - 1
pkg/kubecost/assetprops.go → core/pkg/opencost/assetprops.go

@@ -1,8 +1,10 @@
-package kubecost
+package opencost
 
 import (
 	"fmt"
 	"strings"
+
+	"github.com/opencost/opencost/core/pkg/util/promutil"
 )
 
 // AssetProperty is a kind of property belonging to an Asset
@@ -42,6 +44,9 @@ const (
 	// AssetTypeProp describes the type of the Asset
 	AssetTypeProp AssetProperty = "type"
 
+	// AssetLabelProp describes a single label within an Asset.
+	AssetLabelProp AssetProperty = "label"
+
 	// AssetDepartmentProp describes the department of the Asset
 	AssetDepartmentProp AssetProperty = "department"
 
@@ -58,6 +63,34 @@ const (
 	AssetTeamProp AssetProperty = "team"
 )
 
+// IsLabel returns true if the allocation property has a label prefix
+func (apt *AssetProperty) IsLabel() bool {
+	return strings.HasPrefix(string(*apt), "label:")
+}
+
+// GetLabel returns the label string associated with the label property if it exists.
+// Otherwise, empty string is returned.
+func (apt *AssetProperty) GetLabel() string {
+	if apt.IsLabel() {
+		return strings.TrimSpace(strings.TrimPrefix(string(*apt), "label:"))
+	}
+	return ""
+}
+
+func ParseAssetProperties(values []string) ([]AssetProperty, error) {
+	props := []AssetProperty{}
+
+	for _, value := range values {
+		p, err := ParseAssetProperty(value)
+		if err != nil {
+			return nil, err
+		}
+		props = append(props, p)
+	}
+
+	return props, nil
+}
+
 // ParseAssetProperty attempts to parse a string into an AssetProperty
 func ParseAssetProperty(text string) (AssetProperty, error) {
 	switch strings.TrimSpace(strings.ToLower(text)) {
@@ -90,6 +123,12 @@ func ParseAssetProperty(text string) (AssetProperty, error) {
 	case "team":
 		return AssetTeamProp, nil
 	}
+
+	if strings.HasPrefix(text, "label:") {
+		label := promutil.SanitizeLabelName(strings.TrimSpace(strings.TrimPrefix(text, "label:")))
+		return AssetProperty(fmt.Sprintf("label:%s", label)), nil
+	}
+
 	return AssetNilProp, fmt.Errorf("invalid asset property: %s", text)
 }
 
@@ -145,6 +184,9 @@ const CustomProvider = "custom"
 // ScalewayProvider describes the provider Scaleway
 const ScalewayProvider = "Scaleway"
 
+// OracleProvider describes the provider Oracle
+const OracleProvider = "Oracle"
+
 // NilProvider describes unknown provider
 const NilProvider = "-"
 
@@ -163,6 +205,8 @@ func ParseProvider(str string) string {
 		return AzureProvider
 	case "scaleway", "scw", "kapsule":
 		return ScalewayProvider
+	case "oci", "oracle":
+		return OracleProvider
 	default:
 		return NilProvider
 	}

+ 3 - 3
pkg/kubecost/bingen.go → core/pkg/opencost/bingen.go

@@ -1,4 +1,4 @@
-package kubecost
+package opencost
 
 ////////////////////////////////////////////////////////////////////////////////
 // NOTE: If you add fields to _any_ struct that is serialized by bingen, please
@@ -46,7 +46,7 @@ package kubecost
 // @bingen:end
 
 // Allocation Version Set: Includes Allocation pipeline specific resources
-// @bingen:set[name=Allocation,version=20]
+// @bingen:set[name=Allocation,version=21]
 // @bingen:generate:Allocation
 // @bingen:generate[stringtable]:AllocationSet
 // @bingen:generate:AllocationSetRange
@@ -71,4 +71,4 @@ package kubecost
 // @bingen:generate:CloudCostLabels
 // @bingen:end
 
-//go:generate bingen -package=kubecost -version=17 -buffer=github.com/opencost/opencost/pkg/util
+//go:generate bingen -package=opencost -version=17 -buffer=github.com/opencost/opencost/core/pkg/util

+ 9 - 8
pkg/kubecost/cloudcost.go → core/pkg/opencost/cloudcost.go

@@ -1,15 +1,15 @@
-package kubecost
+package opencost
 
 import (
 	"errors"
 	"fmt"
 	"time"
 
-	"github.com/opencost/opencost/pkg/filter"
-	filter21 "github.com/opencost/opencost/pkg/filter21"
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	"github.com/opencost/opencost/pkg/log"
-	"github.com/opencost/opencost/pkg/util/timeutil"
+	"github.com/opencost/opencost/core/pkg/filter"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	legacyfilter "github.com/opencost/opencost/core/pkg/filter/legacy"
+	"github.com/opencost/opencost/core/pkg/log"
+	"github.com/opencost/opencost/core/pkg/util/timeutil"
 )
 
 // CloudCost represents a CUR line item, identifying a cloud resource and
@@ -279,7 +279,7 @@ func (ccs *CloudCostSet) Equal(that *CloudCostSet) bool {
 	return true
 }
 
-func (ccs *CloudCostSet) Filter(filters filter.Filter[*CloudCost]) *CloudCostSet {
+func (ccs *CloudCostSet) Filter(filters legacyfilter.Filter[*CloudCost]) *CloudCostSet {
 	if ccs == nil {
 		return nil
 	}
@@ -299,7 +299,7 @@ func (ccs *CloudCostSet) Filter(filters filter.Filter[*CloudCost]) *CloudCostSet
 	return result
 }
 
-func (ccs *CloudCostSet) Filter21(filters filter21.Filter) (*CloudCostSet, error) {
+func (ccs *CloudCostSet) Filter21(filters filter.Filter) (*CloudCostSet, error) {
 	if ccs == nil {
 		return nil, nil
 	}
@@ -345,6 +345,7 @@ func (ccs *CloudCostSet) Insert(cc *CloudCost) error {
 		ccs.CloudCosts = map[string]*CloudCost{}
 	}
 
+	// If the Aggregation properties is not set the returned key will be a hash of the properties values
 	ccKey := cc.Properties.GenerateKey(ccs.AggregationProperties)
 
 	// Add the given CloudCost to the existing entry, if there is one;

+ 2 - 2
pkg/kubecost/cloudcost_test.go → core/pkg/opencost/cloudcost_test.go

@@ -1,10 +1,10 @@
-package kubecost
+package opencost
 
 import (
 	"testing"
 	"time"
 
-	"github.com/opencost/opencost/pkg/util/timeutil"
+	"github.com/opencost/opencost/core/pkg/util/timeutil"
 )
 
 var ccProperties1 = &CloudCostProperties{

+ 5 - 5
pkg/kubecost/cloudcostmatcher.go → core/pkg/opencost/cloudcostmatcher.go

@@ -1,12 +1,12 @@
-package kubecost
+package opencost
 
 import (
 	"fmt"
 
-	"github.com/opencost/opencost/pkg/filter21/ast"
-	ccfilter "github.com/opencost/opencost/pkg/filter21/cloudcost"
-	"github.com/opencost/opencost/pkg/filter21/matcher"
-	"github.com/opencost/opencost/pkg/filter21/transform"
+	"github.com/opencost/opencost/core/pkg/filter/ast"
+	ccfilter "github.com/opencost/opencost/core/pkg/filter/cloudcost"
+	"github.com/opencost/opencost/core/pkg/filter/matcher"
+	"github.com/opencost/opencost/core/pkg/filter/transform"
 )
 
 // CloudCostMatcher is a matcher implementation for CloudCost instances,

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels