Showing preview only (665K chars total). Download the full file or copy to clipboard to get everything.
Repository: vmware/purser
Branch: master
Commit: fe4654999969
Files: 249
Total size: 600.1 KB
Directory structure:
gitextract_khhzg63f/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── custom.md
│ │ └── feature_request.md
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .make/
│ ├── Makefile.deploy.controller
│ └── Makefile.deploy.purser
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile.in
├── Gopkg.toml
├── LICENSE
├── Makefile
├── NOTICE
├── README.md
├── build/
│ ├── build.sh
│ ├── purser-binary-install.sh
│ ├── purser-binary-uninstall.sh
│ ├── purser-minimal-setup.sh
│ └── purser-setup.sh
├── cluster/
│ ├── artifacts/
│ │ ├── example-group.yaml
│ │ ├── example-subscriber.yaml
│ │ ├── group-template.json
│ │ ├── purser-group-crd.yaml
│ │ └── purser-subscriber-crd.yaml
│ ├── helm/
│ │ └── chart/
│ │ └── purser/
│ │ ├── .helmignore
│ │ ├── Chart.yaml
│ │ ├── README.md
│ │ ├── templates/
│ │ │ ├── NOTES.txt
│ │ │ ├── _helpers.tpl
│ │ │ ├── purser-controller-deployment.yaml
│ │ │ ├── purser-controller-rbac.yaml
│ │ │ ├── purser-controller-serviceaccount.yaml
│ │ │ ├── purser-controller-svc.yaml
│ │ │ ├── purser-database-statefulset.yaml
│ │ │ ├── purser-database-svc.yaml
│ │ │ ├── purser-ui-configmap.yaml
│ │ │ ├── purser-ui-deployment.yaml
│ │ │ ├── purser-ui-ingress.yaml
│ │ │ └── purser-ui-svc.yaml
│ │ └── values.yaml
│ ├── minimal/
│ │ ├── purser-controller-setup.yaml
│ │ ├── purser-database-setup.yaml
│ │ └── purser-ui-setup.yaml
│ ├── purser-controller-setup.yaml
│ ├── purser-database-setup.yaml
│ └── purser-ui-setup.yaml
├── cmd/
│ ├── controller/
│ │ ├── api/
│ │ │ ├── api.go
│ │ │ ├── apiHandlers/
│ │ │ │ ├── authenticationHandlers.go
│ │ │ │ ├── customGroupHandlers.go
│ │ │ │ ├── helpers.go
│ │ │ │ └── hierarchyAndMetricAPIHandlers.go
│ │ │ ├── logger.go
│ │ │ ├── router.go
│ │ │ └── routes.go
│ │ ├── config/
│ │ │ └── config.go
│ │ └── purserctrl.go
│ └── plugin/
│ ├── purser.go
│ └── types.go
├── docs/
│ ├── architecture.md
│ ├── custom-group-installation-and-usage.md
│ ├── design/
│ │ └── pricing.md
│ ├── developers-guide.md
│ ├── manual-installation.md
│ ├── plugin-installation.md
│ ├── plugin-usage.md
│ ├── purser-deployment.md
│ └── sourcecode-installation.md
├── openapi.yaml
├── pkg/
│ ├── apis/
│ │ ├── groups/
│ │ │ └── v1/
│ │ │ ├── deepcopy.go
│ │ │ ├── docs.go
│ │ │ ├── register.go
│ │ │ └── types.go
│ │ └── subscriber/
│ │ └── v1/
│ │ ├── deepcopy.go
│ │ ├── docs.go
│ │ ├── register.go
│ │ └── types.go
│ ├── client/
│ │ ├── clientset/
│ │ │ └── typed/
│ │ │ ├── groups/
│ │ │ │ └── v1/
│ │ │ │ ├── group.go
│ │ │ │ └── group_client.go
│ │ │ └── subscriber/
│ │ │ └── v1/
│ │ │ ├── subsciber_client.go
│ │ │ └── subscriber.go
│ │ └── clientset.go
│ ├── controller/
│ │ ├── buffering/
│ │ │ └── ring_buffer.go
│ │ ├── controller.go
│ │ ├── controller_test.go
│ │ ├── dgraph/
│ │ │ ├── dgraph.go
│ │ │ ├── login.go
│ │ │ ├── models/
│ │ │ │ ├── constants.go
│ │ │ │ ├── container.go
│ │ │ │ ├── daemonset.go
│ │ │ │ ├── deployment.go
│ │ │ │ ├── group.go
│ │ │ │ ├── job.go
│ │ │ │ ├── label.go
│ │ │ │ ├── namespace.go
│ │ │ │ ├── node.go
│ │ │ │ ├── pod.go
│ │ │ │ ├── pod_test.go
│ │ │ │ ├── process.go
│ │ │ │ ├── pv.go
│ │ │ │ ├── pvc.go
│ │ │ │ ├── query/
│ │ │ │ │ ├── cluster.go
│ │ │ │ │ ├── cluster_test.go
│ │ │ │ │ ├── constants_test.go
│ │ │ │ │ ├── group.go
│ │ │ │ │ ├── group_test.go
│ │ │ │ │ ├── helpers.go
│ │ │ │ │ ├── helpers_test.go
│ │ │ │ │ ├── label.go
│ │ │ │ │ ├── label_test.go
│ │ │ │ │ ├── login.go
│ │ │ │ │ ├── pod.go
│ │ │ │ │ ├── pod_test.go
│ │ │ │ │ ├── queries.go
│ │ │ │ │ ├── resource.go
│ │ │ │ │ ├── resource_test.go
│ │ │ │ │ ├── subscriber.go
│ │ │ │ │ ├── subscriber_test.go
│ │ │ │ │ └── types.go
│ │ │ │ ├── rateCard.go
│ │ │ │ ├── replicaset.go
│ │ │ │ ├── service.go
│ │ │ │ ├── statefulset.go
│ │ │ │ └── subscriber.go
│ │ │ └── purge.go
│ │ ├── discovery/
│ │ │ ├── executer/
│ │ │ │ └── exec.go
│ │ │ ├── generator/
│ │ │ │ └── graph.go
│ │ │ ├── linker/
│ │ │ │ ├── podlinks.go
│ │ │ │ ├── processlinks.go
│ │ │ │ └── servicelinks.go
│ │ │ └── processor/
│ │ │ ├── container.go
│ │ │ ├── pod.go
│ │ │ └── svc.go
│ │ ├── eventprocessor/
│ │ │ ├── notifier.go
│ │ │ ├── processor.go
│ │ │ ├── sync.go
│ │ │ └── updater.go
│ │ ├── metrics/
│ │ │ └── metrics.go
│ │ ├── payload.go
│ │ ├── persistentVolume.go
│ │ ├── types.go
│ │ └── utils/
│ │ ├── jsonutils.go
│ │ ├── k8sUtils.go
│ │ ├── purge.go
│ │ ├── purge_test.go
│ │ ├── timeUtils.go
│ │ ├── unitConversions.go
│ │ └── unitConversions_test.go
│ ├── plugin/
│ │ ├── costing.go
│ │ ├── grouping.go
│ │ ├── metrics/
│ │ │ └── metrics.go
│ │ ├── node.go
│ │ ├── pod.go
│ │ ├── pricing.go
│ │ ├── utils.go
│ │ └── volume.go
│ ├── pricing/
│ │ ├── aws/
│ │ │ ├── aws.go
│ │ │ └── convert.go
│ │ └── cloud.go
│ └── utils/
│ ├── fileutils.go
│ ├── k8sutil.go
│ └── logutil.go
├── plugin.yaml
├── test/
│ ├── controller/
│ │ └── buffering/
│ │ └── ring_buffer_test.go
│ ├── pricing/
│ │ └── pricing_aws_test.go
│ └── utils/
│ └── checkUtil.go
└── ui/
├── Dockerfile.deploy.purser
├── README.md
├── angular.json
├── e2e/
│ ├── protractor.conf.js
│ ├── src/
│ │ ├── app.e2e-spec.ts
│ │ └── app.po.ts
│ └── tsconfig.e2e.json
├── nginx.conf
├── package.json
├── proxy.conf.json
├── src/
│ ├── app/
│ │ ├── app.component.html
│ │ ├── app.component.scss
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.constants.ts
│ │ ├── app.module.ts
│ │ ├── app.routing.ts
│ │ ├── common/
│ │ │ └── messages/
│ │ │ ├── common.messages.ts
│ │ │ └── left-navigation.messages.ts
│ │ └── modules/
│ │ ├── capacity-graph/
│ │ │ ├── capacity-graph.module.ts
│ │ │ ├── components/
│ │ │ │ ├── capactiy-graph.component.html
│ │ │ │ ├── capactiy-graph.component.scss
│ │ │ │ ├── capactiy-graph.component.spec.ts
│ │ │ │ └── capactiy-graph.component.ts
│ │ │ └── services/
│ │ │ └── capacity-graph.service.ts
│ │ ├── changepassword/
│ │ │ ├── changepassword.module.ts
│ │ │ ├── components/
│ │ │ │ ├── changepassword.component.html
│ │ │ │ ├── changepassword.component.scss
│ │ │ │ ├── changepassword.component.spec.ts
│ │ │ │ └── changepassword.component.ts
│ │ │ └── services/
│ │ │ └── changepassword.service.ts
│ │ ├── logical-group/
│ │ │ ├── components/
│ │ │ │ ├── logical-group.component.css
│ │ │ │ ├── logical-group.component.html
│ │ │ │ ├── logical-group.component.spec.ts
│ │ │ │ └── logical-group.component.ts
│ │ │ ├── logical-group.module.ts
│ │ │ └── services/
│ │ │ └── logical-group.service.ts
│ │ ├── login/
│ │ │ ├── components/
│ │ │ │ ├── login.component.html
│ │ │ │ ├── login.component.scss
│ │ │ │ ├── login.component.spec.ts
│ │ │ │ └── login.component.ts
│ │ │ ├── login.module.ts
│ │ │ └── services/
│ │ │ └── login.service.ts
│ │ ├── logout/
│ │ │ ├── components/
│ │ │ │ ├── logout.component.html
│ │ │ │ ├── logout.component.scss
│ │ │ │ ├── logout.component.spec.ts
│ │ │ │ └── logout.component.ts
│ │ │ └── logout.module.ts
│ │ ├── options/
│ │ │ ├── components/
│ │ │ │ ├── options.component.html
│ │ │ │ ├── options.component.scss
│ │ │ │ ├── options.component.spec.ts
│ │ │ │ └── options.component.ts
│ │ │ └── options.module.ts
│ │ ├── topo-graph/
│ │ │ ├── components/
│ │ │ │ ├── topo-graph.component.html
│ │ │ │ ├── topo-graph.component.scss
│ │ │ │ ├── topo-graph.component.spec.ts
│ │ │ │ └── topo-graph.component.ts
│ │ │ ├── modules.ts
│ │ │ └── services/
│ │ │ └── topo-graph.service.ts
│ │ └── topologyGraph/
│ │ ├── components/
│ │ │ ├── index.ts
│ │ │ ├── topologyGraph.component.html
│ │ │ ├── topologyGraph.component.scss
│ │ │ └── topologyGraph.component.ts
│ │ ├── modules.ts
│ │ └── services/
│ │ └── topologyGraph.service.ts
│ ├── assets/
│ │ └── .gitkeep
│ ├── browserslist
│ ├── environments/
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── index.html
│ ├── json/
│ │ └── logicalGroup.json
│ ├── karma.conf.js
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.css
│ ├── test.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── tsconfig.json
└── tslint.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/custom.md
================================================
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
**Is this a BUG REPORT or FEATURE REQUEST?**:
> Uncomment only one, leave it on its own line:
>
> /kind bug
> /kind feature
**What happened**:
**What you expected to happen**:
**How to reproduce it (as minimally and precisely as possible)**:
**Anything else we need to know?**:
**Environment**:
- golang version:
- Kubernetes version (use `kubectl version`):
- Cloud provider or hardware configuration:
- OS (e.g. from /etc/os-release):
- Kernel (e.g. `uname -a`):
- Install tools:
- Others:
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!-- Thanks for sending a pull request! Here are some tips for you:
1. If this is your first time, read our contributor guidelines https://github.com/vmware/purser/blob/master/CONTRIBUTING.md
-->
**What this PR does / why we need it**:
**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #
**Special notes for your reviewer**:
**Release note**:
<!-- Write your release note:
1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE".
-->
```release-note
```
================================================
FILE: .gitignore
================================================
.idea
.vscode
.go/
bin/
vendor/
*.log
# compiled output
/dist
/tmp
/out-tsc
tmp/
# dependencies
node_modules/
================================================
FILE: .make/Makefile.deploy.controller
================================================
IMAGE := $(DOCKER_REPO)/purser
OUTPUT_DIR := tmp
DOCKER_OUT := $(OUTPUT_DIR)/docker
.PHONY: build
build: $(DOCKER_OUT)/bin/$(ARCH)/$(BIN)
$(DOCKER_OUT)/bin/$(ARCH)/$(BIN): build-dirs
@echo "building: $@"
@docker run \
-ti \
-u $$(id -u):$$(id -g) \
-v $$(pwd)/$(DOCKER_OUT)/.go:/go:$(DOCKER_MOUNT_MODE) \
-v $$(pwd)/$(BUILD):/go/src/$(PRO)/$(BUILD):$(DOCKER_MOUNT_MODE) \
-v $$(pwd)/$(CMD):/go/src/$(PRO)/$(CMD):$(DOCKER_MOUNT_MODE) \
-v $$(pwd)/$(PKG):/go/src/$(PRO)/$(PKG):$(DOCKER_MOUNT_MODE) \
-v $$(pwd)/$(DEP):/go/src/$(PRO)/$(DEP):$(DOCKER_MOUNT_MODE) \
-v $$(pwd)/$(DOCKER_OUT)/bin/$(ARCH):/go/bin:$(DOCKER_MOUNT_MODE) \
-v $$(pwd)/$(DOCKER_OUT)/bin/$(ARCH):/go/bin/linux_$(ARCH):$(DOCKER_MOUNT_MODE) \
-v $$(pwd)/$(DOCKER_OUT)/.go/std/$(ARCH):/usr/local/go/pkg/linux_$(ARCH)_static:$(DOCKER_MOUNT_MODE) \
-w /go/src \
$(BUILD_IMAGE) \
/bin/sh -c " \
ARCH=$(ARCH) \
VERSION=$(VERSION) \
PKG=$(PKG) \
./$(PRO)/$(BUILD)/build.sh \
"
DOTFILE_IMAGE = $(subst :,_,$(subst /,_,$(IMAGE))-$(VERSION))
.PHONY: container
container: $(DOCKER_OUT)/.container-$(DOTFILE_IMAGE) container-name
$(DOCKER_OUT)/.container-$(DOTFILE_IMAGE): $(DOCKER_OUT)/bin/$(ARCH)/$(BIN) Dockerfile.in
@sed \
-e 's|ARG_DOCK|$(DOCKER_OUT)|g' \
-e 's|ARG_BIN|$(BIN)|g' \
-e 's|ARG_ARCH|$(ARCH)|g' \
-e 's|ARG_FROM|$(BASEIMAGE)|g' \
Dockerfile.in > $(DOCKER_OUT)/.dockerfile-$(ARCH)
@docker build -t $(IMAGE):$(VERSION) -f $(DOCKER_OUT)/.dockerfile-$(ARCH) .
@docker images -q $(IMAGE):$(VERSION) > $@
.PHONY: container-name
container-name:
@echo "container: $(IMAGE):$(VERSION)"
.PHONY: push
push: $(DOCKER_OUT)/.push-$(DOTFILE_IMAGE) push-name
$(DOCKER_OUT)/.push-$(DOTFILE_IMAGE): $(DOCKER_OUT)/.container-$(DOTFILE_IMAGE)
ifeq ($(findstring gcr.io,$(DOCKER_REPO)),gcr.io)
@gcloud docker -- push $(IMAGE):$(VERSION)
else
@docker push $(IMAGE):$(VERSION)
endif
@docker images -q $(IMAGE):$(VERSION) > $@
.PHONY: push-name
push-name:
@echo "pushed: $(IMAGE):$(VERSION)"
.PHONY: build-dirs
build-dirs:
@mkdir -p $(DOCKER_OUT)
@mkdir -p $(DOCKER_OUT)/bin/$(ARCH)
@mkdir -p $(DOCKER_OUT)/.go/src/$(PKG) $(DOCKER_OUT)/.go/pkg $(DOCKER_OUT)/.go/bin $(DOCKER_OUT)/.go/std/$(ARCH)
.PHONY: clean
clean: container-clean bin-clean
.PHONY: container-clean
container-clean:
rm -rf $(DOCKER_OUT)/.container-* $(DOCKER_OUT)/.dockerfile-* $(DOCKER_OUT)/.push-*
.PHONY: bin-clean
bin-clean:
rm -rf $(DOCKER_OUT)/
================================================
FILE: .make/Makefile.deploy.purser
================================================
DEPLOY_DOCKERFILE?=ui/Dockerfile.deploy.purser
CLUSTER_DIR?=${PWD}/cluster
COMMIT:=$(shell git rev-parse --short HEAD)
TIMESTAMP:=$(shell date +%s)
TAG?=$(COMMIT)-$(TIMESTAMP)
.PHONY: deploy-purser
deploy-purser: kubectl-deploy-purser-db kubectl-deploy-purser-ui
.PHONY: kubectl-deploy-purser-ui
kubectl-deploy-purser-ui:
@echo "Deploys purser-ui service"
@kubectl create -f $(CLUSTER_DIR)/purser-ui.yaml
.PHONY: deploy-purser-ui
deploy-purser-ui: build-purser-ui-image push-purser-ui-image
.PHONY: build-purser-ui-image
build-purser-ui-image:
@docker build --build-arg BINARY=purser-ui -t $(REGISTRY)/$(DOCKER_REPO)/purser-ui -f $(DEPLOY_DOCKERFILE) .
@docker tag $(REGISTRY)/$(DOCKER_REPO)/purser-ui $(REGISTRY)/$(DOCKER_REPO)/purser-ui:$(TAG)
.PHONY: push-purser-ui-image
push-purser-ui-image: build-purser-ui-image
@docker push $(REGISTRY)/$(DOCKER_REPO)/purser-ui
.PHONY: clean-purser-ui-image
clean-purser-ui-image:
@docker rmi -f $(REGISTRY)/$(DOCKER_REPO)/purser-ui
.PHONY: kubectl-deploy-purser-db
kubectl-deploy-purser-db:
@echo "Deploys purser purser-db service"
@kubectl create -f $(CLUSTER_DIR)/purser-db.yaml
================================================
FILE: .travis.yml
================================================
services:
- docker
language: go
os:
- linux
go:
- "1.10"
script:
- make tools
- make deps
- make install
- make travis-build
- make check
notifications:
email: false
================================================
FILE: CODE_OF_CONDUCT.md
================================================
Contributor Code of Conduct
======================
As contributors and maintainers of this project, we pledge to respect
everyone who contributes by posting issues, updating documentation,
submitting pull requests, providing feedback in comments, and any other
activities.
Communication through any project channels (GitHub, mailing lists,
Twitter, and so on) must be constructive and never resort to personal
attacks, trolling, public or private harassment, insults, or other
unprofessional conduct.
We promise to extend courtesy and respect to everyone involved in
this project, regardless of gender, gender identity, sexual
orientation, disability, age, race, ethnicity, religious beliefs,
or level of experience. We expect anyone contributing to this project
to do the same.
If any member of the community violates this code of conduct, the
maintainers of this project may take action, including removing issues,
comments, and PRs or blocking accounts, as deemed appropriate.
If you are subjected to or witness unacceptable behavior, or have any
other concerns, please communicate with us.
If you have suggestions to improve the code of conduct, please submit
an issue or PR.
**Attribution**
This Code of Conduct is adapted from the VMware Clarity project, available at this page: https://github.com/vmware/clarity/blob/master/CODE_OF_CONDUCT.md
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Purser
Welcome! We gladly accept contributions from the community. If you wish
to contribute code and you have not signed our contributor license
agreement (CLA), our bot will update the issue when you open a pull
request. For any questions about the CLA process, please refer to our
[FAQ](https://cla.vmware.com/faq).
## Logging Bugs
Anyone can log a bug using the GitHub 'New Issue' button. Please use
a short title and give as much information as you can about what the
problem is, relevant software versions, and how to reproduce it. If you
know the fix or a workaround include that too.
## Install dependencies
- Install [git](https://git-scm.com/downloads)
- Install [Go](https://golang.org/dl/) version at least 1.7
- Set GOPATH environment variable. [https://github.com/golang/go/wiki/SettingGOPATH](https://github.com/golang/go/wiki/SettingGOPATH)
- Add GOPATH/bin in system PATH variable
## Code Contribution Flow
We use GitHub pull requests to incorporate code changes from external
contributors. Typical contribution flow steps are:
- Fork the Purser repo into a new repo on GitHub
- Clone the forked repo locally and set the original Purser repo as the upstream repo
- Make changes in a topic branch and commit
- Fetch changes from upstream and resolve any merge conflicts so that your topic branch is up-to-date
- Push all commits to the topic branch in your forked repo
- Submit a pull request to merge topic branch commits to upstream master
If this process sounds unfamiliar have a look at the
excellent [overview of collaboration via pull requests on
GitHub](https://help.github.com/categories/collaborating-with-issues-and-pull-requests) for more information.
## Coding Style
Our standard for Golang contributions is to match the format of the [standard
Go package library](https://golang.org/pkg).
- Run `go fmt` on all code.
- All public interfaces, functions, and structs must have complete, grammatically correct Godoc comments that explain their purpose and proper usage.
- Use self-explanatory names for all variables, functions, and interfaces.
- Add comments for non-obvious features of internal implementations but otherwise let the code explain itself.
- Include unit tests for new features and update tests for old ones.
Go is pretty readable so if you follow these rules most functions
will not need additional comments.
### Commit Message Format
We follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/).
Be sure to include any related GitHub
issue references in the commit message. See [GFM
syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown)
for referencing issues.
### Sign the Contributor License Agreement (CLA)
VMware Apache-licensed projects require all contributors to sign a CLA.
Visit https://cla.vmware.com and follow steps presented there.
### Fork the Repo
Navigate to the [Purser repo on
GitHub](https://github.com/vmware/purser) and use the 'Fork' button to
create a forked repository under your GitHub account. This gives you a copy
of the repo for pull requests back to purser in https://github.com/your-github-id/purser
### Clone and Set Upstream Remote
Make a local clone of the forked repo and add the base purser
repo as the upstream remote repository.
``` shell
# (go to directory $GOPATH/src/github.com/vmware)
cd $GOPATH/src/github.com/vmware
# (clone the forked repository)
git clone https://github.com/<your-github-id>/purser.git
# (go to purser directory)
cd $GOPATH/src/github.com/vmware/purser
# (add upstream repository as the original purser repo)
git remote add upstream https://github.com/vmware/purser.git
```
The last git command prepares your clone to pull changes from the
upstream repo and push them into the fork, which enables you to keep
the fork up to date. More on that shortly.
### Download dependencies
Run the following commands to download dependencies.
``` shell
make tools
make deps
make install
```
### Make Changes and Commit
Start a new topic branch(say branch-name: `foo-api-fix-22`) from the current HEAD position on master and
commit your feature changes into that branch.
``` shell
git checkout -b foo-api-fix-22 master
# (Make feature changes)
```
Ensure that you run the following commands to ensure new dependencies are recorded and to fix formatting of the code.
``` shell
make update
make format
make check
```
If there is an error while running `make check`, fix them and re run the above commands.
``` shell
git commit -a --signoff
git push origin foo-api-fix-22
```
The --signoff puts your signature in the commit. It's required by our CLA
bot.
It is a git best practice to put work for each new feature in a separate
topic branch and use git checkout commands to jump between them. This
makes it possible to have multiple active pull requests. We can accept
pull requests from any branch, so it's up to you how to manage them.
### Stay in Sync with Upstream
From time to time you'll need to merge changes from the upstream
repo so your topic branch stays in sync with other checkins. To
do so switch to your topic branch, pull from the upstream repo, and
push into the fork. If there are conflicts you'll need to [merge
them now](https://stackoverflow.com/questions/161813/how-to-resolve-merge-conflicts-in-git).
``` shell
git checkout foo-api-fix-22
git fetch -a
git pull --rebase upstream master --tags
git push --force-with-lease origin foo-api-fix-22
```
The git pull and push options are important. Here are some details if you
need deeper understanding.
- 'pull --rebase' eliminates unnecessary merges
by replaying your commit(s) into the log as if they happened
after the upstream changes. Check out [What is a "merge
bubble"?](https://stackoverflow.com/questions/26239379/what-is-a-merge-bubble)
for why this is important.
- --tags ensures that object tags are also pulled
- Depending on your git configuration push --force-with-lease is required to make git update your fork with commits from the upstream repo.
### Create a Pull Request
Github docs on creating a pull request from a fork: [Pull request from a fork](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
To contribute your feature, create a pull request by going to the [purser upstream repo on GitHub](https://github.com/vmware/purser) and pressing the 'New pull request' button.
Select 'compare across forks' and select your-github-id/purser as 'head fork'
and foo-api-fix-22 as the 'compare' branch. Leave the base fork as
vmware/purser and master.
### Wait...
A committer will look the request over and do one of three things:
- accept it
- send back comments about things you need to fix
- or close the request without merging if we don't think it's a good addition.
### Updating Pull Requests with New Changes
If your pull request needs changes based on code review,
you'll most likely want to squash the fixes into existing commits.
If your pull request contains a single commit or your changes are related
to the most recent commit, you can simply amend the commit.
``` shell
git add .
git commit --amend
git push --force-with-lease origin foo-api-fix-22
```
If you need to squash changes into an earlier commit, you can use:
``` shell
git add .
git commit --fixup <commit>
git rebase -i --autosquash master
git push --force-with-lease origin foo-api-fix-22
```
Be sure to add a comment to the pull request indicating your new changes
are ready to review, as GitHub does not generate a notification when
you git push.
## Final Words
Thanks for helping us make the project better!
================================================
FILE: Dockerfile.in
================================================
FROM ARG_FROM
LABEL maintainer = "VMware <kreddyj@vmware.com>"
LABEL author = "Krishna Karthik <kreddyj@vmware.com>"
ADD ARG_DOCK/bin/ARG_ARCH/ARG_BIN /ARG_BIN
================================================
FILE: Gopkg.toml
================================================
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "k8s.io/api"
version = "kubernetes-1.9.0"
[[constraint]]
name = "k8s.io/apiextensions-apiserver"
version = "kubernetes-1.9.0"
[[constraint]]
name = "k8s.io/apimachinery"
version = "kubernetes-1.9.0"
[[constraint]]
name = "k8s.io/client-go"
version = "6.0.0"
[[constraint]]
name = "google.golang.org/grpc"
version = "1.15.0"
[[constraint]]
name = "github.com/dgraph-io/dgo"
branch = "master"
[[override]]
name = "github.com/tidwall/gjson"
version = "1.1.2"
[prune]
go-tests = true
unused-packages = true
================================================
FILE: LICENSE
================================================
Purser
Copyright (c) 2018 VMware, Inc. All rights reserved.
The Apache 2.0 license (the License) set forth below applies to all parts of the Purser
project. You may not use this file except in compliance with the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the
copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other
entities that control, are controlled by, or are under common control
with that entity. For the purposes of this definition, "control" means
(i) the power, direct or indirect, to cause the direction or management
of such entity, whether by contract or otherwise, or (ii) ownership
of fifty percent (50%) or more of the outstanding shares, or (iii)
beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source,
and configuration files.
"Object" form shall mean any form resulting from mechanical transformation
or translation of a Source form, including but not limited to compiled
object code, generated documentation, and conversions to other media
types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a copyright
notice that is included in or attached to the work (an example is provided
in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form,
that is based on (or derived from) the Work and for which the editorial
revisions, annotations, elaborations, or other modifications represent,
as a whole, an original work of authorship. For the purposes of this
License, Derivative Works shall not include works that remain separable
from, or merely link (or bind by name) to the interfaces of, the Work
and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the
original version of the Work and any modifications or additions to
that Work or Derivative Works thereof, that is intentionally submitted
to Licensor for inclusion in the Work by the copyright owner or by an
individual or Legal Entity authorized to submit on behalf of the copyright
owner. For the purposes of this definition, "submitted" means any form of
electronic, verbal, or written communication sent to the Licensor or its
representatives, including but not limited to communication on electronic
mailing lists, source code control systems, and issue tracking systems
that are managed by, or on behalf of, the Licensor for the purpose of
discussing and improving the Work, but excluding communication that is
conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable copyright license to reproduce, prepare
Derivative Works of, publicly display, publicly perform, sublicense, and
distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
royalty- free, irrevocable (except as stated in this section) patent
license to make, have made, use, offer to sell, sell, import, and
otherwise transfer the Work, where such license applies only to those
patent claims licensable by such Contributor that are necessarily
infringed by their Contribution(s) alone or by combination of
their Contribution(s) with the Work to which such Contribution(s)
was submitted. If You institute patent litigation against any entity
(including a cross-claim or counterclaim in a lawsuit) alleging that the
Work or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses granted
to You under this License for that Work shall terminate as of the date
such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works
thereof in any medium, with or without modifications, and in Source or
Object form, provided that You meet the following conditions:
a. You must give any other recipients of the Work or Derivative Works
a copy of this License; and
b. You must cause any modified files to carry prominent notices stating
that You changed the files; and
c. You must retain, in the Source form of any Derivative Works that
You distribute, all copyright, patent, trademark, and attribution
notices from the Source form of the Work, excluding those notices
that do not pertain to any part of the Derivative Works; and
d. If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one of
the following places: within a NOTICE text file distributed as part
of the Derivative Works; within the Source form or documentation,
if provided along with the Derivative Works; or, within a display
generated by the Derivative Works, if and wherever such third-party
notices normally appear. The contents of the NOTICE file are for
informational purposes only and do not modify the License. You
may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text
from the Work, provided that such additional attribution notices
cannot be construed as modifying the License. You may add Your own
copyright statement to Your modifications and may provide additional
or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works
as a whole, provided Your use, reproduction, and distribution of the
Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally
submitted for inclusion in the Work by You to the Licensor shall be
under the terms and conditions of this License, without any additional
terms or conditions. Notwithstanding the above, nothing herein shall
supersede or modify the terms of any separate license agreement you may
have executed with Licensor regarding such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor
provides the Work (and each Contributor provides its Contributions) on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied, including, without limitation, any warranties or
conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
A PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any risks
associated with Your exercise of permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including
negligence), contract, or otherwise, unless required by applicable law
(such as deliberate and grossly negligent acts) or agreed to in writing,
shall any Contributor be liable to You for damages, including any direct,
indirect, special, incidental, or consequential damages of any character
arising as a result of this License or out of the use or inability to
use the Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all other
commercial damages or losses), even if such Contributor has been advised
of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may
choose to offer, and charge a fee for, acceptance of support, warranty,
indemnity, or other liability obligations and/or rights consistent with
this License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf of
any other Contributor, and only if You agree to indemnify, defend, and
hold each Contributor harmless for any liability incurred by, or claims
asserted against, such Contributor by reason of your accepting any such
warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: Makefile
================================================
# Copyright (c) 2018 VMware Inc. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The binary to build (just the basename).
BIN := controller
# This repo's root import path (under GOPATH)
PRO := github.com/vmware/purser
DEP := vendor
BUILD := build
PKG := pkg
CMD := cmd/controller
# Where to push the docker image.
REGISTRY?=docker.io
DOCKER_REPO?=kreddyj
# Which architecture to build - see $(ALL_ARCH) for options.
ARCH?= amd64
# This version-strategy uses a manual value to set the version string
VERSION := controller-1.0.2
###
### These variables should not need tweaking.
###
ALL_ARCH := amd64 arm arm64 ppc64le
BASEIMAGE?=photon
BUILD_IMAGE?=golang:1.11
DOCKER_MOUNT_MODE=delegated
# Set dep management tool parameters
VENDOR_DIR := vendor
DEP_BIN_NAME := dep
DEP_BIN_DIR := ./tmp/bin
DEP_BIN := $(DEP_BIN_DIR)/$(DEP_BIN_NAME)
DEP_VERSION := v0.5.0
# Define and get the vakue for UNAME_S variable from shell
UNAME_S := $(shell uname -s)
.PHONY: travis-build
travis-build: install-plugin install-controller travis-success
.PHONY: install-plugin
install-plugin:
go install github.com/vmware/purser/cmd/plugin
.PHONY: install-controller
install-controller: build container
.PHONY: travis-success
travis-success:
@echo "travis build success"
# If you want to build all binaries, see the 'all-build' rule.
# If you want to build all containers, see the 'all-container' rule.
# If you want to build AND push all containers, see the 'all-push' rule.
.PHONY: all
all: deps build check
build-%:
@$(MAKE) --no-print-directory ARCH=$* build
container-%:
@$(MAKE) --no-print-directory ARCH=$* container
push-%:
@$(MAKE) --no-print-directory ARCH=$* push
.PHONY: all-build
all-build: $(addprefix build-, $(ALL_ARCH))
.PHONY: all-container
all-container: $(addprefix container-, $(ALL_ARCH))
.PHONY: all-push
all-push: $(addprefix push-, $(ALL_ARCH))
.PHONY: deps
## Download build dependencies.
deps: $(DEP_BIN) $(VENDOR_DIR)
# install dep in a the tmp/bin dir of the repo
$(DEP_BIN):
@echo "Installing 'dep' $(DEP_VERSION) at '$(DEP_BIN_DIR)'..."
mkdir -p $(DEP_BIN_DIR)
ifeq ($(UNAME_S),Darwin)
@curl -L -s https://github.com/golang/dep/releases/download/$(DEP_VERSION)/dep-darwin-amd64 -o $(DEP_BIN)
@cd $(DEP_BIN_DIR) && \
echo "1a7bdb0d6c31ecba8b3fd213a1170adf707657123e89dff234871af9e0498be2 dep" > dep-darwin-amd64.sha256 && \
shasum -a 256 --check dep-darwin-amd64.sha256
else
@curl -L -s https://github.com/golang/dep/releases/download/$(DEP_VERSION)/dep-linux-amd64 -o $(DEP_BIN)
@cd $(DEP_BIN_DIR) && \
echo "287b08291e14f1fae8ba44374b26a2b12eb941af3497ed0ca649253e21ba2f83 dep" > dep-linux-amd64.sha256 && \
sha256sum -c dep-linux-amd64.sha256
endif
@chmod +x $(DEP_BIN)
$(VENDOR_DIR): Gopkg.toml Gopkg.lock
@echo "checking dependencies..."
@$(DEP_BIN) ensure -v
.PHONY: install
install: ## Fetches all dependencies using dep
@$(DEP_BIN) ensure -v
.PHONY: update
update: ## Updates all dependencies defined for dep
@$(DEP_BIN) ensure -update -v
include ./.make/Makefile.deploy.controller
include ./.make/Makefile.deploy.purser
.PHONY: version
version:
@echo $(VERSION)
.PHONY: clean-vendor ## Removes the ./vendor directory.
clean-vendor:
-rm -rf $(VENDOR_DIR)
GOFORMAT_FILES := $(shell find . -name '*.go' | grep -v /vendor/)
.PHONY: format ## Formats any go file that differs from gofmt's style and removes unused imports
format:
@gofmt -s -l -w ${GOFORMAT_FILES}
@goimports -l -w ${GOFORMAT_FILES}
.PHONY: tools
tools: ## Installs required go tools
@go get -u github.com/alecthomas/gometalinter && gometalinter --install
@go get -u golang.org/x/tools/cmd/goimports
.PHONY: check
check: ## Concurrently runs a whole bunch of static analysis tools
gometalinter --enable=misspell --enable-gc --vendor --deadline 300s ./...
================================================
FILE: NOTICE
================================================
Purser
Copyright (c) 2018 VMware, Inc. All Rights Reserved.
This product is licensed to you under the Apache 2.0 license (the "License").
You may not use this product except in compliance with the Apache 2.0 License.
This product may include a number of subcomponents with separate copyright notices and license terms.
Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license,
as noted in the LICENSE file.
================================================
FILE: README.md
================================================

# K8s Extension for Application Visibility
[](https://travis-ci.org/vmware/purser) [](https://goreportcard.com/report/github.com/vmware/purser)
- [What is Purser?](#overview)
- [Features](#features)
- [Setup and Installation](#setup-and-installation)
- [Uninstalling](#uninstalling)
- [API Documentation](#api-documentation)
- [Additional Documentation](#additional-documentation)
- [Community, Discussion, Contribution and Support](#community-discussion-contribution-and-support)
## Overview
Purser is an extension to Kubernetes tasked at providing an insight into *cluster topology*, *costing*, *capacity allocations* and *resource interactions* along with the provision of *logical grouping of resources* for Kubernetes based cloud native applications in a cloud neutral manner, with the focus on catering to a multitude of users ranging from Sys Admins, to DevOps to Developers.
It comprises of three components: a controller, a plugin and a UI dashboard.
The controller component deployed inside the cluster watches for K8s native and custom resources associated with the application, thereby, periodically building not just an inventory but also performing discovery by generating and storing the interactions among the resources such as containers, pods and services.
The plugin component is a CLI tool interfacing with the `kubectl` that helps query costs, savings defined at a level of control of the application level components rather than at the infrastructure level.
The UI dashboard is a robust application that renders the Purser UI for providing visual representation to the complete cluster metrics in a single pane of glass.
## Features
Purser with its robust CLI and UI capabilities provides a set of features including, but not limited to the list below.
- Capability to provide visibility into the following aspects of the K8s cluster
- workload cost associated with the native/custom resources
- savings opportunities associated with storage and compute requirements
- single pane view of the complete cluster hierarchy
- capacity allocations for CPU, memory, disk space and other resources
- interactions among associated resources such as pods and services
- Capability of user defined logical grouping of resources based on `K8s CRD` implementation for enhanced filtering.
- A plugin extension to `kubectl` along with the UI for developer centric usage.
- Capability to subscribe to inventory changes via web-hook implementation.
### UI Demo

### CLI Demo

## Setup and Installation
### Prerequisites
- Kubernetes version 1.9 or greater.
- `kubectl` installed and configured. For details see [here](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
- `curl` installed. Download it [here](https://curl.haxx.se/download.html)
### Linux/Mac Users:
```bash
curl https://raw.githubusercontent.com/vmware/purser/master/build/purser-setup.sh -O && sh purser-setup.sh
```
_NOTE: If you want to try out purser on minikube, you can do the following steps instead._
```bash
curl https://raw.githubusercontent.com/vmware/purser/master/build/purser-minimal-setup.sh -O && sh purser-minimal-setup.sh
# Wait for containers to start, around 30s
# Open Purser in browser
minikube service purser-ui -n purser
```
### Windows/Other Users:
For detailed installation steps follow the instructions in the [manual installation guide](./docs/manual-installation.md).
### Purser Plugin Setup (Optional)
_NOTE: This Plugin installation is optional. This feature is not actively maintained and will be deprecated soon._
If you want to install and use Purser's command line interface
- [Plugin installation guide](./docs/plugin-installation.md).
- [Plugin Usage](./docs/plugin-usage.md).
### Other Installation Methods
For other installation methods such as **manual installation** or **installation from source code** refer guides in [docs](./docs).
### Uninstalling
``` bash
kubectl delete ns purser
```
_**NOTE:** Use flag `--kubeconfig=<absolute path to config>` if your cluster configuration is not at the [default location](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable)._
## API Documentation
The project uses Swagger to document API's endpoints. The documentation is available at [Swagger Hub](https://app.swaggerhub.com/apis/hemani19/purser/1.0.0).
## Additional Documentation
Additional documentation can be found below:
- [Manual Installation Guide](./docs/manual-installation.md)
- [Source Code Installation Guide](./docs/sourcecode-installation.md)
- [Purser Architecture and Workflow](./docs/architecture.md)
- [Purser Plugin Usage](./docs/plugin-usage.md)
- [Developers Guide](./docs/developers-guide.md)
- [Purser Deployment Guide](./docs/purser-deployment.md)
- [Purser UI Development Guide](./ui/README.md)
## Community, Discussion, Contribution and Support
**Issues:** Have an issue with Purser, please [log it](https://github.com/vmware/purser/issues).
**Contributing:** Would you like to contribute to our project, refer [How to contribute](./CONTRIBUTING.md), [Developers Guide](./docs/developers-guide.md) and [Code of Conduct](./CODE_OF_CONDUCT.md) docs.
================================================
FILE: build/build.sh
================================================
# Copyright (c) 2018 VMware Inc. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit
set -o nounset
if [ -z "${PKG}" ]; then
echo "PKG must be set"
exit 1
fi
if [ -z "${ARCH}" ]; then
echo "ARCH must be set"
exit 1
fi
if [ -z "${VERSION}" ]; then
echo "VERSION must be set"
exit 1
fi
export CGO_ENABLED=0
export GOARCH="${ARCH}"
go install \
-installsuffix "static" \
-ldflags "-X ${PKG}/version.VERSION=${VERSION}" \
./...
================================================
FILE: build/purser-binary-install.sh
================================================
# Copyright (c) 2018 VMware Inc. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Realease Version
releaseVersion=v1.0.0
# === Purser Plugin ===
# Detecting os type
unameOut="$(uname -s)"
case "${unameOut}" in
Linux*) machine=Linux;;
Darwin*) machine=Mac;;
CYGWIN*) machine=Cygwin;;
MINGW*) machine=MinGw;;
*) machine="UNKNOWN:${unameOut}"
esac
echo "Detecting your Operating System: ${machine}"
echo "Downloading files for plugin..."
# Download purser plugin yaml
pluginYamlUrl=https://github.com/vmware/purser/releases/download/$releaseVersion/plugin.yaml
wget -q --show-progress -O plugin.yaml $pluginYamlUrl
# Downloading purser plugin binary based on os type
if [ $machine = Linux ]
then
pluginUrl=https://github.com/vmware/purser/releases/download/$releaseVersion/purser_plugin_linux_amd64
elif [ $machine = Mac ]
then
pluginUrl=https://github.com/vmware/purser/releases/download/$releaseVersion/purser_plugin_darwin_amd64
else
echo "No match found for your os: $machine"
echo "Install the plugin from source code: https://github.com/vmware/purser/blob/master/README.md"
exit 3 # unsuccessful shell script
fi
wget -q --show-progress -O purser_plugin $pluginUrl
# Move th plugin yaml to one of the location specified in
# https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/
if [ ! -d $HOME/.kube/plugins ]
then
mkdir $HOME/.kube/plugins
fi
echo "Moving plugin.yaml to $HOME/.kube/plugins/"
mv plugin.yaml $HOME/.kube/plugins/
# Change execution permissions for the binary
chmod +x purser_plugin
# Move the binary to a location which is in environment PATH variable
echo "Moving the binary to /usr/local/bin"
sudo mv purser_plugin /usr/local/bin
echo "Purser plugin installation Completed"
echo ""
echo "Purser Installation Completed"
================================================
FILE: build/purser-binary-uninstall.sh
================================================
# Copyright (c) 2018 VMware Inc. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
echo "Removing plugin.yaml from $HOME/.kube/plugins/"
rm $HOME/.kube/plugins/plugin.yaml
echo "Removing the binary from /usr/local/bin"
sudo rm /usr/local/bin/purser_plugin
================================================
FILE: build/purser-minimal-setup.sh
================================================
# Copyright (c) 2018 VMware Inc. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Realease Version
releaseVersion=1.0.2
echo "Installing Purser (minimal setup) version: ${releaseVersion}"
# Namespace setup
echo "Creating namespace purser"
kubectl create ns purser
# DB setup
echo "Setting up database for Purser"
curl https://raw.githubusercontent.com/vmware/purser/master/cluster/minimal/purser-database-setup.yaml -O
kubectl --namespace=purser create -f purser-database-setup.yaml
echo "Waiting for database containers to be in running state... (30s)"
sleep 30s
# Purser controller setup
echo "Setting up controller for Purser"
curl https://raw.githubusercontent.com/vmware/purser/master/cluster/minimal/purser-controller-setup.yaml -O
kubectl --namespace=purser create -f purser-controller-setup.yaml
# Purser UI setup
echo "Setting up UI for Purser"
curl https://raw.githubusercontent.com/vmware/purser/master/cluster/minimal/purser-ui-setup.yaml -O
kubectl --namespace=purser create -f purser-ui-setup.yaml
echo "Purser setup is completed"
================================================
FILE: build/purser-setup.sh
================================================
# Copyright (c) 2018 VMware Inc. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Kubeconfig location
read -p "Location for cluster's configuration (Press 'Enter' to take default $HOME/.kube/config): " readConfig
if [ -z "$readConfig" ];
then
kubeConfig="$HOME/.kube/config"
else
kubeConfig=$readConfig
fi
# Realease Version
releaseVersion=1.0.2
echo "Installing Purser version: ${releaseVersion}"
# Namespace setup
echo "Creating namespace purser"
kubectl --kubeconfig=$kubeConfig create ns purser
# DB setup
echo "Setting up database for Purser"
curl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-database-setup.yaml -O
kubectl --kubeconfig=$kubeConfig --namespace=purser create -f purser-database-setup.yaml
echo "Waiting for database containers to be in running state... (1 minute)"
sleep 60s
# Purser controller setup
echo "Setting up controller for Purser"
curl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-controller-setup.yaml -O
kubectl --kubeconfig=$kubeConfig --namespace=purser create -f purser-controller-setup.yaml
# Purser UI setup
echo "Setting up UI for Purser"
curl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-ui-setup.yaml -O
kubectl --kubeconfig=$kubeConfig --namespace=purser create -f purser-ui-setup.yaml
echo "Purser setup is completed"
================================================
FILE: cluster/artifacts/example-group.yaml
================================================
apiVersion: vmware.purser.com/v1
kind: Group
metadata:
name: example-group
spec:
name: example-group
labels:
expr1:
app:
- sample-app
- sample-app2
env:
- dev
expr2:
namespace:
- ns1
- ns2
expr3:
key1:
- val1
key2:
- val2
================================================
FILE: cluster/artifacts/example-subscriber.yaml
================================================
apiVersion: vmware.purser.com/v1
kind: Subscriber
metadata:
name: example-subscriber
spec:
name: example-subscriber
headers:
authorization: "Bearer <your-token>"
cluster: "<your-cluster-name>"
url: <your-webhook-url>
================================================
FILE: cluster/artifacts/group-template.json
================================================
{
"apiVersion": "vmware.purser.com/v1",
"kind": "Group",
"metadata": {
"name": "<enter-custom-group-name>"
},
"spec": {
"name": "<enter-custom-group-name>",
"labels": {
"expr1": {
"<enter-key-1>": [
"<enter-value-1>",
"<enter-value-2>"
],
"<enter-key-2>": [
"<enter-value-3>"
]
},
"expr2": {
"<enter-key-3>": [
"<enter-value-4>"
],
"<enter-key-4>": [
"<enter-value-5>"
]
}
}
}
}
================================================
FILE: cluster/artifacts/purser-group-crd.yaml
================================================
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: groups.vmware.purser.com
spec:
group: vmware.purser.com
names:
kind: Group
listKind: GroupList
plural: groups
singular: group
scope: Namespaced
version: v1
status:
acceptedNames:
kind: Group
listKind: GroupList
plural: groups
singular: group
================================================
FILE: cluster/artifacts/purser-subscriber-crd.yaml
================================================
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: subscribers.vmware.purser.com
spec:
group: vmware.purser.com
names:
kind: Subscriber
listKind: SubscriberList
plural: subscribers
singular: subscriber
scope: Namespaced
version: v1
status:
acceptedNames:
kind: Subscriber
listKind: SubscriberList
plural: subscribers
singular: subscriber
================================================
FILE: cluster/helm/chart/purser/.helmignore
================================================
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
================================================
FILE: cluster/helm/chart/purser/Chart.yaml
================================================
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Purser
name: purser
version: 0.1.0
================================================
FILE: cluster/helm/chart/purser/README.md
================================================
# [Purser](https://github.com/vmware/purser)
Purser is an extension to Kubernetes tasked at providing an insight into cluster topology, costing, capacity allocations and resource interactions along with the provision of logical grouping of resources for Kubernetes based cloud native applications in a cloud neutral manner, with the focus on catering to a multitude of users ranging from Sys Admins, to DevOps to Developers.
It comprises of three components: a controller, a plugin and a UI dashboard.
The controller component deployed inside the cluster watches for K8s native and custom resources associated with the application, thereby, periodically building not just an inventory but also performing discovery by generating and storing the interactions among the resources such as containers, pods and services.
The plugin component is a CLI tool interfacing with the kubectl that helps query costs, savings defined at a level of control of the application level components rather than at the infrastructure level.
The UI dashboard is a robust application that renders the Purser UI for providing visual representation to the complete cluster metrics in a single pane of glass.
> Taken from main [README](https://github.com/vmware/purser/blob/master/README.md)
> [Plugin installation guide](https://github.com/vmware/purser/blob/master/README.md#purser-plugin-setup)
## Chart Configuration
*See `values.yaml` for configuration notes. Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
```console
$ helm install --name purser \
--set database.storage=10Gi \
purser
```
Alternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. For example,
```console
$ helm install --name purser -f values.yaml
```
> **Tip**: You can use the default [values.yaml](values.yaml)
================================================
FILE: cluster/helm/chart/purser/templates/NOTES.txt
================================================
1. Get the application URL by running these commands:
{{- if .Values.ui.ingress.enabled }}
{{- range $host := .Values.ui.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ui.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.ui.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "purser.fullname" . }}-ui)
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.ui.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "purser.fullname" . }}-ui'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "purser.fullname" . }}-ui -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.ui.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "purser.name" . }}-ui,app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80
{{- end }}
================================================
FILE: cluster/helm/chart/purser/templates/_helpers.tpl
================================================
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "purser.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "purser.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "purser.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
================================================
FILE: cluster/helm/chart/purser/templates/purser-controller-deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "purser.fullname" . }}-controller
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}-controller
helm.sh/chart: {{ include "purser.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
replicas: {{ .Values.controller.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "purser.name" . }}-controller
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}-controller
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceAccountName: {{ include "purser.fullname" . }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.controller.image.repository }}:{{ .Values.controller.image.tag }}"
imagePullPolicy: {{ .Values.controller.image.pullPolicy }}
command:
- "/controller"
args:
- "--cookieKey=purser-super-secret-key"
- "--cookieName=purser-session-token"
- "--log=info"
{{- if .Values.controller.interactions }}
- "--interactions=enable"
{{- else }}
- "--interactions=disable"
{{- end }}
- "--dgraphURL={{ include "purser.fullname" . }}-database"
- "--dgraphPort=9080"
ports:
- name: http
containerPort: 3030
protocol: TCP
resources:
{{- toYaml .Values.controller.resources | nindent 12 }}
initContainers:
- name: init-sleep
image: "{{ .Values.controller.image.repository }}:{{ .Values.controller.image.tag }}"
command: ["/usr/bin/bash", "-c", "sleep 60"]
{{- with .Values.controller.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.controller.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.controller.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
================================================
FILE: cluster/helm/chart/purser/templates/purser-controller-rbac.yaml
================================================
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: {{ include "purser.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}
helm.sh/chart: {{ include "purser.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
rules:
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "watch", "list", "update", "create", "delete"]
- apiGroups: ["vmware.purser.com"]
resources: ["groups", "subscribers"]
verbs: ["get", "watch", "list", "update", "create", "delete"]
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "watch", "list"]
{{- if .Values.controller.interaction }}
- apiGroups: ["*"]
resources: ["pods/exec"]
verbs: ["create"]
{{- end }}
---
# ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: {{ include "purser.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}
helm.sh/chart: {{ include "purser.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "purser.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "purser.fullname" . }}
namespace: {{ .Release.Namespace }}
================================================
FILE: cluster/helm/chart/purser/templates/purser-controller-serviceaccount.yaml
================================================
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "purser.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}
helm.sh/chart: {{ include "purser.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
================================================
FILE: cluster/helm/chart/purser/templates/purser-controller-svc.yaml
================================================
apiVersion: v1
kind: Service
metadata:
name: {{ include "purser.fullname" . }}-controller
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}
helm.sh/chart: {{ include "purser.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
type: {{ .Values.controller.service.type }}
ports:
- port: 3030
targetPort: http
protocol: TCP
selector:
app.kubernetes.io/name: {{ include "purser.name" . }}-controller
app.kubernetes.io/instance: {{ .Release.Name }}
================================================
FILE: cluster/helm/chart/purser/templates/purser-database-statefulset.yaml
================================================
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "purser.fullname" . }}-database
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}-database
helm.sh/chart: {{ include "purser.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
serviceName: "dgraph"
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ include "purser.name" . }}-database
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}-database
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: {{ .Chart.Name }}-zero
image: "{{ .Values.database.zero.image.repository }}:{{ .Values.database.zero.image.tag }}"
imagePullPolicy: {{ .Values.database.zero.image.pullPolicy }}
ports:
- containerPort: 5080
name: zero-grpc
- containerPort: 6080
name: zero-http
volumeMounts:
- name: datadir
mountPath: /dgraph
command:
- bash
- "-c"
- |
set -ex
dgraph zero --my=0.0.0.0:5080
resources:
{{- toYaml .Values.database.zero.resources | nindent 12 }}
- name: {{ .Chart.Name }}-server
image: "{{ .Values.database.zero.image.repository }}:{{ .Values.database.zero.image.tag }}"
imagePullPolicy: {{ .Values.database.zero.image.pullPolicy }}
ports:
- containerPort: 8080
name: server-http
- containerPort: 9080
name: server-grpc
volumeMounts:
- name: datadir
mountPath: /dgraph
command:
- bash
- "-c"
- |
set -ex
dgraph server --my=0.0.0.0:7080 --lru_mb 2048 --zero 0.0.0.0:5080
resources:
{{- toYaml .Values.database.server.resources | nindent 12 }}
terminationGracePeriodSeconds: 60
volumes:
- name: datadir
persistentVolumeClaim:
claimName: datadir
{{- with .Values.database.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.database.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.database.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumeClaimTemplates:
- metadata:
name: datadir
annotations:
volume.alpha.kubernetes.io/storage-class: anything
spec:
accessModes:
- "ReadWriteOnce"
resources:
requests:
storage: {{ .Values.database.storage | default "10Gi" }}
================================================
FILE: cluster/helm/chart/purser/templates/purser-database-svc.yaml
================================================
apiVersion: v1
kind: Service
metadata:
name: {{ include "purser.fullname" . }}-database
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}
helm.sh/chart: {{ include "purser.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
type: {{ .Values.database.service.type }}
ports:
- port: 5080
targetPort: 5080
name: zero-grpc
- port: 6080
targetPort: 6080
name: zero-http
- port: 8080
targetPort: 8080
name: server-http
- port: 9080
targetPort: 9080
name: server-grpc
selector:
app.kubernetes.io/name: {{ include "purser.name" . }}-database
app.kubernetes.io/instance: {{ .Release.Name }}
================================================
FILE: cluster/helm/chart/purser/templates/purser-ui-configmap.yaml
================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "purser.fullname" . }}-ui
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}-ui
helm.sh/chart: {{ include "purser.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
data:
nginx.conf: |
upstream purser {
server {{ include "purser.fullname" . }}-controller:3030;
}
server {
listen 4200;
location /auth {
proxy_pass http://purser;
}
location /api {
proxy_pass http://purser;
}
location / {
root /usr/share/nginx/html/purser;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
}
================================================
FILE: cluster/helm/chart/purser/templates/purser-ui-deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "purser.fullname" . }}-ui
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}-ui
helm.sh/chart: {{ include "purser.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
replicas: {{ .Values.ui.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "purser.name" . }}-ui
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}-ui
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
volumes:
- configMap:
defaultMode: 420
name: {{ include "purser.fullname" . }}-ui
name: nginx
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.ui.image.repository }}:{{ .Values.ui.image.tag }}"
imagePullPolicy: {{ .Values.ui.image.pullPolicy }}
ports:
- name: http
containerPort: 4200
protocol: TCP
volumeMounts:
- mountPath: /etc/nginx/conf.d
name: nginx
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.ui.resources | nindent 12 }}
{{- with .Values.ui.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.ui.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.ui.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
================================================
FILE: cluster/helm/chart/purser/templates/purser-ui-ingress.yaml
================================================
{{- if .Values.ui.ingress.enabled -}}
{{- $fullName := include "purser.fullname" . -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}
helm.sh/chart: {{ include "purser.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- with .Values.ui.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ui.ingress.tls }}
tls:
{{- range .Values.ui.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ui.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ . }}
backend:
serviceName: {{ $fullName }}-ui
servicePort: http
{{- end }}
{{- end }}
{{- end }}
================================================
FILE: cluster/helm/chart/purser/templates/purser-ui-svc.yaml
================================================
apiVersion: v1
kind: Service
metadata:
name: {{ include "purser.fullname" . }}-ui
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: {{ include "purser.name" . }}-ui
helm.sh/chart: {{ include "purser.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
type: {{ .Values.ui.service.type }}
ports:
- port: {{ .Values.ui.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: {{ include "purser.name" . }}-ui
app.kubernetes.io/instance: {{ .Release.Name }}
================================================
FILE: cluster/helm/chart/purser/values.yaml
================================================
# Default values for purser.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
nameOverride: ""
fullnameOverride: ""
controller:
replicaCount: 1
interaction: false
image:
repository: kreddyj/controller-amd64
tag: 1.0.2
pullPolicy: Always
service:
type: ClusterIP
port: 80
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
database:
replicaCount: 1
# Storage space given to dgraph
storage: 10Gi
service:
type: ClusterIP
zero:
image:
repository: dgraph/dgraph
tag: v1.0.9
pullPolicy: IfNotPresent
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
server:
image:
repository: dgraph/dgraph
tag: v1.0.9
pullPolicy: IfNotPresent
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
ui:
replicaCount: 1
image:
repository: kreddyj/purser-ui
tag: 1.0.2
pullPolicy: Always
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths: []
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
================================================
FILE: cluster/minimal/purser-controller-setup.yaml
================================================
# Service account
apiVersion: v1
kind: ServiceAccount
metadata:
name: purser-service-account
---
# RBAC
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: purser-permissions
rules:
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "watch", "list", "update", "create", "delete"]
- apiGroups: ["vmware.purser.com"]
resources: ["groups", "subscribers"]
verbs: ["get", "watch", "list", "update", "create", "delete"]
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "watch", "list"]
# Uncomment next three lines to enable interactions feature.
# - apiGroups: ["*"]
# resources: ["pods/exec"]
# verbs: ["create"]
---
# ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: purser-cluster-role
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: purser-permissions
subjects:
- kind: ServiceAccount
name: purser-service-account
namespace: purser
---
apiVersion: v1
kind: Service
metadata:
name: purser
spec:
selector:
app: purser
ports:
- protocol: TCP
port: 3030
targetPort: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: purser
spec:
selector:
matchLabels:
app: purser
replicas: 1
template:
metadata:
labels:
app: purser
spec:
serviceAccountName: purser-service-account
containers:
- name: purser-controller
image: kreddyj/purser:controller-1.0.2
imagePullPolicy: Always
ports:
- name: http
containerPort: 3030
command: ["/controller"]
args: ["--log=info", "--interactions=disable", "--dgraphURL=purser-db", "--dgraphPort=9080"]
================================================
FILE: cluster/minimal/purser-database-setup.yaml
================================================
# Service Dgraph - This is the service that should be used by the clients of Dgraph to talk to the server.
apiVersion: v1
kind: Service
metadata:
name: purser-db
labels:
app: purser-db
spec:
type: ClusterIP
ports:
- port: 5080
targetPort: 5080
name: zero-grpc
- port: 6080
targetPort: 6080
name: zero-http
- port: 8080
targetPort: 8080
name: server-http
- port: 9080
targetPort: 9080
name: server-grpc
selector:
app: purser-db
---
# Dgraph StatefulSet runs 1 pod with one Zero and one Server containers.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: purser-dgraph
spec:
serviceName: "dgraph"
replicas: 1
selector:
matchLabels:
app: purser-db
template:
metadata:
labels:
app: purser-db
spec:
containers:
- name: zero
image: dgraph/dgraph:v1.0.9
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5080
name: zero-grpc
- containerPort: 6080
name: zero-http
volumeMounts:
- name: datadir
mountPath: /dgraph
command:
- bash
- "-c"
- |
set -ex
dgraph zero --my=$(hostname -f):5080
- name: server
image: dgraph/dgraph:v1.0.9
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: server-http
- containerPort: 9080
name: server-grpc
volumeMounts:
- name: datadir
mountPath: /dgraph
command:
- bash
- "-c"
- |
set -ex
dgraph server --my=$(hostname -f):7080 --lru_mb 2048 --zero $(hostname -f):5080
terminationGracePeriodSeconds: 60
volumes:
- name: datadir
persistentVolumeClaim:
claimName: datadir
updateStrategy:
type: RollingUpdate
volumeClaimTemplates:
- metadata:
name: datadir
annotations:
volume.alpha.kubernetes.io/storage-class: anything
spec:
accessModes:
- "ReadWriteOnce"
resources:
requests:
storage: 5Gi
================================================
FILE: cluster/minimal/purser-ui-setup.yaml
================================================
apiVersion: v1
kind: Service
metadata:
name: purser-ui
labels:
run: purser-ui
app: purser
spec:
selector:
app: purser
run: purser-ui
ports:
- protocol: TCP
port: 80
targetPort: 4200
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: purser-ui
spec:
selector:
matchLabels:
app: purser
run: purser-ui
replicas: 1
template:
metadata:
labels:
app: purser
run: purser-ui
spec:
containers:
- name: purser-ui
image: kreddyj/purser:ui-1.0.2
imagePullPolicy: Always
ports:
- containerPort: 4200
================================================
FILE: cluster/purser-controller-setup.yaml
================================================
# Service account
apiVersion: v1
kind: ServiceAccount
metadata:
name: purser-service-account
---
# RBAC
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: purser-permissions
rules:
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "watch", "list", "update", "create", "delete"]
- apiGroups: ["vmware.purser.com"]
resources: ["groups", "subscribers"]
verbs: ["get", "watch", "list", "update", "create", "delete"]
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "watch", "list"]
# Uncomment next three lines to enable interactions feature.
# - apiGroups: ["*"]
# resources: ["pods/exec"]
# verbs: ["create"]
---
# ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: purser-cluster-role
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: purser-permissions
subjects:
- kind: ServiceAccount
name: purser-service-account
namespace: purser
---
apiVersion: v1
kind: Service
metadata:
name: purser
spec:
selector:
app: purser
ports:
- protocol: TCP
port: 3030
targetPort: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: purser
spec:
selector:
matchLabels:
app: purser
replicas: 1
template:
metadata:
labels:
app: purser
spec:
serviceAccountName: purser-service-account
containers:
- name: purser-controller
image: kreddyj/purser:controller-1.0.2
imagePullPolicy: Always
resources:
limits:
memory: 1000Mi
cpu: 300m
requests:
memory: 1000Mi
cpu: 300m
ports:
- name: http
containerPort: 3030
command: ["/controller"]
args: ["--log=info", "--interactions=disable", "--dgraphURL=purser-db", "--dgraphPort=9080"]
================================================
FILE: cluster/purser-database-setup.yaml
================================================
# Service Dgraph - This is the service that should be used by the clients of Dgraph to talk to the server.
apiVersion: v1
kind: Service
metadata:
name: purser-db
labels:
app: purser-db
spec:
type: ClusterIP
ports:
- port: 5080
targetPort: 5080
name: zero-grpc
- port: 6080
targetPort: 6080
name: zero-http
- port: 8080
targetPort: 8080
name: server-http
- port: 9080
targetPort: 9080
name: server-grpc
selector:
app: purser-db
---
# Dgraph StatefulSet runs 1 pod with one Zero and one Server containers.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: purser-dgraph
spec:
serviceName: "dgraph"
replicas: 1
selector:
matchLabels:
app: purser-db
template:
metadata:
labels:
app: purser-db
spec:
containers:
- name: zero
image: dgraph/dgraph:v1.0.9
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 1000Mi
cpu: 300m
requests:
memory: 1000Mi
cpu: 300m
ports:
- containerPort: 5080
name: zero-grpc
- containerPort: 6080
name: zero-http
volumeMounts:
- name: datadir
mountPath: /dgraph
command:
- bash
- "-c"
- |
set -ex
dgraph zero --my=$(hostname -f):5080
- name: server
image: dgraph/dgraph:v1.0.9
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 1500Mi
cpu: 500m
requests:
memory: 1500Mi
cpu: 500m
ports:
- containerPort: 8080
name: server-http
- containerPort: 9080
name: server-grpc
volumeMounts:
- name: datadir
mountPath: /dgraph
command:
- bash
- "-c"
- |
set -ex
dgraph server --my=$(hostname -f):7080 --lru_mb 2048 --zero $(hostname -f):5080
terminationGracePeriodSeconds: 60
volumes:
- name: datadir
persistentVolumeClaim:
claimName: datadir
updateStrategy:
type: RollingUpdate
volumeClaimTemplates:
- metadata:
name: datadir
annotations:
volume.alpha.kubernetes.io/storage-class: anything
spec:
accessModes:
- "ReadWriteOnce"
resources:
requests:
storage: 10Gi
================================================
FILE: cluster/purser-ui-setup.yaml
================================================
apiVersion: v1
kind: Service
metadata:
name: purser-ui
labels:
run: purser-ui
app: purser
spec:
selector:
app: purser
run: purser-ui
ports:
- protocol: TCP
port: 80
targetPort: 4200
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: purser-ui
spec:
selector:
matchLabels:
app: purser
run: purser-ui
replicas: 1
template:
metadata:
labels:
app: purser
run: purser-ui
spec:
containers:
- name: purser-ui
image: kreddyj/purser:ui-1.0.2
imagePullPolicy: Always
resources:
limits:
memory: 1200Mi
cpu: 500m
requests:
memory: 1200Mi
cpu: 500m
ports:
- containerPort: 4200
================================================
FILE: cmd/controller/api/api.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package api
import (
"net/http"
"github.com/Sirupsen/logrus"
"github.com/gorilla/handlers"
"github.com/vmware/purser/cmd/controller/api/apiHandlers"
"github.com/vmware/purser/pkg/controller"
)
// StartServer starts api server
func StartServer(conf controller.Config) {
apiHandlers.SetKubeClientAndGroupClient(conf)
allowedOrigins := handlers.AllowedOrigins([]string{"*"})
allowedCredentials := handlers.AllowCredentials()
router := NewRouter()
logrus.Info("Purser server started on port `localhost:3030`")
logrus.Fatal(http.ListenAndServe(":3030", handlers.CORS(allowedOrigins, allowedCredentials)(router)))
}
================================================
FILE: cmd/controller/api/apiHandlers/authenticationHandlers.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apiHandlers
import (
"encoding/json"
"encoding/gob"
"net/http"
"github.com/Sirupsen/logrus"
"github.com/gorilla/sessions"
"github.com/gorilla/securecookie"
"github.com/vmware/purser/pkg/controller/dgraph/models/query"
)
// Credentials structure
type Credentials struct {
Password string `json:"password"`
Username string `json:"username"`
NewPassword string `json:"newPassword"`
}
// User structure
type User struct {
Username string
Authenticated bool
}
var cookieName = "purser-session-token"
var store *sessions.CookieStore
// initialises cookie store
func init() {
authKeyOne := securecookie.GenerateRandomKey(64)
encryptionKeyOne := securecookie.GenerateRandomKey(32)
store = sessions.NewCookieStore(
authKeyOne,
encryptionKeyOne,
)
store.Options = &sessions.Options{
MaxAge: 60 * 15,
HttpOnly: true,
}
gob.Register(User{})
}
// LoginUser listens on /auth/login endpoint
func LoginUser(w http.ResponseWriter, r *http.Request) {
addAccessControlHeaders(&w, r)
var cred Credentials
err := json.NewDecoder(r.Body).Decode(&cred)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if !query.Authenticate(cred.Username, cred.Password) {
logrus.Errorf("wrong credentials")
w.WriteHeader(http.StatusUnauthorized)
return
}
session, err := store.Get(r, cookieName)
if err != nil {
logrus.Errorf("unable to get session from cookie store, err: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
session.Values["user"] = User{
Username: cred.Username,
Authenticated: true,
}
err = session.Save(r, w)
if err != nil {
logrus.Errorf("unable to get session from cookie store, err: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
logrus.Infof("login success")
w.WriteHeader(http.StatusOK)
}
// LogoutUser listens on /auth/logout endpoint
func LogoutUser(w http.ResponseWriter, r *http.Request) {
addAccessControlHeaders(&w, r)
session, err := store.Get(r, cookieName)
if err != nil {
logrus.Errorf("unable to get session from cookie store, err: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
session.Values["user"] = User{}
session.Options.MaxAge = -1
err = session.Save(r, w)
if err != nil {
logrus.Errorf("unable to get session from cookie store, err: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusFound)
}
// TODO: Enhance
func isUserAuthenticated(w http.ResponseWriter, r *http.Request) bool {
return true
}
// ChangePassword listens on /auth/changePassword endpoint
func ChangePassword(w http.ResponseWriter, r *http.Request) {
addAccessControlHeaders(&w, r)
var cred Credentials
err := json.NewDecoder(r.Body).Decode(&cred)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if !query.UpdatePassword(cred.Username, cred.Password, cred.NewPassword) {
w.WriteHeader(http.StatusUnauthorized)
return
}
}
================================================
FILE: cmd/controller/api/apiHandlers/customGroupHandlers.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apiHandlers
import (
"encoding/json"
"github.com/Sirupsen/logrus"
group_v1 "github.com/vmware/purser/pkg/apis/groups/v1"
"github.com/vmware/purser/pkg/controller/dgraph/models"
"github.com/vmware/purser/pkg/controller/dgraph/models/query"
"github.com/vmware/purser/pkg/controller/eventprocessor"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net/http"
)
// GetGroupsData listens on /api/groups endpoint
func GetGroupsData(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
groupsData, err := query.RetrieveGroupsData()
if err != nil {
logrus.Errorf("unable to retrieve groups data from dgraph, %v", err)
} else {
encodeAndWrite(w, groupsData)
}
}
}
// DeleteGroup listens on /api/group/delete
func DeleteGroup(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addAccessControlHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var err error
if name, isName := queryParams[query.Name]; isName {
err = getGroupClient().Delete(name[0], &meta_v1.DeleteOptions{})
if err == nil {
w.WriteHeader(http.StatusOK)
models.DeleteGroup(name[0])
return
}
}
logrus.Errorf("unable to delete: query params: %v, err: %v", queryParams, err)
http.Error(w, err.Error(), http.StatusBadRequest)
}
}
// CreateGroup listens on /api/group/create
func CreateGroup(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addAccessControlHeaders(&w, r)
groupData, err := convertRequestBodyToJSON(r)
if err != nil {
logrus.Errorf("unable to parse request as either JSON or YAML, err: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
newGroup := group_v1.Group{}
if jsonErr := json.Unmarshal(groupData, &newGroup); jsonErr != nil {
logrus.Errorf("unable to parse object as group, err: %v", jsonErr)
http.Error(w, jsonErr.Error(), http.StatusBadRequest)
return
}
if _, groupErr := getGroupClient().Create(&newGroup); groupErr != nil {
logrus.Errorf("unable to create group: %v", groupErr)
http.Error(w, groupErr.Error(), http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
eventprocessor.UpdateGroup(&newGroup, getGroupClient())
}
}
================================================
FILE: cmd/controller/api/apiHandlers/helpers.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apiHandlers
import (
"encoding/json"
"github.com/Sirupsen/logrus"
"io"
"io/ioutil"
"k8s.io/apimachinery/pkg/util/yaml"
"net/http"
"github.com/vmware/purser/pkg/controller"
"github.com/vmware/purser/pkg/client/clientset/typed/groups/v1"
"k8s.io/client-go/kubernetes"
)
var groupClient *v1.GroupClient
var kubeClient *kubernetes.Clientset
func addHeaders(w *http.ResponseWriter, r *http.Request) {
addAccessControlHeaders(w, r)
(*w).Header().Set("Content-Type", "application/json; charset=UTF-8")
(*w).WriteHeader(http.StatusOK)
}
func addAccessControlHeaders(w *http.ResponseWriter, r *http.Request) {
(*w).Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
(*w).Header().Set("Access-Control-Allow-Credentials", "true")
}
func writeBytes(w io.Writer, data []byte) {
_, err := w.Write(data)
if err != nil {
logrus.Errorf("Unable to encode to json: (%v)", err)
}
}
func encodeAndWrite(w io.Writer, obj interface{}) {
err := json.NewEncoder(w).Encode(obj)
if err != nil {
logrus.Errorf("Unable to encode to json: (%v)", err)
}
}
func convertRequestBodyToJSON(r *http.Request) ([]byte, error) {
requestData, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
groupData, err := yaml.ToJSON(requestData)
return groupData, err
}
// SetKubeClientAndGroupClient sets groupcrd client
func SetKubeClientAndGroupClient(conf controller.Config) {
groupClient = conf.Groupcrdclient
kubeClient = conf.Kubeclient
}
func getGroupClient() *v1.GroupClient {
return groupClient
}
func getKubeClient() *kubernetes.Clientset {
return kubeClient
}
================================================
FILE: cmd/controller/api/apiHandlers/hierarchyAndMetricAPIHandlers.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apiHandlers
import (
"encoding/json"
"fmt"
"net/http"
"github.com/Sirupsen/logrus"
"github.com/vmware/purser/pkg/controller/dgraph/models"
"github.com/vmware/purser/pkg/controller/dgraph/models/query"
"github.com/vmware/purser/pkg/controller/discovery/generator"
"github.com/vmware/purser/pkg/controller/eventprocessor"
)
// GetHomePage is the default api home page
func GetHomePage(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
_, err := fmt.Fprintf(w, "Welcome to the Purser!")
if err != nil {
logrus.Errorf("Unable to write welcome message to Homepage: (%v)", err)
}
}
}
// GetPodInteractions listens on /interactions/pod endpoint and returns pod interactions
func GetPodInteractions(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonResp []byte
if name, isName := queryParams[query.Name]; isName {
jsonResp = query.RetrievePodsInteractions(name[0], false)
} else {
if orphanVal, isOrphan := queryParams[query.Orphan]; isOrphan && orphanVal[0] == query.False {
jsonResp = query.RetrievePodsInteractions(query.All, false)
} else {
jsonResp = query.RetrievePodsInteractions(query.All, true)
}
}
writeBytes(w, jsonResp)
}
}
// GetClusterHierarchy listens on /hierarchy endpoint and returns all namespaces(or nodes and PV) in the cluster
func GetClusterHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if view, isView := queryParams[query.View]; isView && view[0] == query.Physical {
jsonData = query.RetrieveClusterHierarchy(query.Physical)
} else {
jsonData = query.RetrieveClusterHierarchy(query.Logical)
}
encodeAndWrite(w, jsonData)
}
}
// GetNamespaceHierarchy listens on /hierarchy/namespace endpoint and returns all children of namespace
func GetNamespaceHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.NamespaceCheck,
Type: query.NamespaceType,
Name: name[0],
ChildFilter: query.NamespaceChildFilter,
}
jsonData = resourceQuery.RetrieveResourceHierarchy()
} else {
jsonData = query.RetrieveClusterHierarchy(query.Logical)
}
encodeAndWrite(w, jsonData)
}
}
// GetDeploymentHierarchy listens on /hierarchy/deployment endpoint and returns all children of deployment
func GetDeploymentHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.DeploymentCheck,
Type: query.DeploymentType,
Name: name[0],
ChildFilter: query.IsReplicasetFilter,
}
jsonData = resourceQuery.RetrieveResourceHierarchy()
} else {
logrus.Errorf("wrong type of query for deployment, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetReplicasetHierarchy listens on /hierarchy/replicaset endpoint and returns all children of replicaset
func GetReplicasetHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.ReplicasetCheck,
Type: query.ReplicasetType,
Name: name[0],
ChildFilter: query.IsPodFilter,
}
jsonData = resourceQuery.RetrieveResourceHierarchy()
} else {
logrus.Errorf("wrong type of query for replicaset, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetStatefulsetHierarchy listens on /hierarchy/statefulset endpoint and returns all children of statefulset
func GetStatefulsetHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.StatefulsetCheck,
Type: query.StatefulsetType,
Name: name[0],
ChildFilter: query.IsPodFilter,
}
jsonData = resourceQuery.RetrieveResourceHierarchy()
} else {
logrus.Errorf("wrong type of query for statefulset, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetPodHierarchy listens on /hierarchy/pod endpoint and returns all children of pod
func GetPodHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.PodCheck,
Type: query.PodType,
Name: name[0],
ChildFilter: query.IsContainerFilter,
}
jsonData = resourceQuery.RetrieveResourceHierarchy()
} else {
logrus.Errorf("wrong type of query for pod, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetContainerHierarchy listens on /hierarchy/container endpoint and returns all children of container
func GetContainerHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.ContainerCheck,
Type: query.ContainerType,
Name: name[0],
ChildFilter: query.IsProcFilter,
}
jsonData = resourceQuery.RetrieveResourceHierarchy()
} else {
logrus.Errorf("wrong type of query for container, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetEmptyHierarchy listens on /hierarchy/process and /hierarchy/pvc endpoint and returns empty data
func GetEmptyHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
encodeAndWrite(w, jsonData)
}
}
// GetNodeHierarchy listens on /hierarchy/node endpoint and returns all children of node
func GetNodeHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.NodeCheck,
Type: query.NodeType,
Name: name[0],
ChildFilter: query.IsPodFilter,
}
jsonData = resourceQuery.RetrieveResourceHierarchy()
} else {
logrus.Errorf("wrong type of query for node, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetPVHierarchy listens on /hierarchy/pv endpoint and returns all children of PV
func GetPVHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.PVCheck,
Type: query.PVType,
Name: name[0],
ChildFilter: query.IsPVCFilter,
}
jsonData = resourceQuery.RetrieveResourceHierarchy()
} else {
logrus.Errorf("wrong type of query for PV, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetDaemonsetHierarchy listens on /hierarchy/daemonset endpoint and returns all children of Daemonset
func GetDaemonsetHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.DaemonsetCheck,
Type: query.DaemonsetType,
Name: name[0],
ChildFilter: query.IsPodFilter,
}
jsonData = resourceQuery.RetrieveResourceHierarchy()
} else {
logrus.Errorf("wrong type of query for Daemonset, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetJobHierarchy listens on /hierarchy/job endpoint and returns all children of Job
func GetJobHierarchy(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.JobCheck,
Type: query.JobType,
Name: name[0],
ChildFilter: query.IsPodFilter,
}
jsonData = resourceQuery.RetrieveResourceHierarchy()
} else {
logrus.Errorf("wrong type of query for Job, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetClusterMetrics listens on /metrics endpoint with option for view(physical or logical)
func GetClusterMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if view, isView := queryParams[query.View]; isView && view[0] == query.Physical {
jsonData = query.RetrieveClusterMetrics(query.Physical)
} else {
jsonData = query.RetrieveClusterMetrics(query.Logical)
}
query.PopulateClusterAllocationAndCapacity(&jsonData)
encodeAndWrite(w, jsonData)
}
}
// GetNamespaceMetrics listens on /metrics/namespace
func GetNamespaceMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.NamespaceCheck,
Type: query.NamespaceType,
Name: name[0],
}
jsonData = resourceQuery.RetrieveResourceMetrics()
} else {
jsonData = query.RetrieveClusterMetrics(query.Logical)
}
query.PopulateClusterAllocationAndCapacity(&jsonData)
encodeAndWrite(w, jsonData)
}
}
// GetDeploymentMetrics listens on /metrics/deployment
func GetDeploymentMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.DeploymentCheck,
Type: query.DeploymentType,
Name: name[0],
}
jsonData = resourceQuery.RetrieveResourceMetrics()
query.PopulateClusterAllocationAndCapacity(&jsonData)
} else {
logrus.Errorf("wrong type of query for deployment, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetDaemonsetMetrics listens on /metrics/daemonset
func GetDaemonsetMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.DaemonsetCheck,
Type: query.DaemonsetType,
Name: name[0],
}
jsonData = resourceQuery.RetrieveResourceMetrics()
query.PopulateClusterAllocationAndCapacity(&jsonData)
} else {
logrus.Errorf("wrong type of query for daemonset, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetJobMetrics listens on /metrics/job
func GetJobMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.JobCheck,
Type: query.JobType,
Name: name[0],
}
jsonData = resourceQuery.RetrieveResourceMetrics()
query.PopulateClusterAllocationAndCapacity(&jsonData)
} else {
logrus.Errorf("wrong type of query for job, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetStatefulsetMetrics listens on /metrics/statefulset
func GetStatefulsetMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.StatefulsetCheck,
Type: query.StatefulsetType,
Name: name[0],
}
jsonData = resourceQuery.RetrieveResourceMetrics()
query.PopulateClusterAllocationAndCapacity(&jsonData)
} else {
logrus.Errorf("wrong type of query for statefulset, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetReplicasetMetrics listens on /metrics/replicaset
func GetReplicasetMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.ReplicasetCheck,
Type: query.ReplicasetType,
Name: name[0],
}
jsonData = resourceQuery.RetrieveResourceMetrics()
query.PopulateClusterAllocationAndCapacity(&jsonData)
} else {
logrus.Errorf("wrong type of query for statefulset, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetNodeMetrics listens on /metrics/node
func GetNodeMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.NodeCheck,
Type: query.NodeType,
Name: name[0],
}
jsonData = resourceQuery.RetrieveResourceMetrics()
resourceQuery.PopulateNodeOrPVAllocationAndCapacity(&jsonData)
} else {
logrus.Errorf("wrong type of query for node, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetPodMetrics listens on /metrics/pod
func GetPodMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.PodCheck,
Type: query.PodType,
Name: name[0],
}
jsonData = resourceQuery.RetrieveResourceMetrics()
query.PopulateClusterAllocationAndCapacity(&jsonData)
} else {
logrus.Errorf("wrong type of query for pod, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetContainerMetrics listens on /metrics/container
func GetContainerMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.ContainerCheck,
Type: query.ContainerType,
Name: name[0],
}
jsonData = resourceQuery.RetrieveResourceMetrics()
query.PopulateClusterAllocationAndCapacity(&jsonData)
} else {
logrus.Errorf("wrong type of query for container, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetPVMetrics listens on /metrics/pv
func GetPVMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.PVCheck,
Type: query.PVType,
Name: name[0],
}
jsonData = resourceQuery.RetrieveResourceMetrics()
resourceQuery.PopulateNodeOrPVAllocationAndCapacity(&jsonData)
} else {
logrus.Errorf("wrong type of query for PV, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetPVCMetrics listens on /metrics/pvc
func GetPVCMetrics(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
addHeaders(&w, r)
queryParams := r.URL.Query()
logrus.Debugf("Query params: (%v)", queryParams)
var jsonData query.JSONDataWrapper
if name, isName := queryParams[query.Name]; isName {
resourceQuery := query.Resource{
Check: query.PVCCheck,
Type: query.PVCType,
Name: name[0],
}
jsonData = resourceQuery.RetrieveResourceMetrics()
query.PopulateClusterAllocationAndCapacity(&jsonData)
} else {
logrus.Errorf("wrong type of query for PVC, no name is given")
}
encodeAndWrite(w, jsonData)
}
}
// GetPodDiscoveryNodes listens on /discovery/pod/nodes endpoint
func GetPodDiscoveryNodes(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
var pods []models.Pod
var err error
addHeaders(&w, r)
pods, err = query.RetrievePodsInteractionsForAllLivePodsWithCount()
generator.GeneratePodNodesAndEdges(pods)
if err != nil {
logrus.Errorf("Unable to get response: (%v)", err)
}
nodes := generator.GetGraphNodes()
if nodes != nil {
logrus.Infof("No nodes found")
return
}
err = json.NewEncoder(w).Encode(nodes)
if err != nil {
logrus.Errorf("Unable to encode to json: (%v)", err)
}
}
}
// GetPodDiscoveryEdges listens on /discovery/pod/edges endpoint
func GetPodDiscoveryEdges(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
var err error
addHeaders(&w, r)
edges := generator.GetGraphEdges()
if edges == nil {
logrus.Infof("No edges found")
return
}
err = json.NewEncoder(w).Encode(edges)
if err != nil {
logrus.Errorf("Unable to encode to json: (%v)", err)
}
}
}
// SyncCluster listens on /api/sync
func SyncCluster(w http.ResponseWriter, r *http.Request) {
if isUserAuthenticated(w, r) {
w.WriteHeader(http.StatusAccepted)
go syncResourcesInCluster()
}
}
func syncResourcesInCluster() {
eventprocessor.SyncCluster(getKubeClient())
eventprocessor.UpdateGroups(getGroupClient())
query.ComputeClusterAllocationAndCapacity()
}
================================================
FILE: cmd/controller/api/logger.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package api
import (
"net/http"
"time"
"github.com/Sirupsen/logrus"
)
// Logger implements web logging logic
func Logger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner.ServeHTTP(w, r)
logrus.Infof(
"%s\t%s\t%s\t%s",
r.Method,
r.RequestURI,
name,
time.Since(start),
)
})
}
================================================
FILE: cmd/controller/api/router.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package api
import (
"github.com/gorilla/mux"
)
// NewRouter returns a new instance of the router
func NewRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
for _, route := range routes {
handlerFunc := route.HandlerFunc
handler := Logger(handlerFunc, route.Name)
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(handler)
}
return router
}
================================================
FILE: cmd/controller/api/routes.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package api
import (
"github.com/vmware/purser/cmd/controller/api/apiHandlers"
"net/http"
)
// Route structure
type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}
// Routes list
type Routes []Route
var routes = Routes{
Route{
"GetHomePage",
"GET",
"/api",
apiHandlers.GetHomePage,
},
Route{
"GetPodInteractions",
"GET",
"/api/interactions/pod",
apiHandlers.GetPodInteractions,
},
Route{
"GetClusterHierarchy",
"GET",
"/api/hierarchy",
apiHandlers.GetClusterHierarchy,
},
Route{
"GetNamespaceHierarchy",
"GET",
"/api/hierarchy/namespace",
apiHandlers.GetNamespaceHierarchy,
},
Route{
"GetDeploymentHierarchy",
"GET",
"/api/hierarchy/deployment",
apiHandlers.GetDeploymentHierarchy,
},
Route{
"GetReplicasetHierarchy",
"GET",
"/api/hierarchy/replicaset",
apiHandlers.GetReplicasetHierarchy,
},
Route{
"GetStatefulsetHierarchy",
"GET",
"/api/hierarchy/statefulset",
apiHandlers.GetStatefulsetHierarchy,
},
Route{
"GetPodHierarchy",
"GET",
"/api/hierarchy/pod",
apiHandlers.GetPodHierarchy,
},
Route{
"GetContainerHierarchy",
"GET",
"/api/hierarchy/container",
apiHandlers.GetContainerHierarchy,
},
Route{
"GetProcessHierarchy",
"GET",
"/api/hierarchy/process",
apiHandlers.GetEmptyHierarchy,
},
Route{
"GetNodeHierarchy",
"GET",
"/api/hierarchy/node",
apiHandlers.GetNodeHierarchy,
},
Route{
"GetPVHierarchy",
"GET",
"/api/hierarchy/pv",
apiHandlers.GetPVHierarchy,
},
Route{
"GetPVCHierarchy",
"GET",
"/api/hierarchy/pvc",
apiHandlers.GetEmptyHierarchy,
},
Route{
"GetDaemonsetHierarchy",
"GET",
"/api/hierarchy/daemonset",
apiHandlers.GetDaemonsetHierarchy,
},
Route{
"GetJobHierarchy",
"GET",
"/api/hierarchy/job",
apiHandlers.GetJobHierarchy,
},
Route{
"GetClusterMetrics",
"GET",
"/api/metrics",
apiHandlers.GetClusterMetrics,
},
Route{
"GetNamespaceMetrics",
"GET",
"/api/metrics/namespace",
apiHandlers.GetNamespaceMetrics,
},
Route{
"GetDeploymentMetrics",
"GET",
"/api/metrics/deployment",
apiHandlers.GetDeploymentMetrics,
},
Route{
"GetDaemonsetMetrics",
"GET",
"/api/metrics/daemonset",
apiHandlers.GetDaemonsetMetrics,
},
Route{
"GetJobMetrics",
"GET",
"/api/metrics/job",
apiHandlers.GetJobMetrics,
},
Route{
"GetStatefulsetMetrics",
"GET",
"/api/metrics/statefulset",
apiHandlers.GetStatefulsetMetrics,
},
Route{
"GetReplicasetMetrics",
"GET",
"/api/metrics/replicaset",
apiHandlers.GetReplicasetMetrics,
},
Route{
"GetNodeMetrics",
"GET",
"/api/metrics/node",
apiHandlers.GetNodeMetrics,
},
Route{
"GetPodMetrics",
"GET",
"/api/metrics/pod",
apiHandlers.GetPodMetrics,
},
Route{
"GetContainerMetrics",
"GET",
"/api/metrics/container",
apiHandlers.GetContainerMetrics,
},
Route{
"GetPVMetrics",
"GET",
"/api/metrics/pv",
apiHandlers.GetPVMetrics,
},
Route{
"GetPVCMetrics",
"GET",
"/api/metrics/pvc",
apiHandlers.GetPVCMetrics,
},
Route{
"GetPodDiscoveryNodes",
"GET",
"/api/nodes",
apiHandlers.GetPodDiscoveryNodes,
},
Route{
"GetPodDiscoveryEdges",
"GET",
"/api/edges",
apiHandlers.GetPodDiscoveryEdges,
},
Route{
"GetGroupsData",
"GET",
"/api/groups",
apiHandlers.GetGroupsData,
},
Route{
"Login",
"POST",
"/auth/login",
apiHandlers.LoginUser,
},
Route{
"Logout",
"POST",
"/auth/logout",
apiHandlers.LogoutUser,
},
Route{
"ChangePassword",
"POST",
"/auth/changePassword",
apiHandlers.ChangePassword,
},
Route{
"DeleteGroup",
"POST",
"/api/group/delete",
apiHandlers.DeleteGroup,
},
Route{
"CreateGroup",
"POST",
"/api/group/create",
apiHandlers.CreateGroup,
},
Route{
"SyncCluster",
"GET",
"/api/sync",
apiHandlers.SyncCluster,
},
}
================================================
FILE: cmd/controller/config/config.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package config
import (
"sync"
log "github.com/Sirupsen/logrus"
"github.com/vmware/purser/pkg/client"
group_client "github.com/vmware/purser/pkg/client/clientset/typed/groups/v1"
subscriber_client "github.com/vmware/purser/pkg/client/clientset/typed/subscriber/v1"
"github.com/vmware/purser/pkg/controller"
"github.com/vmware/purser/pkg/controller/buffering"
"github.com/vmware/purser/pkg/utils"
)
// Setup initialzes the controller configuration
func Setup(conf *controller.Config, kubeconfig string) {
var err error
*conf = controller.Config{}
conf.KubeConfig, err = utils.GetKubeconfig(kubeconfig)
if err != nil {
log.Fatal(err)
}
conf.Kubeclient = utils.GetKubeclient(conf.KubeConfig)
conf.Resource = controller.Resource{
Pod: true,
Node: true,
PersistentVolume: true,
PersistentVolumeClaim: true,
ReplicaSet: true,
Deployment: true,
StatefulSet: true,
DaemonSet: true,
Job: true,
Service: true,
Namespace: true,
Group: true,
Subscriber: true,
}
conf.RingBuffer = &buffering.RingBuffer{Size: buffering.BufferSize, Mutex: &sync.Mutex{}}
clientset, clusterConfig := client.GetAPIExtensionClient(kubeconfig)
conf.Groupcrdclient = group_client.NewGroupClient(clientset, clusterConfig)
conf.Subscriberclient = subscriber_client.NewSubscriberClient(clientset, clusterConfig)
}
================================================
FILE: cmd/controller/purserctrl.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package main
import (
"flag"
"time"
"github.com/vmware/purser/pkg/controller/dgraph/models/query"
"github.com/vmware/purser/pkg/pricing"
log "github.com/Sirupsen/logrus"
"github.com/robfig/cron"
"github.com/vmware/purser/cmd/controller/api"
"github.com/vmware/purser/cmd/controller/config"
"github.com/vmware/purser/pkg/controller"
"github.com/vmware/purser/pkg/controller/dgraph"
"github.com/vmware/purser/pkg/controller/discovery/processor"
"github.com/vmware/purser/pkg/controller/eventprocessor"
"github.com/vmware/purser/pkg/utils"
)
var conf controller.Config
// InClusterConfigPath should be empty to get client and config for InCluster environment.
const InClusterConfigPath = ""
var interactions *string
func init() {
logLevel := flag.String("log", "info", "set log level as info or debug")
dgraphURL := flag.String("dgraphURL", "purser-db", "dgraph zero url")
dgraphPort := flag.String("dgraphPort", "9080", "dgraph zero port")
interactions = flag.String("interactions", "disable", "enable discovery of interactions")
kubeconfig := flag.String("kubeconfig", InClusterConfigPath, "path to the kubeconfig file")
flag.Parse()
utils.InitializeLogger(*logLevel)
config.Setup(&conf, *kubeconfig)
// start dgraph and create login if not exists
dgraph.Start(*dgraphURL, *dgraphPort)
dgraph.StoreLogin()
}
func main() {
go api.StartServer(conf)
go startCronJobForPopulatingRateCard()
time.Sleep(time.Minute * 3)
go eventprocessor.ProcessEvents(&conf)
if *interactions == "enable" {
go startInteractionsDiscovery()
}
go startCronJobForUpdatingCustomGroups()
controller.Start(&conf)
}
// starts first discovery after 5 min of controller starting. Next runs will occur in every 59 min
func startInteractionsDiscovery() {
time.Sleep(time.Minute * 5)
runDiscovery()
c := cron.New()
err := c.AddFunc("@every 0h59m", runDiscovery)
if err != nil {
log.Error(err)
}
err = c.AddFunc("@daily", dgraph.RemoveResourcesInactive)
if err != nil {
log.Error(err)
}
c.Start()
}
func runDiscovery() {
processor.ProcessPodInteractions(conf)
processor.ProcessServiceInteractions(conf)
}
func startCronJobForUpdatingCustomGroups() {
query.ComputeClusterAllocationAndCapacity()
runGroupUpdate()
c := cron.New()
err := c.AddFunc("@every 0h5m", runGroupUpdate)
if err != nil {
log.Error(err)
}
err = c.AddFunc("@every 0h5m", query.ComputeClusterAllocationAndCapacity)
if err != nil {
log.Error(err)
}
c.Start()
}
func runGroupUpdate() {
eventprocessor.UpdateGroups(conf.Groupcrdclient)
}
func startCronJobForPopulatingRateCard() {
cloud := &pricing.Cloud{Kubeclient: conf.Kubeclient}
// find cloud provider and region
cloud.CloudProvider, cloud.Region = pricing.GetClusterProviderAndRegion()
cloud.PopulateRateCard()
c := cron.New()
err := c.AddFunc("@every 168h", cloud.PopulateRateCard)
if err != nil {
log.Error(err)
}
c.Start()
}
================================================
FILE: cmd/plugin/purser.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package main
import (
"flag"
"fmt"
"os"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/vmware/purser/pkg/client"
groups_client_v1 "github.com/vmware/purser/pkg/client/clientset/typed/groups/v1"
"github.com/vmware/purser/pkg/plugin"
"github.com/vmware/purser/pkg/utils"
)
const (
pluginVersion = "version v1.0.0"
)
var (
groupClient *groups_client_v1.GroupClient
// Variables used for cmd interface
kubeconfig string
info string
version string
description = fmt.Sprintf("Purser gives cost insights of kubernetes deployments.\n\n")
usage = fmt.Sprintf("Usage:\n kubectl plugin purser [options] <command> <args>\n\n")
supportedCmds = fmt.Sprintf("The supported commands are:\n get Get resource information.\n set Set resource information.\n\n")
optionHelp = fmt.Sprintf("\n --info Show more details about the plugin.")
optionKubeConfig = fmt.Sprintf("\n --kubeconfig Absolute path for the kube config file.")
optionVersion = fmt.Sprintf("\n --version Show plugin version.")
options = fmt.Sprintf("options:%s%s%s\n\n", optionHelp, optionKubeConfig, optionVersion)
kubecltOption = fmt.Sprintf("\nUse \"kubectl options\" for a list of global command-line options (applies to all commands).\n\n")
)
func init() {
flag.StringVar(&kubeconfig, "kubeconfig", os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_KUBECONFIG"), "path to Kubernetes config file")
flag.StringVar(&info, "info", os.Getenv("KUBECTL_PLUGINS_LOCAL_FLAG_INFO"), "Show help documentation")
flag.StringVar(&version, "version", os.Getenv("KUBECTL_PLUGINS_LOCAL_FLAG_VERSION"), "Show version number")
flag.Usage = func() {
_, err := fmt.Fprint(flag.CommandLine.Output(), description)
if err != nil {
log.Fatal(err)
}
_, err = fmt.Fprint(flag.CommandLine.Output(), usage)
if err != nil {
log.Fatal(err)
}
_, err = fmt.Fprint(flag.CommandLine.Output(), supportedCmds)
if err != nil {
log.Fatal(err)
}
_, err = fmt.Fprint(flag.CommandLine.Output(), options)
if err != nil {
log.Fatal(err)
}
_, err = fmt.Fprint(flag.CommandLine.Output(), "Example(s):\n\n")
if err != nil {
log.Fatal(err)
}
printHelp()
_, err = fmt.Fprint(flag.CommandLine.Output(), kubecltOption)
if err != nil {
log.Fatal(err)
}
}
if version != "" {
fmt.Println(pluginVersion)
os.Exit(0)
}
if info != "" {
flag.Usage()
os.Exit(0)
}
config, err := utils.GetKubeconfig(kubeconfig)
if err != nil {
log.Fatal(err)
}
plugin.ProvideClientSetInstance(utils.GetKubeclient(config))
client, clusterConfig := client.GetAPIExtensionClient(kubeconfig)
groupClient = groups_client_v1.NewGroupClient(client, clusterConfig)
}
func main() {
inputs := os.Args[2:] // index 1 is empty
if len(inputs) == 4 && inputs[0] == Get {
computeMetricInsight(inputs)
} else if len(inputs) == 2 {
computeStats(inputs)
} else {
printHelp()
}
}
func computeMetricInsight(inputs []string) {
switch inputs[1] {
case Cost:
computeCost(inputs)
case Resources:
fetchResource(inputs)
}
}
func computeCost(inputs []string) {
switch inputs[2] {
case Label:
plugin.GetPodsCostForLabel(inputs[3])
case Pod:
plugin.GetPodCost(inputs[3])
case Node:
plugin.GetAllNodesCost()
default:
printHelp()
}
}
func fetchResource(inputs []string) {
switch inputs[2] {
case Namespace:
group := plugin.GetGroupByName(groupClient, inputs[3])
if group != nil {
plugin.PrintGroup(group)
} else {
fmt.Printf("Group %s is not present\n", inputs[3])
}
case Label:
if !strings.Contains(inputs[3], "=") {
printHelp()
}
group := plugin.GetGroupByName(groupClient, createGroupNameFromLabel(inputs[3]))
if group != nil {
plugin.PrintGroup(group)
} else {
fmt.Printf("Group %s is not present\n", inputs[3])
}
case Group:
group := plugin.GetGroupByName(groupClient, inputs[3])
if group != nil {
plugin.PrintGroup(group)
} else {
fmt.Printf("No group with name: %s\n", inputs[3])
}
default:
printHelp()
}
}
func createGroupNameFromLabel(input string) string {
inp := strings.Split(input, "=")
key, val := inp[0], inp[1]
groupName := key + "." + val
if strings.Contains(groupName, "/") {
groupName = strings.Replace(groupName, "/", "-", -1)
}
return strings.ToLower(groupName)
}
func computeStats(inputs []string) {
switch inputs[0] {
case Get:
getStats(inputs)
case Set:
inputUserCosts(inputs)
default:
printHelp()
}
}
func getStats(inputs []string) {
switch inputs[1] {
case "summary":
plugin.GetClusterSummary()
case "savings":
plugin.GetSavings()
case "user-costs":
price := plugin.GetUserCosts()
fmt.Printf("cpu cost per CPU per hour:\t %f$\nmem cost per GB per hour:\t %f$\nstorage cost per GB per hour:\t %f$\n",
price.CPU,
price.Memory,
price.Storage)
default:
printHelp()
}
}
func inputUserCosts(inputs []string) {
if inputs[1] == "user-costs" {
fmt.Printf("Enter CPU cost per cpu per hour:\t ")
var cpuCostPerCPUPerHour string
_, err := fmt.Scan(&cpuCostPerCPUPerHour)
logError(err)
fmt.Printf("Enter Memory cost per GB per hour:\t ")
var memCostPerGBPerHour string
_, err = fmt.Scan(&memCostPerGBPerHour)
logError(err)
fmt.Printf("Enter Storage cost per GB per hour:\t ")
var storageCostPerGBPerHour string
_, err = fmt.Scan(&storageCostPerGBPerHour)
logError(err)
plugin.SaveUserCosts(cpuCostPerCPUPerHour, memCostPerGBPerHour, storageCostPerGBPerHour)
} else {
printHelp()
}
}
func printHelp() {
pluginExt := "kubectl --kubeconfig=<absolute path to config> plugin purser "
fmt.Println("Try one of the following commands...")
fmt.Println(pluginExt + "get summary")
fmt.Println(pluginExt + "get resources group <group-name>")
fmt.Println(pluginExt + "get cost label <key=val>")
fmt.Println(pluginExt + "get cost pod <pod name>")
fmt.Println(pluginExt + "get cost node all")
fmt.Println(pluginExt + "set user-costs")
fmt.Println(pluginExt + "get user-costs")
fmt.Println(pluginExt + "get savings")
}
func logError(err error) {
if err != nil {
log.Printf("failed to read user input %+v", err)
}
}
================================================
FILE: cmd/plugin/types.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package main
// These are possible actions for resources
const (
Get = "get"
Set = "set"
)
// These are kubernetes components
const (
Label = "label"
Pod = "pod"
Node = "node"
Namespace = "namespace"
Group = "group"
)
// These are utilisation metrics
const (
Cost = "cost"
Resources = "resources"
)
================================================
FILE: docs/architecture.md
================================================
# Architecture of Purser
The following diagram represents the architecture of Purser.

The following are the main componenets installed in Kubernetes for Purser.
1. **Kubernetes API Server**
All the Purser `kubectl` commands hit the API server extension. These APIs understand the input command, compute and return the required output.
2. **Custom Controller**
The custom controller watches for changes in state of pods, nodes, persistent volumes, etc. and update the inventory in CRDs.
3. **Custom Resource Definitions(CRDs)**
Custom Resource Definitions are like any other resource(Pod, Node, etc.) and store the config data like `Group Definitions` and inventory.
4. **Metric Store**
Metric store is used to store the utilization, allocation metrics of inventory and also calculated costs.
5. **CRON Job**
CRON Job collects the stats of inventory and calculates the cost periodically and stores in Metric Store.
## Work Flow
1. Purser installation steps create Custom Controller, CRON Job and CRDs in Kubernetes.
2. Once installed the custom controller collects all the inventory(pods, nodes, pv, etc.) and stores in CRDs, later it watches for any changes in inventory and stores the changes in CRDs.
3. CRON Job kicks in periodically and collect the stats and stores the stats in metric store. CRON Job also calculates the Costs in the same cycle and stores them in the metric store.
4. Any `kubectl` command invocations are received by Kubernetes API server extension. APIs then process the required output based on the configurations(for groups), inventory, costs metrics and returns to the user.
================================================
FILE: docs/custom-group-installation-and-usage.md
================================================
# Custom Group Installation and Usage
To get resource and cost visibility for a particular set of pods Purser allows user to create custom logical group.
User can define the label filter logic(`AND of ORs`: Conjunctive normal form) while creating the logical group i.e, pods satisfying these conditions will belong to this custom group.
## Installing logical group definition and an example logical group
To install the logical group definition into your cluster,
download [purser-group-crd.yaml](../cluster/artifacts/purser-group-crd.yaml) yaml i.e,
```yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: groups.vmware.purser.com
spec:
group: vmware.purser.com
names:
kind: Group
listKind: GroupList
plural: groups
singular: group
scope: Namespaced
version: v1
status:
acceptedNames:
kind: Group
listKind: GroupList
plural: groups
singular: group
```
and use kubectl to install this definition
```bash
kubectl create -f purser-group-crd.yaml
```
_**NOTE:** This installation is needed only once per cluster_
**Installing an example logical group**
Download [example-group.yaml](../cluster/artifacts/example-group.yaml) yaml i.e,
```yaml
apiVersion: vmware.purser.com/v1
kind: Group
metadata:
name: example-group
spec:
name: example-group
labels:
expr1:
app:
- sample-app
- sample-app2
env:
- dev
expr2:
namespace:
- ns1
- ns2
expr3:
key1:
- val1
key2:
- val2
```
and use kubectl to create this logical group
```bash
kubectl create -f example-group.yaml
kubectl get groups.vmware.purser.com
```
This will create a custom logical group with name `example-group` of type `groups.vmware.purser.com`.
The label filter (used to fetch pods belonging to this group) for `example-group` will be
```yaml
(app=sampl-app OR app=sample-app2 OR env=dev) AND (namespace=ns1 OR namespace=ns2) AND (key1=val1 OR key2=val2)
```
In general the syntax purser supports is:
```
expr1 AND expr2 AND expr3 AND ...
where each expr is of form key1:value1 OR key2:value2 OR key1:value3 OR ...
```
## Usage
For resource and cost visibility into this newly created logical group run the following command
```bash
kubectl plugin purser get resources group example-group
```
_Refer [purser installation](../README.md#installation) to install purser controller and plugin_
## Uninstalling purser custom group
To uninstall purser custom group run the following command
```bash
kubectl delete -f purser-group-crd.yaml
```
where [purser-group-crd.yaml](../cluster/artifacts/purser-group-crd.yaml) is same file that you downloaded during installation.
================================================
FILE: docs/design/pricing.md
================================================
# Pricing in Purser
## User defined pricing
Currently purser supports user defined pricing for cpu, memory and storage resources per hour. The default pricing for the resources are
* CPU: 0.024$ per vCPU per Hour
* Memory: 0.01$ per GB per Hour
* Storage: 0.00013888888$ per GB per Hour
These default pricing for cpu and memory have been set by taking average [prices](https://aws.amazon.com/ec2/pricing/on-demand/) in AWS ec2 instances.
For storage we set pricing proportional to 0.1$ per GB per month referring to AWS [ebs pricing](https://aws.amazon.com/ebs/pricing/).
User can edit these pricing using purser plugin: `kubectl plugin purser set user-costs`
_(Future work)_ Option to edit these default pricing in UI.
## Using node labels for accurate pricing (WIP)
Data needed to get correct pricing of node:
* Cloud Provider (AWS, GCE etc)
* Region (us-east etc)
* Machine Type (t2.micro, m4.large)
* Operating system (linux, windows etc)
* Rate card which gives cost of node depending on above data
* _(Future work)_ Costing based on instance type i.e., "Is the instance On-demand or Spot-Instance or Reserved-Instance etc?"
* _(Future work)_ Discounts
### Getting cloud provider, region and machine type
Kubelet populates few [reserved labels](https://kubernetes.io/docs/reference/kubernetes-api/labels-annotations-taints/#beta-kubernetes-io-instance-type) on nodes. Using these labels we can determine region, machineType and operating system.
Command `kubectl describe node <nodeName>` gives labels.
* Default assume cloud provider as aws. Check section [Finding Cloud Provider](#finding-cloud-provider).
* Label `beta.kubernetes.io/instance-type=m4.10xlarg` gives machine type. Here for this example machineType is m4.10xlarge
* Label `beta.kubernetes.io/os=linux` gives operating system. Here os is linux
* Label `failure-domain.beta.kubernetes.io/region=us-west-1` gives region. Here region is us-west-1
_Note: kubelet will not set these reserved labels if the cluster is not using cloud provider._
If any of the required labels is not available then we should fall back to default pricing.
#### Storage Volume Pricing
`kubectl get pv` gives us storage class(ex: gp2, my-storage-class etc) for each volume.
Further using command `kubectl describe storageclass <storageclass-name>` will give output in which there will be a field `Parameters`
containing labels for `type` of storage (Ex: gp2) and `zone` (Ex: us-west-1c).
_Parameters_ field may not contain _zone_ label. In such case we can get region from `kubectl describe pv <pv-name>` using label `failure-domain.beta.kubernetes.io/region`.
If _type_ label is also not present then we should fall back to default pricing.
### Finding Cloud Provider
While initiating a cluster either by kubeadm or kops or other kubernetes installers the user will set cloud-provider, if it isn't set kubernetes assumes that cluster is being deployed on bare metal.
Further when a new node is created `.spec.providerID` will be set based (by _kubelet_) on cloud-provider.
The value of `providerId` will be `(providerName + "://" + instanceID)`. As we need `providerName` (aws, azure etc) we can get it from `providerID`.
If getting cloud provider name fails we should fallback and assume default prices for aws.
*Example cluster on aws: --cloud-provider=aws command-line flag is needed (to successfully register the node with cloud provider) to be present for the API server, controller manager, and every kubelet in the cluster.
References:
* kubeadm: https://kubernetes.io/docs/concepts/cluster-administration/cloud-providers/
* providerID value: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/cloud-provider/cloud.go#L94
* Cluster on aws: https://blog.heptio.com/setting-up-the-kubernetes-aws-cloud-provider-6f0349b512bd
* More on providerID: https://blog.scottlowe.org/2018/09/28/setting-up-the-kubernetes-aws-cloud-provider/
*Note: All above kubectl commands will have corresponding methods in kubernetes client-go
### Populating Rate Card
#### Design
* Identify cloud provider, region of the cluster
* Embed the crawler code in purser and run cloud specific crawler to fetch the rate cards.
* Populate the data in dgraph.
* Update rate card periodically.
* Support: AWS, Azure, PKS, VKE, GCE
#### AWS:
* Public API: Available
* Reference: https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/price-changes.html
* API call: https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/region/index.json
* Example for us-east-1: https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/us-east-1/index.json
* Note: aws provides sdk in golang for pricing. Reference: https://docs.aws.amazon.com/sdk-for-go/api/service/pricing/
================================================
FILE: docs/developers-guide.md
================================================
# Developers Guide
- [Prerequisites](#prerequisites)
- [Workspace Setup](#workspace-setup)
- [Database Setup](#database-setup)
- [Running Purser Controller](#running-purser-controller)
- [Running Purser UI](#running-purser-ui)
- [Purser Plugin Compilation](#purser-plugin-compilation)
- [Plugin Execution](#plugin-execution)
## Prerequisites
1. Ensure the following dependencies are installed on your system.
- [Go](https://golang.org/dl/)
- [Git](https://git-scm.com/downloads)
- [Docker](https://www.docker.com/)
You may use the official binaries or your usual package manager.
Also set the following environment variables
- Set `GOPATH` environment variable. Refer [setting GOPATH](https://github.com/golang/go/wiki/SettingGOPATH)
- Add `$GOPATH/bin` in system `PATH` variable by running `export PATH=$PATH:$GOPATH/bin`.
Optionally, add the above exports to your `.bash_profile` or `.bashrc` to persist across console sessions.
2. Verify that the dependencies are properly installed.
``` bash
go version, should be at least 1.7
git version
docker version
```
## Workspace Setup
### Fork the repository
Navigate to the [Purser repo on GitHub](https://github.com/vmware/purser) and use the 'Fork' button.
This gives you a copy of the repo for pull requests back to purser in `https://github.com/<your-github-id>/purser`
### Clone and Set Upstream Remote
Make a local clone of the forked repo and add the base purser
repo as the upstream remote repository.
``` shell
# create and change directory to $GOPATH/src/github.com/vmware
mkdir -p $GOPATH/src/github.com/vmware
cd $GOPATH/src/github.com/vmware
# clone the forked repository and change directory to purser
git clone https://github.com/<your-github-id>/purser.git
cd purser
# add upstream repository as the original purser repo
git remote add upstream https://github.com/vmware/purser.git
```
The last git command prepares your clone to pull changes from the
upstream repo and push them into the fork, which enables you to keep
the fork up to date.
### Download dependencies
Run the following commands to download dependencies.
``` shell
make tools
make deps
make install
```
## Database Setup
In order to persist inventory and discovery information such as pods and service details we use
[Dgraph](https://dgraph.io/) to store the inventory metrics and resource relationship.
In order to install DGraph from docker image follow the following steps:
- Pull the latest Dgraph version
```bash
docker pull dgraph/dgraph
```
- To run Dgraph in Docker
```bash
mkdir -p /tmp/data
# Run dgraph-zero
docker run -d -p 5080:5080 -p 6080:6080 -p 8080:8080 -p 9080:9080 -p 8000:8000 -v /tmp/data:/dgraph --name diggy dgraph/dgraph dgraph zero
# In another terminal, now run dgraph-alpha
docker exec -d diggy dgraph alpha --lru_mb 2048 --zero localhost:5080
```
- Optional: To start Dgraph UI(at `localhost:8000`) for running manual queries
```bash
# Run Dgraph Ratel
docker exec -d diggy dgraph-ratel
```
## Running Purser Controller
To run purser controller execute following commands
```bash
# change directory to purser main folder
cd $GOPATH/src/github.com/vmware/purser
# run purser with log level as info and interactions as disabled by default
go run cmd/controller/purserctrl.go --kubeconfig=<path-to-your-cluster-config> --interactions=disable --dgraphURL=localhost --log=info
```
## Running Purser UI
Install latest version of `node` and `npm`. Then to run purser UI execute the following commands
```bash
# change directory to purser ui folder
cd $GOPATH/src/github.com/vmware/purser/ui
# install node modules
npm install
# run purser UI at localhost:4200
npm run startdev
```
_Refer [UI docs](../ui/README.md) for more details._
## Purser Plugin Compilation
To create purser plugin binary `purser_plugin` at path `$GOPATH/bin` run the following commands
```bash
# change directory to purser main folder
cd $GOPATH/src/github.com/vmware/purser
# create binary at path $GOPATH/bin
go build -o $GOPATH/bin/purser_plugin github.com/vmware/purser/cmd/plugin
```
**NOTE:** _Windows users need to rename `purser_plugin` to `purser_plugin.exe`_
## Plugin Execution
1. In order to install the Purser plugin, copy the [plugin.yaml](../plugin.yaml) file to one of the specified paths defined under the section [installing kubectl plugins](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/).
2. Run the following command to check the purser plugin works locally.
``` bash
kubectl --kubeconfig=<absolute path to kubeconfig file> plugin purser help
```
## Useful commands and links
- To contribute to purser refer [CONTRIBUTING](../CONTRIBUTING.md) and [CODE_OF_CONDUCT](../CODE_OF_CONDUCT.md)
- To drop complete dgraph database: `curl -X POST localhost:8080/alter -d '{"drop_all": true}'`
================================================
FILE: docs/manual-installation.md
================================================
# Manual Installation
To install Purser manually from the Binary follow the steps described below.
## Purser Setup
The following steps will install Purser in your cluster at namespace `purser`.
Creation of this namespace is needed because purser needs to create a service-account which requires namespace.
Also, the frontend will use kubernetes DNS to call backend for data and this DNS contains a field for namespace.
``` bash
# Namespace setup
kubectl create ns purser
# DB setup
curl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-database-setup.yaml -O
kubectl --namespace=purser create -f purser-database-setup.yaml
# Purser controller setup
curl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-controller-setup.yaml -O
kubectl --namespace=purser create -f purser-controller-setup.yaml
# Purser UI setup
curl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-ui-setup.yaml -O
kubectl --namespace=purser create -f purser-ui-setup.yaml
```
**NOTE:** If you don't have `curl` installed you can download `purser-database-setup.yaml` from [here](./cluster/purser-database-setup.yaml), `purser-controller-setup.yaml` from [here](cluster/purser-controller-setup.yaml) and `purser-ui-setup.yaml` from [here](cluster/purser-ui-setup.yaml).
Then `kubectl create -f purser-database-setup.yaml` ,
`kubectl create -f purser-controller-setup.yaml` and `kubectl create -f purser-ui-setup.yaml` will setup purser in your cluster.
##### Change Settings and Enable/Disable Purser Features
The following settings can be customized before Controller installation:
- Change the default **log level**, **dgraph url** and **dgraph port** by editing `args` field in the [purser-controller-setup.yaml](cluster/purser-controller-setup.yaml). (Default: `--log=info`, `--dgraphURL=purser-db`, `--dgraphPort=9080`)
- Enable/Disable **resource interactions** capability by editing `args` field in the [purser-controller-setup.yaml](cluster/purser-controller-setup.yaml) and uncommenting `pods/exec` rule from purser-permissions. (Default: `disabled`)
- Enable **subscription to inventory changes** capability by creating an object of custom resource kind `Subscriber`. (Refer: [example-subscriber.yaml](./cluster/artifacts/example-subscriber.yaml))
- Enable **customized logical grouping of resources** by creating an object of custom resource kind `Group`. (Refer: [docs](docs/custom-group-installation-and-usage.md) for custom group installation and usage)
_**NOTE:** Use flag `--kubeconfig=<absolute path to config>` if your cluster configuration is not at the [default location](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable)._
## Purser Plugin Installation (Optional)
- Download the purser plugin descriptor for your environment from the [releases page](https://github.com/vmware/purser/releases/download/v1.0.0/plugin.yaml).
- Move the `plugin.yaml` file into one of the paths specified under the Kubernetes [documentation](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins).
- Download the purser binary corresponding to your operating system from the [releases page](https://github.com/vmware/purser/releases/tag/v1.0.0).
- Move the binary into one of the directories in your environment `PATH`.
================================================
FILE: docs/plugin-installation.md
================================================
# Purser Plugin Setup
_NOTE: This Plugin installation is optional. Install it if you want to use CLI of Purser._
## Linux and macOS
``` bash
# Binary installation
wget -q https://github.com/vmware/purser/blob/master/build/purser-binary-install.sh && sh purser-binary-install.sh
```
Enter your cluster's configuration path when prompted. The plugin binary needs to be in your `PATH` environment variable, so once the download of the binary is finished the script tries to move it to `/usr/local/bin`. This may need your sudo permission.
## Windows/Others
For installation on Windows follow the steps in the [manual installation guide](./docs/manual-installation.md).
## Uninstalling Purser Plugin
### Linux/macOS
``` bash
curl https://raw.githubusercontent.com/vmware/purser/master/build/purser-binary-install.sh -O && sh purser-binary-uninstall.sh
```
================================================
FILE: docs/plugin-usage.md
================================================
# Purser Plugin Usage
Once installed, Purser is ready for use right away. You can query using native Kubernetes grouping artifacts.
Purser supports the following list of commands.
``` bash
# query cluster visibility in terms of savings and summary for the application.
kubectl plugin purser get [summary|savings]
# query resources filtered by associated namespace, labels and groups.
kubectl plugin purser get resources group <group-name>
# query cost filtered by associated labels, pods and node.
kubectl plugin purser get cost label <key=val>
kubectl plugin purser get cost pod <pod name>
kubectl plugin purser get cost node all
# configure user-costs for the choice of deployment.
kubectl plugin purser [set|get] user-costs
```
_Use flag `--kubeconfig=<absolute path to config>` if your cluster configuration is not at the [default location](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable)._
## Examples
1. Get Cluster Summary
``` bash
$ kubectl plugin purser get summary
Cluster Summary
Compute:
Node count: 57
Cost: 3015.48$
Total Capacity:
Cpu(vCPU): 456
Memory(GB): 1770.50
Provisioned Resources:
Cpu Request(vCPU): 319
Memory Request(GB): 1032.67
Storage:
Persistent Volume count: 151
Capacity(GB): 9297.00
Cost: 4124.79$
PV Claim count: 108
PV Claim Capacity(GB): 8867.00
Cost:
Compute cost: 3015.48$
Storage cost: 4124.79$
Total cost: 7140.27$
```
2. Get Cost Of All Nodes
``` bash
kubectl purser get cost node all
```
3. Get Savings
``` bash
$ kubectl plugin purser get savings
Savings Summary
Storage:
Unused Volumes: 43
Unused Capacity(GB): 430.00
Month To Date Savings: 186.33$
Projected Monthly Savings: 1066.40$
```
Next, define higher level groupings to define your business, logical or application constructs.
## Defining Custom Groups
Refer [doc](./custom-group-installation-and-usage.md) for custom group installation and usage.
================================================
FILE: docs/purser-deployment.md
================================================
# Purser Deployment
In order to deploy the Purser UI and DGraph database service, follow the below listed steps:
1. Switch the current context to point to the desired cluster.
``` bash
kubectl config use-context <context>
```
Read more about configuring and setting the `KUBECONFIG` and kubernetes context [here](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/).
2. If the cluster does not have a valid public IP, set proxy in order to expose the service externally.
``` bash
kubectl proxy
```
3. When set, you can simply deploy the Purser UI and Dgraph database service using target `make deploy-purser`.
_If you wish to however, deploy the database service and the UI service separately, execute the following targets respectively._
``` bash
# deploy Dgraph database
make kubectl-deploy-purser-db
# deploy purser UI
make kubectl-deploy-purser-ui
```
4. Once deployed, if proxy was set the UI service can be accessed from [this url](http://127.0.0.1:8001/api/v1/namespaces/default/services/http:purser-ui:4200/proxy/home).
If public IP was available for your cluster, the UI service should be accessible from path `<External-Public-IP>:<NodePort>`.
Eg. `http://<minishiftIP>:<NodePort>/home`
5. In order to drop the Dgraph entries from the database, delete the `Persistent Volume` corresponding to the `dgraph datadir`.
================================================
FILE: docs/sourcecode-installation.md
================================================
# Installation Through Source Code
- [Prerequisites](#prerequisites)
- [Server Side Installation (Controller Installation)](#server-side-installation-controller-installation)
- [Client Side Installation (Plugin Installation)](#client-side-installation-plugin-installation)
## Prerequisites
1. Kubernetes Version 1.9 or greater
- `kubectl` installed and configured. For details refer [here](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
2. Dependencies
- [Go](https://golang.org/dl/)
- version > 1.7
- setup `GOPATH` environment variable by as per the [Golang documentation](https://github.com/golang/go/wiki/SettingGOPATH).
- add `$GOPATH/bin` directory to your environment `$PATH` variable.
- [Docker](https://www.docker.com/get-started)
3. Fetch the Purser source code from GitHub.
``` go
go get github.com/vmware/purser
```
``` bash
# change directory to project root
cd $GOPATH/src/github.com/vmware/purser
```
4. For Windows users, install gnu `make` from [here](http://gnuwin32.sourceforge.net/packages/make.htm).
5. Download project dependencies with `make`.
``` bash
# download project tools
make tools
# download project dependencies
make deps
# update project depedencies
make update
```
## Server Side Installation (Controller Installation)
Follow the below steps to install the purser controller and custom resource definitions for the user groups in the Kubernetes cluster.
### Build Controller Binary
Build the purser controller binary using `make` target.
``` bash
make build
```
### Build Container Image
Update the [Makefile](./Makefile) to set the `REGISTRY` field to your Docker username and execute the following `make` targets to build and publish the docker images.
``` bash
# create the container(docker image)
make container
# authenticate your Docker credentials
docker login
# publish your docker image to docker hub
make push
```
### Install Purser Plugin
- Update the image name in [`purser-controller-setup.yaml`](../cluster/purser-controller-setup.yaml) to the docker image name that you pushed.
- Install the controller in the cluster using `kubectl`.
The following steps will install Purser in your cluster at namespace `purser`.
Creation of this namespace is needed because purser needs to create a service-account which requires namespace.
Also, the frontend will use kubernetes DNS to call backend for data and this DNS contains a field for namespace.
``` bash
# Namespace setup
kubectl create ns purser
# DB setup
curl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-database-setup.yaml -O
kubectl --namespace=purser create -f purser-database-setup.yaml
# Purser controller setup
kubectl --namespace=purser create -f purser-controller-setup.yaml
# Purser UI setup
curl https://raw.githubusercontent.com/vmware/purser/master/cluster/purser-ui-setup.yaml -O
kubectl --namespace=purser create -f purser-ui-setup.yaml
```
_Use flag `--kubeconfig=<absolute path to config>` if your cluster configuration is not at the [default location](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable)._
## Client Side Installation (Plugin Installation)
- Build the purser plugin binary in the `GOPATH/bin` directory.
``` go
go build -o $GOPATH/bin/purser_plugin github.com/vmware/purser/cmd/plugin
```
- Install the Purser plugin by copying the [`plugin.yaml`](../plugin.yaml) into one of the paths specified under the Kubernetes documentation section [installing kubectl plugins](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/).
================================================
FILE: openapi.yaml
================================================
---
openapi: 3.0.1
info:
title: Purser
description: Purser runs on server port `:3030` and exposes API endpoints to generate an insight into your Kubernetes applications by providing details of communicating services and pods.
version: 1.0.0
servers:
- url: http://localhost:3030
paths:
/api/hierarchy:
get:
description: Gets the top level cluster hierachy
parameters:
- name: view
in: query
description: physical or logical depending on selection of physical entities such as nodes, persistent volumes or logical entities such as namespaces, pods etc. Default is logical.
required: false
style: FORM
explode: true
schema:
type: string
example: physical
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/namespace:
get:
description: Gets the K8s Namespace hierachy
parameters:
- name: name
in: query
description: a valid K8s Namespace name prefixed with `namespace-`
required: false
style: FORM
explode: true
schema:
type: string
example: namespace-kube-public
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/pvc:
get:
description: Gets the K8s PVC hierachy
parameters:
- name: name
in: query
description: a valid K8s PVC name prefixed with `pvc-`
required: true
style: FORM
explode: true
schema:
type: string
example: pvc-datadir-dgraph-0
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/job:
get:
description: Gets the K8s Job hierachy
parameters:
- name: name
in: query
description: a valid K8s Job name prefixed with `job-`
required: true
style: FORM
explode: true
schema:
type: string
example: job-kube-proxy
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/container:
get:
description: Gets the K8s container hierachy
parameters:
- name: name
in: query
description: a valid K8s container name prefixed with `container-`
required: true
style: FORM
explode: true
schema:
type: string
example: container-etcd
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/replicaset:
get:
description: Gets the K8s Replicaset hierachy
parameters:
- name: name
in: query
description: a valid K8s Replicaset name prefixed with `replicaset-`
required: true
style: FORM
explode: true
schema:
type: string
example: replicaset-kube-dns-86f4d74b45
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/pod:
get:
description: Gets the K8s Pod hierachy
parameters:
- name: name
in: query
description: a valid K8s Pod name prefixed with `pod-`
required: true
style: FORM
explode: true
schema:
type: string
example: pod-etcd-minikube
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/node:
get:
description: Gets the K8s Node hierachy
parameters:
- name: name
in: query
description: a valid K8s Node name prefixed with `node-`
required: true
style: FORM
explode: true
schema:
type: string
example: node-minikube
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/daemonset:
get:
description: Gets the K8s Daemonset hierachy
parameters:
- name: name
in: query
description: a valid K8s Daemonset name prefixed with `daemonset-`
required: true
style: FORM
explode: true
schema:
type: string
example: daemonset-kube-proxy
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/deployment:
get:
description: Gets the K8s Deployment hierachy
parameters:
- name: name
in: query
description: a valid K8s Deployment name prefixed with `deployment-`
required: true
style: FORM
explode: true
schema:
type: string
example: deployment-kube-dns
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/pv:
get:
description: Gets the K8s PV hierachy
parameters:
- name: name
in: query
description: a valid K8s PV name prefixed with `pv-`
required: true
style: FORM
explode: true
schema:
type: string
example: pv-pvc-5ffeaa3f-ed5e-11e8-b395-080027a0bfc5
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/statefulset:
get:
description: Gets the K8s Statefulset hierachy
parameters:
- name: name
in: query
description: a valid K8s Statefulset name prefixed with `statefulset-`
required: true
style: FORM
explode: true
schema:
type: string
example: statefulset-kube-dns-86f4d74b45
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/hierarchy/process:
get:
description: Gets the K8s container process hierachy
parameters:
- name: name
in: query
description: a valid K8s container process name prefixed with `process-`
required: true
style: FORM
explode: true
schema:
type: string
example: process-etcd
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Hierarchy'
/api/metrics:
get:
description: Gets the complete K8s cluster metrics
parameters:
- name: view
in: query
description: physical or logical depending on selection of physical entities such as nodes, persistent volumes or logical entities such as namespaces, pods etc. Default is logical.
required: false
style: FORM
explode: true
schema:
type: string
example: logical
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/metrics/namespace:
get:
description: Gets the K8s Namespace metrics
parameters:
- name: name
in: query
description: a valid K8s Namespace name prefixed with `namespace-`
required: false
style: FORM
explode: true
schema:
type: string
example: namespace-kube-public
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/metrics/pvc:
get:
description: Gets the K8s PVC metrics
parameters:
- name: name
in: query
description: a valid K8s PVC name prefixed with `pvc-`
required: true
style: FORM
explode: true
schema:
type: string
example: pvc-datadir-dgraph-0
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/metrics/job:
get:
description: Gets the K8s Job metrics
parameters:
- name: name
in: query
description: a valid K8s Job name prefixed with `job-`
required: true
style: FORM
explode: true
schema:
type: string
example: job-kube-proxy
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/metrics/container:
get:
description: Gets the K8s container metrics
parameters:
- name: name
in: query
description: a valid K8s container name prefixed with `container-`
required: true
style: FORM
explode: true
schema:
type: string
example: container-etcd
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/metrics/replicaset:
get:
description: Gets the K8s Replicaset metrics
parameters:
- name: name
in: query
description: a valid K8s Replicaset name prefixed with `replicaset-`
required: true
style: FORM
explode: true
schema:
type: string
example: replicaset-kube-dns-86f4d74b45
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/metrics/pod:
get:
description: Gets the K8s Pod metrics
parameters:
- name: name
in: query
description: a valid K8s Pod name prefixed with `pod-`
required: true
style: FORM
explode: true
schema:
type: string
example: pod-etcd-minikube
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/metrics/node:
get:
description: Gets the K8s Node metrics
parameters:
- name: name
in: query
description: a valid K8s Node name prefixed with `node-`
required: true
style: FORM
explode: true
schema:
type: string
example: node-minikube
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/metrics/daemonset:
get:
description: Gets the K8s Daemonset metrics
parameters:
- name: name
in: query
description: a valid K8s Daemonset name prefixed with `daemonset-`
required: true
style: FORM
explode: true
schema:
type: string
example: daemonset-kube-proxy
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/metrics/deployment:
get:
description: Gets the K8s Deployment metrics
parameters:
- name: name
in: query
description: a valid K8s Deployment name prefixed with `deployment-`
required: false
style: FORM
explode: true
schema:
type: string
example: deployment-kube-dns
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/metrics/pv:
get:
description: Gets the K8s PV metrics
parameters:
- name: name
in: query
description: a valid K8s PV name prefixed with `pv-`
required: true
style: FORM
explode: true
schema:
type: string
example: pv-pvc-5ffeaa3f-ed5e-11e8-b395-080027a0bfc5
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/metrics/statefulset:
get:
description: Gets the K8s Statefulset metrics
parameters:
- name: name
in: query
description: a valid K8s Statefulset name prefixed with `statefulset-`
required: true
style: FORM
explode: true
schema:
type: string
example: statefulset-kube-dns-86f4d74b45
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Metrics'
/api/interactions/pod:
get:
description: Gets K8s Pods interactions
parameters:
- name: name
in: query
description: a valid K8s Pod name prefixed with `pod-`
required: false
style: FORM
explode: true
schema:
type: string
example: pod-kube-dns-86f4d74b45-4v66p
- name: orphan
in: query
description: filters out orphan pods if set to false. Default is true
required: false
style: FORM
explode: true
schema:
type: boolean
example: "false"
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
$ref: '#/components/schemas/Interactions'
/api/edges:
get:
description: Gets edges between Dgraph Components
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
type: array
items:
$ref: '#/components/schemas/Edges'
/api/nodes:
get:
description: Gets Dgraph node Components
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
type: array
items:
$ref: '#/components/schemas/Nodes'
/api/groups:
get:
description: Gets array of Group objects along with their metrics
responses:
200:
description: Operation Successful
content:
application/json; charset=UTF-8:
schema:
type: array
items:
$ref: '#/components/schemas/Groups'
components:
schemas:
Hierarchy:
type: object
properties:
data:
$ref: '#/components/schemas/Hierarchy_data'
Metrics:
type: object
properties:
data:
$ref: '#/components/schemas/Metrics_data'
Interactions:
type: object
properties:
pods:
type: array
items:
$ref: '#/components/schemas/Interactions_pods'
Nodes:
type: object
properties:
id:
type: integer
format: int32
example: 1
label:
type: string
example: postgres-54f9679f4b-nj8vv
title:
type: string
example: pods
value:
type: integer
format: int32
example: 1
group:
type: integer
format: int32
example: 1
cid:
type: array
items:
type: string
example: postgres
Edges:
type: object
properties:
from:
type: integer
format: int32
example: 4
to:
type: integer
format: int32
example: 3
title:
type: string
example: 7 times communicated
Groups:
type: object
properties:
name:
type: string
example: Group-1
podsCount:
type: integer
format: int32
example: 4
mtdCPU:
type: number
example: 10.2
mtdMemory:
type: number
example: 3.9
mtdStorage:
type: number
example: 41.5
cpu:
type: number
example: 2.04
memory:
type: number
example: 0.78
storage:
type: number
example: 8.3
mtdCPUCost:
type: number
example: 0.2448
mtdMemoryCost:
type: number
example: 0.039
mtdStorageCost:
type: number
example: 0.00576388852
mtdCost:
type: number
example: 0.28956388852
Hierarchy_data_children:
type: object
properties:
name:
type: string
example: namespace-default
type:
type: string
example: namespace
Hierarchy_data:
type: object
properties:
name:
type: string
example: cluster
type:
type: string
example: cluster
children:
type: array
items:
$ref: '#/components/schemas/Hierarchy_data_children'
Metrics_data_children:
type: object
properties:
name:
type: string
example: namespace-default
type:
type: string
example: namespace
cpu:
type: number
example: 0.915
memory:
type: number
example: 0.224609
cpuCost:
type: number
example: 0.02196
memoryCost:
type: number
example: 0.002246
Metrics_data:
type: object
properties:
name:
type: string
example: cluster
type:
type: string
example: cluster
children:
type: array
items:
$ref: '#/components/schemas/Metrics_data_children'
cpu:
type: number
example: 0.915
memory:
type: number
example: 0.224609
cpuCost:
type: number
example: 0.02196
memoryCost:
type: number
example: 0.002246
Interactions_inbound:
type: object
properties:
name:
type: string
example: pod-webapp-958cf5567-xb758
Interactions_pods:
type: object
properties:
name:
type: string
example: pod-postgres-54f9679f4b-vsdhl
inbound:
type: array
items:
$ref: '#/components/schemas/Interactions_inbound'
outbound:
type: array
items:
$ref: '#/components/schemas/Interactions_inbound'
extensions: {}
================================================
FILE: pkg/apis/groups/v1/deepcopy.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package v1
import "k8s.io/apimachinery/pkg/runtime"
// DeepCopyInto copies all properties of this object into another object of the
// same type that is provided as a pointer.
func (in *Group) DeepCopyInto(out *Group) {
out.TypeMeta = in.TypeMeta
out.ObjectMeta = in.ObjectMeta
out.Spec = in.Spec
out.Status = in.Status
}
// DeepCopyObject returns a generically typed copy of an object
func (in *Group) DeepCopyObject() runtime.Object {
out := Group{}
in.DeepCopyInto(&out)
return &out
}
// DeepCopyObject returns a generically typed copy of an object
func (in *GroupList) DeepCopyObject() runtime.Object {
out := GroupList{}
out.TypeMeta = in.TypeMeta
out.ListMeta = in.ListMeta
if in.Items != nil {
out.Items = make([]*Group, len(in.Items))
for i := range in.Items {
in.Items[i].DeepCopyInto(out.Items[i])
}
}
return &out
}
================================================
FILE: pkg/apis/groups/v1/docs.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package v1
================================================
FILE: pkg/apis/groups/v1/register.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package v1
import (
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// SchemeBuilder parameters
var (
SchemeBuilder = runtime.NewSchemeBuilder(AddKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: CRDGroup, Version: CRDVersion}
// Kind takes an unqualified kind and returns a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
// AddKnownTypes ...
func AddKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Group{},
&GroupList{},
)
meta_v1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
================================================
FILE: pkg/apis/groups/v1/types.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package v1
import (
"github.com/vmware/purser/pkg/controller/metrics"
"time"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// CRD Group attributes
const (
CRDPlural string = "groups"
CRDGroup string = "vmware.purser.com"
CRDVersion string = "v1"
FullCRDName string = CRDPlural + "." + CRDGroup
)
// Group describes our custom Group resource
type Group struct {
meta_v1.TypeMeta `json:",inline"`
meta_v1.ObjectMeta `json:"metadata"`
Spec GroupSpec `json:"spec"`
Status GroupStatus `json:"status,omitempty"`
}
// GroupSpec is the spec for the Group resource
type GroupSpec struct {
Name string `json:"name"`
Type string `json:"type,omitempty"`
Expressions map[string]map[string][]string `json:"labels,omitempty"`
AllocatedResources *GroupMetrics `json:"metrics,omitempty"`
PITMetrics *GroupMetrics `json:"pitMetrics,omitempty"`
MTDMetrics *GroupMetrics `json:"mtdMetrics,omitempty"`
MTDCost *Cost `json:"mtdCost,omitempty"`
PerHourCost *Cost `json:"perHourCost,omitempty"`
LastMonthCost *Cost `json:"lastMonthCost,omitempty"`
LastLastMonthCost *Cost `json:"lastLastMonthCost,omitempty"`
LastUpdated time.Time `json:"lastUpdated,omitempty"`
}
// GroupMetrics ...
type GroupMetrics struct {
CPULimit float64
MemoryLimit float64
StorageCapacity float64
CPURequest float64
MemoryRequest float64
StorageClaim float64
}
// Cost details
type Cost struct {
TotalCost float64
CPUCost float64
MemoryCost float64
StorageCost float64
}
// GroupList is the list of Group resources
type GroupList struct {
meta_v1.TypeMeta `json:",inline"`
meta_v1.ListMeta `json:"metadata"`
Items []*Group `json:"items"`
}
// GroupStatus holds the status information for each Group resource
type GroupStatus struct {
State string `json:"state,omitempty"`
Message string `json:"message,omitempty"`
}
// PodDetails information for the pods associated with the Group resource
type PodDetails struct {
Name string
StartTime meta_v1.Time
EndTime meta_v1.Time
Containers []*Container
PodVolumeClaims map[string]*PersistentVolumeClaim
}
// PersistentVolumeClaim information for the pods associated with the Group resource
// A PVC can bound and unbound to a pod many times, so maintaining
// BoundTimes and UnboundTimes as lists.
// A PVC can be upgraded or downgraded, so maintaining capacityAllocated as a list
// Whenever a PVC capacity changes will update UnboundTime for old capacity, and
// append new capacity to capacityAllocated with bound time appended to BoundTimes
// The i-th capacity allocated corresponds to the i-th bound time and to i-th unbound time.
// Similarly for RequestSizeInGB
type PersistentVolumeClaim struct {
Name string
VolumeName string
RequestSizeInGB []float64
CapacityAllocatedInGB []float64
BoundTimes []meta_v1.Time
UnboundTimes []meta_v1.Time
}
// Container information for the pods associated with the Group resource
type Container struct {
Name string
Metrics *metrics.Metrics
}
================================================
FILE: pkg/apis/subscriber/v1/deepcopy.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package v1
import "k8s.io/apimachinery/pkg/runtime"
// DeepCopyInto copies all properties of this object into another object of the
// same type that is provided as a pointer.
func (in *Subscriber) DeepCopyInto(out *Subscriber) {
out.TypeMeta = in.TypeMeta
out.ObjectMeta = in.ObjectMeta
out.Spec = in.Spec
out.Status = in.Status
}
// DeepCopyObject returns a generically typed copy of an object
func (in *Subscriber) DeepCopyObject() runtime.Object {
out := Subscriber{}
in.DeepCopyInto(&out)
return &out
}
// DeepCopyObject returns a generically typed copy of an object
func (in *SubscriberList) DeepCopyObject() runtime.Object {
out := SubscriberList{}
out.TypeMeta = in.TypeMeta
out.ListMeta = in.ListMeta
if in.Items != nil {
out.Items = make([]Subscriber, len(in.Items))
for i := range in.Items {
in.Items[i].DeepCopyInto(&out.Items[i])
}
}
return &out
}
================================================
FILE: pkg/apis/subscriber/v1/docs.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package v1
================================================
FILE: pkg/apis/subscriber/v1/register.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package v1
import (
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// SchemeBuilder parameters
var (
SchemeBuilder = runtime.NewSchemeBuilder(AddKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
// SubscriberGroupVersion is group version used to register these objects
var SubscriberGroupVersion = schema.GroupVersion{Group: SubscriberGroup, Version: SubscriberVersion}
// Kind takes an unqualified kind and returns a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SubscriberGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SubscriberGroupVersion.WithResource(resource).GroupResource()
}
// AddKnownTypes ...
func AddKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SubscriberGroupVersion,
&Subscriber{},
&SubscriberList{},
)
meta_v1.AddToGroupVersion(scheme, SubscriberGroupVersion)
return nil
}
================================================
FILE: pkg/apis/subscriber/v1/types.go
================================================
/*
* Copyright (c) 2018 VMware Inc. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package v1
import meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// CRD Subscriber attributes
const (
SubscriberPlural string = "subscribers"
SubscriberGroup string = "vmware.purser.com"
SubscriberVersion string = "v1"
SubscriberFullName string = SubscriberPlural + "." + SubscriberGroup
)
// Subscriber information
type Subscriber struct {
meta_v1.TypeMeta `json:",inline"`
meta_v1.ObjectMeta `json:"metadata"`
Spec SubscriberSpec `json:"spec"`
Status SubscriberStatus `json:"status,omitempty"`
}
// SubscriberSpec definition details
type SubscriberSpec str
gitextract_khhzg63f/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── custom.md
│ │ └── feature_request.md
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .make/
│ ├── Makefile.deploy.controller
│ └── Makefile.deploy.purser
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile.in
├── Gopkg.toml
├── LICENSE
├── Makefile
├── NOTICE
├── README.md
├── build/
│ ├── build.sh
│ ├── purser-binary-install.sh
│ ├── purser-binary-uninstall.sh
│ ├── purser-minimal-setup.sh
│ └── purser-setup.sh
├── cluster/
│ ├── artifacts/
│ │ ├── example-group.yaml
│ │ ├── example-subscriber.yaml
│ │ ├── group-template.json
│ │ ├── purser-group-crd.yaml
│ │ └── purser-subscriber-crd.yaml
│ ├── helm/
│ │ └── chart/
│ │ └── purser/
│ │ ├── .helmignore
│ │ ├── Chart.yaml
│ │ ├── README.md
│ │ ├── templates/
│ │ │ ├── NOTES.txt
│ │ │ ├── _helpers.tpl
│ │ │ ├── purser-controller-deployment.yaml
│ │ │ ├── purser-controller-rbac.yaml
│ │ │ ├── purser-controller-serviceaccount.yaml
│ │ │ ├── purser-controller-svc.yaml
│ │ │ ├── purser-database-statefulset.yaml
│ │ │ ├── purser-database-svc.yaml
│ │ │ ├── purser-ui-configmap.yaml
│ │ │ ├── purser-ui-deployment.yaml
│ │ │ ├── purser-ui-ingress.yaml
│ │ │ └── purser-ui-svc.yaml
│ │ └── values.yaml
│ ├── minimal/
│ │ ├── purser-controller-setup.yaml
│ │ ├── purser-database-setup.yaml
│ │ └── purser-ui-setup.yaml
│ ├── purser-controller-setup.yaml
│ ├── purser-database-setup.yaml
│ └── purser-ui-setup.yaml
├── cmd/
│ ├── controller/
│ │ ├── api/
│ │ │ ├── api.go
│ │ │ ├── apiHandlers/
│ │ │ │ ├── authenticationHandlers.go
│ │ │ │ ├── customGroupHandlers.go
│ │ │ │ ├── helpers.go
│ │ │ │ └── hierarchyAndMetricAPIHandlers.go
│ │ │ ├── logger.go
│ │ │ ├── router.go
│ │ │ └── routes.go
│ │ ├── config/
│ │ │ └── config.go
│ │ └── purserctrl.go
│ └── plugin/
│ ├── purser.go
│ └── types.go
├── docs/
│ ├── architecture.md
│ ├── custom-group-installation-and-usage.md
│ ├── design/
│ │ └── pricing.md
│ ├── developers-guide.md
│ ├── manual-installation.md
│ ├── plugin-installation.md
│ ├── plugin-usage.md
│ ├── purser-deployment.md
│ └── sourcecode-installation.md
├── openapi.yaml
├── pkg/
│ ├── apis/
│ │ ├── groups/
│ │ │ └── v1/
│ │ │ ├── deepcopy.go
│ │ │ ├── docs.go
│ │ │ ├── register.go
│ │ │ └── types.go
│ │ └── subscriber/
│ │ └── v1/
│ │ ├── deepcopy.go
│ │ ├── docs.go
│ │ ├── register.go
│ │ └── types.go
│ ├── client/
│ │ ├── clientset/
│ │ │ └── typed/
│ │ │ ├── groups/
│ │ │ │ └── v1/
│ │ │ │ ├── group.go
│ │ │ │ └── group_client.go
│ │ │ └── subscriber/
│ │ │ └── v1/
│ │ │ ├── subsciber_client.go
│ │ │ └── subscriber.go
│ │ └── clientset.go
│ ├── controller/
│ │ ├── buffering/
│ │ │ └── ring_buffer.go
│ │ ├── controller.go
│ │ ├── controller_test.go
│ │ ├── dgraph/
│ │ │ ├── dgraph.go
│ │ │ ├── login.go
│ │ │ ├── models/
│ │ │ │ ├── constants.go
│ │ │ │ ├── container.go
│ │ │ │ ├── daemonset.go
│ │ │ │ ├── deployment.go
│ │ │ │ ├── group.go
│ │ │ │ ├── job.go
│ │ │ │ ├── label.go
│ │ │ │ ├── namespace.go
│ │ │ │ ├── node.go
│ │ │ │ ├── pod.go
│ │ │ │ ├── pod_test.go
│ │ │ │ ├── process.go
│ │ │ │ ├── pv.go
│ │ │ │ ├── pvc.go
│ │ │ │ ├── query/
│ │ │ │ │ ├── cluster.go
│ │ │ │ │ ├── cluster_test.go
│ │ │ │ │ ├── constants_test.go
│ │ │ │ │ ├── group.go
│ │ │ │ │ ├── group_test.go
│ │ │ │ │ ├── helpers.go
│ │ │ │ │ ├── helpers_test.go
│ │ │ │ │ ├── label.go
│ │ │ │ │ ├── label_test.go
│ │ │ │ │ ├── login.go
│ │ │ │ │ ├── pod.go
│ │ │ │ │ ├── pod_test.go
│ │ │ │ │ ├── queries.go
│ │ │ │ │ ├── resource.go
│ │ │ │ │ ├── resource_test.go
│ │ │ │ │ ├── subscriber.go
│ │ │ │ │ ├── subscriber_test.go
│ │ │ │ │ └── types.go
│ │ │ │ ├── rateCard.go
│ │ │ │ ├── replicaset.go
│ │ │ │ ├── service.go
│ │ │ │ ├── statefulset.go
│ │ │ │ └── subscriber.go
│ │ │ └── purge.go
│ │ ├── discovery/
│ │ │ ├── executer/
│ │ │ │ └── exec.go
│ │ │ ├── generator/
│ │ │ │ └── graph.go
│ │ │ ├── linker/
│ │ │ │ ├── podlinks.go
│ │ │ │ ├── processlinks.go
│ │ │ │ └── servicelinks.go
│ │ │ └── processor/
│ │ │ ├── container.go
│ │ │ ├── pod.go
│ │ │ └── svc.go
│ │ ├── eventprocessor/
│ │ │ ├── notifier.go
│ │ │ ├── processor.go
│ │ │ ├── sync.go
│ │ │ └── updater.go
│ │ ├── metrics/
│ │ │ └── metrics.go
│ │ ├── payload.go
│ │ ├── persistentVolume.go
│ │ ├── types.go
│ │ └── utils/
│ │ ├── jsonutils.go
│ │ ├── k8sUtils.go
│ │ ├── purge.go
│ │ ├── purge_test.go
│ │ ├── timeUtils.go
│ │ ├── unitConversions.go
│ │ └── unitConversions_test.go
│ ├── plugin/
│ │ ├── costing.go
│ │ ├── grouping.go
│ │ ├── metrics/
│ │ │ └── metrics.go
│ │ ├── node.go
│ │ ├── pod.go
│ │ ├── pricing.go
│ │ ├── utils.go
│ │ └── volume.go
│ ├── pricing/
│ │ ├── aws/
│ │ │ ├── aws.go
│ │ │ └── convert.go
│ │ └── cloud.go
│ └── utils/
│ ├── fileutils.go
│ ├── k8sutil.go
│ └── logutil.go
├── plugin.yaml
├── test/
│ ├── controller/
│ │ └── buffering/
│ │ └── ring_buffer_test.go
│ ├── pricing/
│ │ └── pricing_aws_test.go
│ └── utils/
│ └── checkUtil.go
└── ui/
├── Dockerfile.deploy.purser
├── README.md
├── angular.json
├── e2e/
│ ├── protractor.conf.js
│ ├── src/
│ │ ├── app.e2e-spec.ts
│ │ └── app.po.ts
│ └── tsconfig.e2e.json
├── nginx.conf
├── package.json
├── proxy.conf.json
├── src/
│ ├── app/
│ │ ├── app.component.html
│ │ ├── app.component.scss
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.constants.ts
│ │ ├── app.module.ts
│ │ ├── app.routing.ts
│ │ ├── common/
│ │ │ └── messages/
│ │ │ ├── common.messages.ts
│ │ │ └── left-navigation.messages.ts
│ │ └── modules/
│ │ ├── capacity-graph/
│ │ │ ├── capacity-graph.module.ts
│ │ │ ├── components/
│ │ │ │ ├── capactiy-graph.component.html
│ │ │ │ ├── capactiy-graph.component.scss
│ │ │ │ ├── capactiy-graph.component.spec.ts
│ │ │ │ └── capactiy-graph.component.ts
│ │ │ └── services/
│ │ │ └── capacity-graph.service.ts
│ │ ├── changepassword/
│ │ │ ├── changepassword.module.ts
│ │ │ ├── components/
│ │ │ │ ├── changepassword.component.html
│ │ │ │ ├── changepassword.component.scss
│ │ │ │ ├── changepassword.component.spec.ts
│ │ │ │ └── changepassword.component.ts
│ │ │ └── services/
│ │ │ └── changepassword.service.ts
│ │ ├── logical-group/
│ │ │ ├── components/
│ │ │ │ ├── logical-group.component.css
│ │ │ │ ├── logical-group.component.html
│ │ │ │ ├── logical-group.component.spec.ts
│ │ │ │ └── logical-group.component.ts
│ │ │ ├── logical-group.module.ts
│ │ │ └── services/
│ │ │ └── logical-group.service.ts
│ │ ├── login/
│ │ │ ├── components/
│ │ │ │ ├── login.component.html
│ │ │ │ ├── login.component.scss
│ │ │ │ ├── login.component.spec.ts
│ │ │ │ └── login.component.ts
│ │ │ ├── login.module.ts
│ │ │ └── services/
│ │ │ └── login.service.ts
│ │ ├── logout/
│ │ │ ├── components/
│ │ │ │ ├── logout.component.html
│ │ │ │ ├── logout.component.scss
│ │ │ │ ├── logout.component.spec.ts
│ │ │ │ └── logout.component.ts
│ │ │ └── logout.module.ts
│ │ ├── options/
│ │ │ ├── components/
│ │ │ │ ├── options.component.html
│ │ │ │ ├── options.component.scss
│ │ │ │ ├── options.component.spec.ts
│ │ │ │ └── options.component.ts
│ │ │ └── options.module.ts
│ │ ├── topo-graph/
│ │ │ ├── components/
│ │ │ │ ├── topo-graph.component.html
│ │ │ │ ├── topo-graph.component.scss
│ │ │ │ ├── topo-graph.component.spec.ts
│ │ │ │ └── topo-graph.component.ts
│ │ │ ├── modules.ts
│ │ │ └── services/
│ │ │ └── topo-graph.service.ts
│ │ └── topologyGraph/
│ │ ├── components/
│ │ │ ├── index.ts
│ │ │ ├── topologyGraph.component.html
│ │ │ ├── topologyGraph.component.scss
│ │ │ └── topologyGraph.component.ts
│ │ ├── modules.ts
│ │ └── services/
│ │ └── topologyGraph.service.ts
│ ├── assets/
│ │ └── .gitkeep
│ ├── browserslist
│ ├── environments/
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── index.html
│ ├── json/
│ │ └── logicalGroup.json
│ ├── karma.conf.js
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.css
│ ├── test.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── tsconfig.json
└── tslint.json
SYMBOL INDEX (823 symbols across 134 files)
FILE: cmd/controller/api/api.go
function StartServer (line 30) | func StartServer(conf controller.Config) {
FILE: cmd/controller/api/apiHandlers/authenticationHandlers.go
type Credentials (line 32) | type Credentials struct
type User (line 39) | type User struct
function init (line 49) | func init() {
function LoginUser (line 66) | func LoginUser(w http.ResponseWriter, r *http.Request) {
function LogoutUser (line 103) | func LogoutUser(w http.ResponseWriter, r *http.Request) {
function isUserAuthenticated (line 124) | func isUserAuthenticated(w http.ResponseWriter, r *http.Request) bool {
function ChangePassword (line 129) | func ChangePassword(w http.ResponseWriter, r *http.Request) {
FILE: cmd/controller/api/apiHandlers/customGroupHandlers.go
function GetGroupsData (line 32) | func GetGroupsData(w http.ResponseWriter, r *http.Request) {
function DeleteGroup (line 46) | func DeleteGroup(w http.ResponseWriter, r *http.Request) {
function CreateGroup (line 66) | func CreateGroup(w http.ResponseWriter, r *http.Request) {
FILE: cmd/controller/api/apiHandlers/helpers.go
function addHeaders (line 35) | func addHeaders(w *http.ResponseWriter, r *http.Request) {
function addAccessControlHeaders (line 41) | func addAccessControlHeaders(w *http.ResponseWriter, r *http.Request) {
function writeBytes (line 46) | func writeBytes(w io.Writer, data []byte) {
function encodeAndWrite (line 53) | func encodeAndWrite(w io.Writer, obj interface{}) {
function convertRequestBodyToJSON (line 60) | func convertRequestBodyToJSON(r *http.Request) ([]byte, error) {
function SetKubeClientAndGroupClient (line 70) | func SetKubeClientAndGroupClient(conf controller.Config) {
function getGroupClient (line 75) | func getGroupClient() *v1.GroupClient {
function getKubeClient (line 79) | func getKubeClient() *kubernetes.Clientset {
FILE: cmd/controller/api/apiHandlers/hierarchyAndMetricAPIHandlers.go
function GetHomePage (line 33) | func GetHomePage(w http.ResponseWriter, r *http.Request) {
function GetPodInteractions (line 43) | func GetPodInteractions(w http.ResponseWriter, r *http.Request) {
function GetClusterHierarchy (line 64) | func GetClusterHierarchy(w http.ResponseWriter, r *http.Request) {
function GetNamespaceHierarchy (line 81) | func GetNamespaceHierarchy(w http.ResponseWriter, r *http.Request) {
function GetDeploymentHierarchy (line 104) | func GetDeploymentHierarchy(w http.ResponseWriter, r *http.Request) {
function GetReplicasetHierarchy (line 127) | func GetReplicasetHierarchy(w http.ResponseWriter, r *http.Request) {
function GetStatefulsetHierarchy (line 150) | func GetStatefulsetHierarchy(w http.ResponseWriter, r *http.Request) {
function GetPodHierarchy (line 173) | func GetPodHierarchy(w http.ResponseWriter, r *http.Request) {
function GetContainerHierarchy (line 196) | func GetContainerHierarchy(w http.ResponseWriter, r *http.Request) {
function GetEmptyHierarchy (line 219) | func GetEmptyHierarchy(w http.ResponseWriter, r *http.Request) {
function GetNodeHierarchy (line 231) | func GetNodeHierarchy(w http.ResponseWriter, r *http.Request) {
function GetPVHierarchy (line 254) | func GetPVHierarchy(w http.ResponseWriter, r *http.Request) {
function GetDaemonsetHierarchy (line 277) | func GetDaemonsetHierarchy(w http.ResponseWriter, r *http.Request) {
function GetJobHierarchy (line 300) | func GetJobHierarchy(w http.ResponseWriter, r *http.Request) {
function GetClusterMetrics (line 323) | func GetClusterMetrics(w http.ResponseWriter, r *http.Request) {
function GetNamespaceMetrics (line 341) | func GetNamespaceMetrics(w http.ResponseWriter, r *http.Request) {
function GetDeploymentMetrics (line 364) | func GetDeploymentMetrics(w http.ResponseWriter, r *http.Request) {
function GetDaemonsetMetrics (line 387) | func GetDaemonsetMetrics(w http.ResponseWriter, r *http.Request) {
function GetJobMetrics (line 410) | func GetJobMetrics(w http.ResponseWriter, r *http.Request) {
function GetStatefulsetMetrics (line 433) | func GetStatefulsetMetrics(w http.ResponseWriter, r *http.Request) {
function GetReplicasetMetrics (line 456) | func GetReplicasetMetrics(w http.ResponseWriter, r *http.Request) {
function GetNodeMetrics (line 479) | func GetNodeMetrics(w http.ResponseWriter, r *http.Request) {
function GetPodMetrics (line 502) | func GetPodMetrics(w http.ResponseWriter, r *http.Request) {
function GetContainerMetrics (line 525) | func GetContainerMetrics(w http.ResponseWriter, r *http.Request) {
function GetPVMetrics (line 548) | func GetPVMetrics(w http.ResponseWriter, r *http.Request) {
function GetPVCMetrics (line 571) | func GetPVCMetrics(w http.ResponseWriter, r *http.Request) {
function GetPodDiscoveryNodes (line 594) | func GetPodDiscoveryNodes(w http.ResponseWriter, r *http.Request) {
function GetPodDiscoveryEdges (line 618) | func GetPodDiscoveryEdges(w http.ResponseWriter, r *http.Request) {
function SyncCluster (line 636) | func SyncCluster(w http.ResponseWriter, r *http.Request) {
function syncResourcesInCluster (line 643) | func syncResourcesInCluster() {
FILE: cmd/controller/api/logger.go
function Logger (line 28) | func Logger(inner http.Handler, name string) http.Handler {
FILE: cmd/controller/api/router.go
function NewRouter (line 25) | func NewRouter() *mux.Router {
FILE: cmd/controller/api/routes.go
type Route (line 26) | type Route struct
type Routes (line 34) | type Routes
FILE: cmd/controller/config/config.go
function Setup (line 34) | func Setup(conf *controller.Config, kubeconfig string) {
FILE: cmd/controller/purserctrl.go
constant InClusterConfigPath (line 43) | InClusterConfigPath = ""
function init (line 47) | func init() {
function main (line 63) | func main() {
function startInteractionsDiscovery (line 77) | func startInteractionsDiscovery() {
function runDiscovery (line 93) | func runDiscovery() {
function startCronJobForUpdatingCustomGroups (line 98) | func startCronJobForUpdatingCustomGroups() {
function runGroupUpdate (line 114) | func runGroupUpdate() {
function startCronJobForPopulatingRateCard (line 118) | func startCronJobForPopulatingRateCard() {
FILE: cmd/plugin/purser.go
constant pluginVersion (line 35) | pluginVersion = "version v1.0.0"
function init (line 58) | func init() {
function main (line 115) | func main() {
function computeMetricInsight (line 126) | func computeMetricInsight(inputs []string) {
function computeCost (line 135) | func computeCost(inputs []string) {
function fetchResource (line 148) | func fetchResource(inputs []string) {
function createGroupNameFromLabel (line 179) | func createGroupNameFromLabel(input string) string {
function computeStats (line 189) | func computeStats(inputs []string) {
function getStats (line 200) | func getStats(inputs []string) {
function inputUserCosts (line 217) | func inputUserCosts(inputs []string) {
function printHelp (line 240) | func printHelp() {
function logError (line 254) | func logError(err error) {
FILE: cmd/plugin/types.go
constant Get (line 22) | Get = "get"
constant Set (line 23) | Set = "set"
constant Label (line 28) | Label = "label"
constant Pod (line 29) | Pod = "pod"
constant Node (line 30) | Node = "node"
constant Namespace (line 31) | Namespace = "namespace"
constant Group (line 32) | Group = "group"
constant Cost (line 37) | Cost = "cost"
constant Resources (line 38) | Resources = "resources"
FILE: pkg/apis/groups/v1/deepcopy.go
method DeepCopyInto (line 24) | func (in *Group) DeepCopyInto(out *Group) {
method DeepCopyObject (line 32) | func (in *Group) DeepCopyObject() runtime.Object {
method DeepCopyObject (line 39) | func (in *GroupList) DeepCopyObject() runtime.Object {
FILE: pkg/apis/groups/v1/register.go
function Kind (line 36) | func Kind(kind string) schema.GroupKind {
function Resource (line 41) | func Resource(resource string) schema.GroupResource {
function AddKnownTypes (line 46) | func AddKnownTypes(scheme *runtime.Scheme) error {
FILE: pkg/apis/groups/v1/types.go
constant CRDPlural (line 29) | CRDPlural string = "groups"
constant CRDGroup (line 30) | CRDGroup string = "vmware.purser.com"
constant CRDVersion (line 31) | CRDVersion string = "v1"
constant FullCRDName (line 32) | FullCRDName string = CRDPlural + "." + CRDGroup
type Group (line 36) | type Group struct
type GroupSpec (line 44) | type GroupSpec struct
type GroupMetrics (line 59) | type GroupMetrics struct
type Cost (line 69) | type Cost struct
type GroupList (line 77) | type GroupList struct
type GroupStatus (line 84) | type GroupStatus struct
type PodDetails (line 90) | type PodDetails struct
type PersistentVolumeClaim (line 106) | type PersistentVolumeClaim struct
type Container (line 116) | type Container struct
FILE: pkg/apis/subscriber/v1/deepcopy.go
method DeepCopyInto (line 24) | func (in *Subscriber) DeepCopyInto(out *Subscriber) {
method DeepCopyObject (line 32) | func (in *Subscriber) DeepCopyObject() runtime.Object {
method DeepCopyObject (line 39) | func (in *SubscriberList) DeepCopyObject() runtime.Object {
FILE: pkg/apis/subscriber/v1/register.go
function Kind (line 36) | func Kind(kind string) schema.GroupKind {
function Resource (line 41) | func Resource(resource string) schema.GroupResource {
function AddKnownTypes (line 46) | func AddKnownTypes(scheme *runtime.Scheme) error {
FILE: pkg/apis/subscriber/v1/types.go
constant SubscriberPlural (line 24) | SubscriberPlural string = "subscribers"
constant SubscriberGroup (line 25) | SubscriberGroup string = "vmware.purser.com"
constant SubscriberVersion (line 26) | SubscriberVersion string = "v1"
constant SubscriberFullName (line 27) | SubscriberFullName string = SubscriberPlural + "." + SubscriberGroup
type Subscriber (line 31) | type Subscriber struct
type SubscriberSpec (line 39) | type SubscriberSpec struct
type SubscriberStatus (line 46) | type SubscriberStatus struct
type SubscriberList (line 52) | type SubscriberList struct
FILE: pkg/client/clientset.go
function GetAPIExtensionClient (line 30) | func GetAPIExtensionClient(kubeconfigPath string) (*apiextcs.Clientset, ...
FILE: pkg/client/clientset/typed/groups/v1/group.go
type GroupInterface (line 30) | type GroupInterface interface
type GroupClient (line 40) | type GroupClient struct
method Create (line 48) | func (c *GroupClient) Create(obj *v1.Group) (*v1.Group, error) {
method Update (line 60) | func (c *GroupClient) Update(obj *v1.Group) (*v1.Group, error) {
method Delete (line 73) | func (c *GroupClient) Delete(name string, options *meta_v1.DeleteOptio...
method Get (line 84) | func (c *GroupClient) Get(name string) (*v1.Group, error) {
method List (line 96) | func (c *GroupClient) List(opts meta_v1.ListOptions) (*v1.GroupList, e...
method Watch (line 108) | func (c *GroupClient) Watch(opts meta_v1.ListOptions) (watch.Interface...
FILE: pkg/client/clientset/typed/groups/v1/group_client.go
function NewGroupClient (line 38) | func NewGroupClient(clientset apiextcs.Interface, config *rest.Config) *...
function Group (line 58) | func Group(client *rest.RESTClient, scheme *runtime.Scheme, namespace st...
function createGroupCRD (line 67) | func createGroupCRD(clientset apiextcs.Interface) error {
function newClient (line 89) | func newClient(cfg *rest.Config) (*rest.RESTClient, *runtime.Scheme, err...
function setConfigDefaults (line 103) | func setConfigDefaults(config *rest.Config) (*runtime.Scheme, error) {
FILE: pkg/client/clientset/typed/subscriber/v1/subsciber_client.go
function NewSubscriberClient (line 38) | func NewSubscriberClient(clientset apiextcs.Interface, config *rest.Conf...
function Subscriber (line 58) | func Subscriber(client *rest.RESTClient, scheme *runtime.Scheme, namespa...
function createSubscriberCRD (line 67) | func createSubscriberCRD(clientset apiextcs.Interface) error {
function newClient (line 89) | func newClient(cfg *rest.Config) (*rest.RESTClient, *runtime.Scheme, err...
function setConfigDefaults (line 103) | func setConfigDefaults(config *rest.Config) (*runtime.Scheme, error) {
FILE: pkg/client/clientset/typed/subscriber/v1/subscriber.go
type SubscriberInterface (line 30) | type SubscriberInterface interface
type SubscriberClient (line 40) | type SubscriberClient struct
method Create (line 48) | func (c *SubscriberClient) Create(obj *v1.Subscriber) (*v1.Subscriber,...
method Update (line 60) | func (c *SubscriberClient) Update(obj *v1.Subscriber) (*v1.Subscriber,...
method Delete (line 73) | func (c *SubscriberClient) Delete(name string, options *meta_v1.Delete...
method Get (line 84) | func (c *SubscriberClient) Get(name string) (*v1.Subscriber, error) {
method List (line 96) | func (c *SubscriberClient) List(opts meta_v1.ListOptions) (*v1.Subscri...
method Watch (line 108) | func (c *SubscriberClient) Watch(opts meta_v1.ListOptions) (watch.Inte...
FILE: pkg/controller/buffering/ring_buffer.go
constant BufferSize (line 27) | BufferSize uint32 = 5000
type RingBuffer (line 30) | type RingBuffer struct
method Put (line 38) | func (r *RingBuffer) Put(inp interface{}) bool {
method Get (line 53) | func (r *RingBuffer) Get() *interface{} {
method ReadN (line 70) | func (r *RingBuffer) ReadN(n uint32) ([]*interface{}, uint32) {
method RemoveN (line 88) | func (r *RingBuffer) RemoveN(n uint32) {
method isEmpty (line 103) | func (r *RingBuffer) isEmpty() bool {
method isFull (line 107) | func (r *RingBuffer) isFull() bool {
method PrintDetails (line 116) | func (r *RingBuffer) PrintDetails() {
function next (line 111) | func next(cur uint32, size uint32) uint32 {
FILE: pkg/controller/controller.go
type Controller (line 51) | type Controller struct
method Run (line 423) | func (c *Controller) Run(stopCh <-chan struct{}) {
method HasSynced (line 439) | func (c *Controller) HasSynced() bool {
method LastSyncResourceVersion (line 444) | func (c *Controller) LastSyncResourceVersion() string {
method runWorker (line 448) | func (c *Controller) runWorker() {
method processNextItem (line 454) | func (c *Controller) processNextItem() bool {
method processItem (line 473) | func (c *Controller) processItem(newEvent Event) error {
type Event (line 59) | type Event struct
function Start (line 69) | func Start(conf *Config) {
function newResourceController (line 377) | func newResourceController(client kubernetes.Interface, informer cache.S...
FILE: pkg/controller/controller_test.go
function TestCrdFlow (line 35) | func TestCrdFlow(t *testing.T) {
function ListSubscriberCrdInstances (line 47) | func ListSubscriberCrdInstances(crdclient *subscriber_v1.SubscriberClien...
FILE: pkg/controller/dgraph/dgraph.go
constant CREATE (line 35) | CREATE = "create"
constant UPDATE (line 36) | UPDATE = "update"
constant DELETE (line 37) | DELETE = "delete"
type ID (line 47) | type ID struct
function Start (line 53) | func Start(url string, port string) {
function Open (line 66) | func Open(url string) error {
function Close (line 80) | func Close() {
function CreateSchema (line 88) | func CreateSchema() error {
function GetUID (line 150) | func GetUID(id string, nodeType string) string {
function ExecuteQueryRaw (line 171) | func ExecuteQueryRaw(query string) ([]byte, error) {
function ExecuteQuery (line 184) | func ExecuteQuery(query string, root interface{}) error {
function MutateNode (line 199) | func MutateNode(data interface{}, mutateType string) (*api.Assigned, err...
function unmarshalDgraphResponse (line 220) | func unmarshalDgraphResponse(resp *api.Response, id string) string {
FILE: pkg/controller/dgraph/login.go
type Login (line 26) | type Login struct
constant DefaultUsername (line 35) | DefaultUsername = "admin"
constant DefaultPassword (line 36) | DefaultPassword = "purser!123"
constant DefaultLoginXID (line 37) | DefaultLoginXID = "purser-login-xid"
constant IsLogin (line 38) | IsLogin = "isLogin"
function StoreLogin (line 42) | func StoreLogin() {
FILE: pkg/controller/dgraph/models/constants.go
constant DefaultCPUCostPerCPUPerHour (line 6) | DefaultCPUCostPerCPUPerHour = "0.024"
constant DefaultMemCostPerGBPerHour (line 7) | DefaultMemCostPerGBPerHour = "0.01"
constant DefaultStorageCostPerGBPerHour (line 8) | DefaultStorageCostPerGBPerHour = "0.00013888888"
constant DefaultCPUCostInFloat64 (line 9) | DefaultCPUCostInFloat64 = 0.024
constant DefaultMemCostInFloat64 (line 10) | DefaultMemCostInFloat64 = 0.01
constant DefaultStorageCostInFloat64 (line 11) | DefaultStorageCostInFloat64 = 0.00013888888
constant AWS (line 14) | AWS = "aws"
constant HoursInMonth (line 17) | HoursInMonth = 720
constant PriceError (line 20) | PriceError = -1.0
FILE: pkg/controller/dgraph/models/container.go
constant IsContainer (line 34) | IsContainer = "isContainer"
type Container (line 38) | type Container struct
function newContainer (line 54) | func newContainer(container api_v1.Container, podUID, namespaceUID strin...
function StoreAndRetrieveContainersAndMetrics (line 78) | func StoreAndRetrieveContainersAndMetrics(pod api_v1.Pod, podUID, namesp...
function StoreContainerProcessEdge (line 106) | func StoreContainerProcessEdge(containerXID string, procsXIDs []string) ...
function storeContainerIfNotExist (line 121) | func storeContainerIfNotExist(c api_v1.Container, pod api_v1.Pod, podUID...
function deleteContainersInTerminatedPod (line 143) | func deleteContainersInTerminatedPod(containers []*Container, endTime ti...
FILE: pkg/controller/dgraph/models/daemonset.go
constant IsDaemonset (line 30) | IsDaemonset = "isDaemonset"
type Daemonset (line 34) | type Daemonset struct
function createDaemonsetObject (line 45) | func createDaemonsetObject(daemonset ext_v1beta1.DaemonSet) Daemonset {
function StoreDaemonset (line 67) | func StoreDaemonset(daemonset ext_v1beta1.DaemonSet) (string, error) {
function CreateOrGetDaemonsetByID (line 84) | func CreateOrGetDaemonsetByID(xid string) string {
FILE: pkg/controller/dgraph/models/deployment.go
constant IsDeployment (line 31) | IsDeployment = "isDeployment"
type Deployment (line 35) | type Deployment struct
function createDeploymentObject (line 46) | func createDeploymentObject(deployment apps_v1beta1.Deployment) Deployme...
function StoreDeployment (line 68) | func StoreDeployment(deployment apps_v1beta1.Deployment) (string, error) {
function CreateOrGetDeploymentByID (line 85) | func CreateOrGetDeploymentByID(xid string) string {
FILE: pkg/controller/dgraph/models/group.go
constant IsGroup (line 30) | IsGroup = "isGroup"
constant groupXIDPrefix (line 31) | groupXIDPrefix = "purser-group-"
type Group (line 35) | type Group struct
function CreateOrUpdateGroup (line 65) | func CreateOrUpdateGroup(group *groups_v1.Group, podsCount int) (*api.As...
function DeleteGroup (line 105) | func DeleteGroup(name string) {
FILE: pkg/controller/dgraph/models/job.go
constant IsJob (line 30) | IsJob = "isJob"
type Job (line 34) | type Job struct
function createJobObject (line 45) | func createJobObject(job batch_v1.Job) Job {
function StoreJob (line 67) | func StoreJob(job batch_v1.Job) (string, error) {
function CreateOrGetJobByID (line 84) | func CreateOrGetJobByID(xid string) string {
FILE: pkg/controller/dgraph/models/label.go
constant Islabel (line 27) | Islabel = "isLabel"
type Label (line 31) | type Label struct
function GetLabel (line 39) | func GetLabel(key, value string) *Label {
function CreateOrGetLabelByID (line 48) | func CreateOrGetLabelByID(key, value string) string {
function getXIDOfLabel (line 58) | func getXIDOfLabel(key, value string) string {
function createLabelObject (line 62) | func createLabelObject(key, value string) string {
FILE: pkg/controller/dgraph/models/namespace.go
constant IsNamespace (line 32) | IsNamespace = "isNamespace"
type Namespace (line 36) | type Namespace struct
function newNamespace (line 45) | func newNamespace(namespace api_v1.Namespace) Namespace {
function CreateOrGetNamespaceByID (line 64) | func CreateOrGetNamespaceByID(xid string) string {
function StoreNamespace (line 90) | func StoreNamespace(namespace api_v1.Namespace) (string, error) {
FILE: pkg/controller/dgraph/models/node.go
constant IsNode (line 33) | IsNode = "isNode"
constant DefaultNodeInstance (line 34) | DefaultNodeInstance = "purser-default"
constant DefaultNodeOS (line 35) | DefaultNodeOS = "purser-default"
constant InstanceTypeLabelKey (line 36) | InstanceTypeLabelKey = "beta.kubernetes.io/instance-type"
constant OSLabelKey (line 37) | OSLabelKey = "beta.kubernetes.io/os"
type Node (line 41) | type Node struct
function createNodeObject (line 57) | func createNodeObject(node api_v1.Node) Node {
function createOrGetNodeByID (line 83) | func createOrGetNodeByID(xid string) (string, error) {
function StoreNode (line 105) | func StoreNode(node api_v1.Node) (string, error) {
function getInstanceTypeAndOS (line 127) | func getInstanceTypeAndOS(node api_v1.Node) (string, string) {
FILE: pkg/controller/dgraph/models/pod.go
constant IsPod (line 34) | IsPod = "isPod"
type Pod (line 38) | type Pod struct
type Metrics (line 68) | type Metrics struct
function newPod (line 76) | func newPod(k8sPod api_v1.Pod) (*api.Assigned, error) {
function StorePod (line 99) | func StorePod(k8sPod api_v1.Pod) error {
function StorePodsInteraction (line 150) | func StorePodsInteraction(sourcePodXID string, destinationPodsXIDs []str...
function retrievePodsFromPodsXIDs (line 166) | func retrievePodsFromPodsXIDs(podsXIDs []string) []*Pod {
function retrievePodsWithCountAsEdgeWeightFromPodsXIDs (line 182) | func retrievePodsWithCountAsEdgeWeightFromPodsXIDs(podsXIDs []string, co...
function setPodOwners (line 200) | func setPodOwners(pod *Pod, k8sPod api_v1.Pod) {
function updateDeploymentAsPodOwner (line 221) | func updateDeploymentAsPodOwner(pod *Pod, ownerXID string) {
function updateReplicasetAsPodOwner (line 228) | func updateReplicasetAsPodOwner(pod *Pod, ownerXID string) {
function updateStatefulsetAsPodOwner (line 235) | func updateStatefulsetAsPodOwner(pod *Pod, ownerXID string) {
function updateJobAsPodOwner (line 242) | func updateJobAsPodOwner(pod *Pod, ownerXID string) {
function updateDaemonsetAsPodOwner (line 249) | func updateDaemonsetAsPodOwner(pod *Pod, ownerXID string) {
function getPodVolumes (line 256) | func getPodVolumes(k8sPod api_v1.Pod) ([]*PersistentVolumeClaim, float64) {
function populatePodLabels (line 278) | func populatePodLabels(pod *Pod, podLabels map[string]string) {
function RetrievePodWithContainers (line 288) | func RetrievePodWithContainers(xid string) Pod {
FILE: pkg/controller/dgraph/models/pod_test.go
function TestStorePodsInteraction (line 28) | func TestStorePodsInteraction(t *testing.T) {
FILE: pkg/controller/dgraph/models/process.go
constant IsProc (line 33) | IsProc = "isProc"
type Proc (line 37) | type Proc struct
function newProc (line 49) | func newProc(procXID, procName, containerUID, containerXID string, creat...
function StoreProcess (line 62) | func StoreProcess(procXID, containerXID string, podsXIDs []string, creat...
function deleteProcessesInTerminatedContainers (line 90) | func deleteProcessesInTerminatedContainers(containers []*Container) {
function retrieveProcessesFromProcessesXIDs (line 107) | func retrieveProcessesFromProcessesXIDs(procsXIDs []string) []*Proc {
FILE: pkg/controller/dgraph/models/pv.go
constant IsPersistentVolume (line 35) | IsPersistentVolume = "isPersistentVolume"
type PersistentVolume (line 39) | type PersistentVolume struct
function createPersistentVolumeObject (line 50) | func createPersistentVolumeObject(pv api_v1.PersistentVolume, client *ku...
function StorePersistentVolume (line 73) | func StorePersistentVolume(pv api_v1.PersistentVolume, client *kubernete...
function CreateOrGetPersistentVolumeByID (line 90) | func CreateOrGetPersistentVolumeByID(xid string) string {
FILE: pkg/controller/dgraph/models/pvc.go
constant IsPersistentVolumeClaim (line 32) | IsPersistentVolumeClaim = "isPersistentVolumeClaim"
type PersistentVolumeClaim (line 36) | type PersistentVolumeClaim struct
function createPvcObject (line 48) | func createPvcObject(pvc api_v1.PersistentVolumeClaim) PersistentVolumeC...
function StorePersistentVolumeClaim (line 79) | func StorePersistentVolumeClaim(pvc api_v1.PersistentVolumeClaim) (strin...
function CreateOrGetPersistentVolumeClaimByID (line 96) | func CreateOrGetPersistentVolumeClaimByID(xid string) string {
function getPVCFromUID (line 119) | func getPVCFromUID(uid string) (PersistentVolumeClaim, error) {
FILE: pkg/controller/dgraph/models/query/cluster.go
function getClusterHierarchyQuery (line 30) | func getClusterHierarchyQuery(view string) string {
function RetrieveClusterHierarchy (line 42) | func RetrieveClusterHierarchy(view string) JSONDataWrapper {
function getClusterMetricsQuery (line 62) | func getClusterMetricsQuery(view string) string {
function RetrieveClusterMetrics (line 75) | func RetrieveClusterMetrics(view string) JSONDataWrapper {
function calculateAggregateMetrics (line 101) | func calculateAggregateMetrics(objRoot *ParentWrapper) {
function PopulateClusterAllocationAndCapacity (line 113) | func PopulateClusterAllocationAndCapacity(jsonData *JSONDataWrapper) {
function ComputeClusterAllocationAndCapacity (line 121) | func ComputeClusterAllocationAndCapacity() {
method PopulateNodeOrPVAllocationAndCapacity (line 135) | func (r *Resource) PopulateNodeOrPVAllocationAndCapacity(jsonData *JSOND...
function populateCapacityData (line 141) | func populateCapacityData(allocatedAndCapacityData ParentWrapper, jsonDa...
FILE: pkg/controller/dgraph/models/query/cluster_test.go
function mockSecondsSinceMonthStart (line 30) | func mockSecondsSinceMonthStart() {
function removeMocks (line 36) | func removeMocks() {
function TestMain (line 43) | func TestMain(m *testing.M) {
function mockDgraphForClusterQueries (line 50) | func mockDgraphForClusterQueries(queryType string) {
function TestRetrieveClusterHierarchyNoView (line 109) | func TestRetrieveClusterHierarchyNoView(t *testing.T) {
function TestRetrieveClusterHierarchyLogicalView (line 117) | func TestRetrieveClusterHierarchyLogicalView(t *testing.T) {
function TestRetrieveClusterHierarchyPhysicalView (line 140) | func TestRetrieveClusterHierarchyPhysicalView(t *testing.T) {
function TestRetrieveClusterMetricsNoView (line 163) | func TestRetrieveClusterMetricsNoView(t *testing.T) {
function TestRetrieveClusterMetricsLogicalView (line 171) | func TestRetrieveClusterMetricsLogicalView(t *testing.T) {
function TestRetrieveClusterMetricsPhysicalView (line 212) | func TestRetrieveClusterMetricsPhysicalView(t *testing.T) {
function TestPopulateClusterAllocationAndCapacityNil (line 252) | func TestPopulateClusterAllocationAndCapacityNil(t *testing.T) {
function TestPopulateClusterAllocationAndCapacity (line 273) | func TestPopulateClusterAllocationAndCapacity(t *testing.T) {
function resetAllocatedAndCapacity (line 291) | func resetAllocatedAndCapacity(old *ParentWrapper) {
function TestPopulateNodeOrPVAllocationAndCapacity (line 295) | func TestPopulateNodeOrPVAllocationAndCapacity(t *testing.T) {
function getTestAllocatedCapacityData (line 308) | func getTestAllocatedCapacityData() JSONDataWrapper {
FILE: pkg/controller/dgraph/models/query/constants_test.go
constant testSecondsSinceMonthStart (line 21) | testSecondsSinceMonthStart = "1.45"
constant testPodUIDList (line 22) | testPodUIDList = "0x3e283, 0x3e288"
constant testPodName (line 23) | testPodName = "pod-purser-dgraph-0"
constant testDaemonsetName (line 24) | testDaemonsetName = "daemonset-purser"
constant testResourceName (line 25) | testResourceName = "resource-purser"
constant testPodUID (line 26) | testPodUID = "0x3e283"
constant testPodXID (line 27) | testPodXID = "purser:pod-purser-dgraph-0"
constant testHierarchy (line 29) | testHierarchy = "hierarchy"
constant testMetrics (line 30) | testMetrics = "metrics"
constant testRetrieveAllGroups (line 31) | testRetrieveAllGroups = "retrieveAllGroups"
constant testRetrieveGroupMetrics (line 32) | testRetrieveGroupMetrics = "retrieveGroupMetrics"
constant testRetrieveSubscribers (line 33) | testRetrieveSubscribers = "retrieveSubscribers"
constant testLabelFilterPods (line 34) | testLabelFilterPods = "labelFilterPods"
constant testAlivePods (line 35) | testAlivePods = "alivePods"
constant testPodInteractions (line 36) | testPodInteractions = "podInteractions"
constant testPodPrices (line 37) | testPodPrices = "podPrices"
constant testCapacity (line 38) | testCapacity = "capacityAllocation"
constant testWrongQuery (line 39) | testWrongQuery = "wrongQuery"
constant testCPUPrice (line 40) | testCPUPrice = 0.24
constant testMemoryPrice (line 41) | testMemoryPrice = 0.1
FILE: pkg/controller/dgraph/models/query/group.go
type GroupMetrics (line 27) | type GroupMetrics struct
type groupsRoot (line 53) | type groupsRoot struct
type groupJSONMetrics (line 57) | type groupJSONMetrics struct
function RetrieveGroupsData (line 63) | func RetrieveGroupsData() ([]models.Group, error) {
function RetrieveGroupMetricsFromPodUIDs (line 75) | func RetrieveGroupMetricsFromPodUIDs(podsUIDs string) (GroupMetrics, err...
function convertToGroupMetrics (line 86) | func convertToGroupMetrics(jsonMetrics []map[string]float64) GroupMetrics {
function populateMetric (line 99) | func populateMetric(groupMetrics *GroupMetrics, key string, value float6...
FILE: pkg/controller/dgraph/models/query/group_test.go
function mockMetricsMap (line 31) | func mockMetricsMap(key string, value float64) map[string]float64 {
function mockDgraphForGroupQueries (line 37) | func mockDgraphForGroupQueries(queryType string) {
function TestRetrieveGroupsDataWithDgraphError (line 89) | func TestRetrieveGroupsDataWithDgraphError(t *testing.T) {
function TestRetrieveGroupsData (line 96) | func TestRetrieveGroupsData(t *testing.T) {
function TestGroupMetricsFromPodUIDsWithDgraphError (line 118) | func TestGroupMetricsFromPodUIDsWithDgraphError(t *testing.T) {
function TestGroupMetricsFromPodUIDs (line 125) | func TestGroupMetricsFromPodUIDs(t *testing.T) {
FILE: pkg/controller/dgraph/models/query/helpers.go
function getSecondsSinceMonthStart (line 29) | func getSecondsSinceMonthStart() string {
function getSecondsSinceForOtherMonths (line 33) | func getSecondsSinceForOtherMonths() map[string]string {
function getQueryForMetricsComputationWithAliasAndVariables (line 49) | func getQueryForMetricsComputationWithAliasAndVariables(suffix string) s...
function getQueryForMetricsComputationWithAlias (line 59) | func getQueryForMetricsComputationWithAlias(suffix string) string {
function getQueryForMetricsComputation (line 69) | func getQueryForMetricsComputation(suffix string) string {
function getQueryForTimeComputation (line 77) | func getQueryForTimeComputation(suffix string) string {
function getQueryForCostWithPriceWithAliasAndVariables (line 88) | func getQueryForCostWithPriceWithAliasAndVariables(suffix string) string {
function getQueryForCostWithPriceWithAlias (line 96) | func getQueryForCostWithPriceWithAlias(suffix string) string {
function getQueryForCostWithPrice (line 104) | func getQueryForCostWithPrice(suffix string) string {
function getQueryForAggregatingChildMetricsWithAlias (line 112) | func getQueryForAggregatingChildMetricsWithAlias(childSuffix string) str...
function getQueryForAggregatingChildMetrics (line 123) | func getQueryForAggregatingChildMetrics(parentSuffix, childSuffix string...
function getQueryFromSubQueryWithAlias (line 132) | func getQueryFromSubQueryWithAlias(suffix string) string {
method getQueryForPodParentMetrics (line 143) | func (r *Resource) getQueryForPodParentMetrics() string {
method getQueryForHierarchy (line 154) | func (r *Resource) getQueryForHierarchy() string {
FILE: pkg/controller/dgraph/models/query/helpers_test.go
function TestGetSecondsSinceMonthStart (line 28) | func TestGetSecondsSinceMonthStart(t *testing.T) {
FILE: pkg/controller/dgraph/models/query/label.go
function CreateFilterFromListOfLabels (line 22) | func CreateFilterFromListOfLabels(labels map[string][]string) string {
function createFilterFromLabel (line 40) | func createFilterFromLabel(key, value string) string {
FILE: pkg/controller/dgraph/models/query/label_test.go
function TestCreateFilterFromLabel (line 27) | func TestCreateFilterFromLabel(t *testing.T) {
function TestCreateFilterFromListOfLabels (line 34) | func TestCreateFilterFromListOfLabels(t *testing.T) {
FILE: pkg/controller/dgraph/models/query/login.go
function Authenticate (line 28) | func Authenticate(username, inputPassword string) bool {
function UpdatePassword (line 41) | func UpdatePassword(username, oldPassword, newPassword string) bool {
function hashAndUpdatePassword (line 56) | func hashAndUpdatePassword(login *dgraph.Login, newPassword string) error {
function getLoginCredentials (line 67) | func getLoginCredentials(username string) (dgraph.Login, error) {
function validateUsername (line 85) | func validateUsername(username string) bool {
function comparePasswords (line 89) | func comparePasswords(hashedPwd string, plainPwd []byte) bool {
FILE: pkg/controller/dgraph/models/query/pod.go
type podRoot (line 25) | type podRoot struct
function RetrieveAllLivePods (line 31) | func RetrieveAllLivePods() []models.Pod {
function RetrievePodsInteractions (line 43) | func RetrievePodsInteractions(name string, isOrphan bool) []byte {
function getPricePerResourceForPod (line 93) | func getPricePerResourceForPod(name string) (float64, float64) {
function RetrievePodsInteractionsForAllLivePodsWithCount (line 111) | func RetrievePodsInteractionsForAllLivePodsWithCount() ([]models.Pod, er...
function RetrievePodsUIDsByLabelsFilter (line 137) | func RetrievePodsUIDsByLabelsFilter(labelFilter string) ([]string, error) {
function removeDuplicates (line 147) | func removeDuplicates(pods []models.Pod) []string {
FILE: pkg/controller/dgraph/models/query/pod_test.go
function mockDgraphForPodQueries (line 31) | func mockDgraphForPodQueries(queryType string) {
function TestRetrievePodsUIDsByLabelsFilterWithError (line 68) | func TestRetrievePodsUIDsByLabelsFilterWithError(t *testing.T) {
function TestRetrievePodsUIDsByLabelsFilter (line 81) | func TestRetrievePodsUIDsByLabelsFilter(t *testing.T) {
function TestRetrieveAllLivePodsWithDgraphError (line 96) | func TestRetrieveAllLivePodsWithDgraphError(t *testing.T) {
function TestRetrieveAllLivePods (line 103) | func TestRetrieveAllLivePods(t *testing.T) {
function TestPodInteractionsErrorCase (line 115) | func TestPodInteractionsErrorCase(t *testing.T) {
function TestGetPricePerResourceForPodWithError (line 127) | func TestGetPricePerResourceForPodWithError(t *testing.T) {
function TestGetPricePerResourceForPod (line 135) | func TestGetPricePerResourceForPod(t *testing.T) {
FILE: pkg/controller/dgraph/models/query/queries.go
function getQueryForDeploymentMetrics (line 25) | func getQueryForDeploymentMetrics(name string) string {
function getQueryForPodMetrics (line 47) | func getQueryForPodMetrics(name, cpuPrice, memoryPrice string) string {
function getQueryForContainerMetrics (line 65) | func getQueryForContainerMetrics(name string) string {
function getQueryForPVMetrics (line 80) | func getQueryForPVMetrics(name string) string {
function getQueryForPVCMetrics (line 102) | func getQueryForPVCMetrics(name string) string {
function getQueryForNodeMetrics (line 115) | func getQueryForNodeMetrics(name string) string {
function getQueryForNamespaceMetrics (line 137) | func getQueryForNamespaceMetrics(name string) string {
function getMetricsQueryForLogicalResources (line 188) | func getMetricsQueryForLogicalResources() string {
function getMetricsQueryForPhysicalResources (line 204) | func getMetricsQueryForPhysicalResources() string {
function getHierarchyQueryForLogicalResource (line 219) | func getHierarchyQueryForLogicalResource() string {
function getHierarchyQueryForPhysicalResource (line 229) | func getHierarchyQueryForPhysicalResource() string {
function getQueryForAllGroupsData (line 242) | func getQueryForAllGroupsData() string {
function getQueryForGroupMetrics (line 273) | func getQueryForGroupMetrics(podsUIDs string) string {
function getQueryForSubscribersRetrieval (line 358) | func getQueryForSubscribersRetrieval() string {
function getAllLivePodsQuery (line 370) | func getAllLivePodsQuery() string {
function getQueryForPodsWithLabelFilter (line 380) | func getQueryForPodsWithLabelFilter(labelFilter string) string {
FILE: pkg/controller/dgraph/models/query/resource.go
constant ContainerCheck (line 28) | ContainerCheck = "isContainer"
constant ContainerType (line 29) | ContainerType = "container"
constant IsProcFilter (line 30) | IsProcFilter = "@filter(has(isProc))"
constant DaemonsetCheck (line 32) | DaemonsetCheck = "isDaemonset"
constant DaemonsetType (line 33) | DaemonsetType = "daemonset"
constant IsPodFilter (line 34) | IsPodFilter = "@filter(has(isPod))"
constant DeploymentCheck (line 36) | DeploymentCheck = "isDeployment"
constant DeploymentType (line 37) | DeploymentType = "deployment"
constant IsReplicasetFilter (line 38) | IsReplicasetFilter = "@filter(has(isReplicaset))"
constant JobCheck (line 40) | JobCheck = "isJob"
constant JobType (line 41) | JobType = "job"
constant NamespaceCheck (line 43) | NamespaceCheck = "isNamespace"
constant NamespaceType (line 44) | NamespaceType = "namespace"
constant NamespaceChildFilter (line 45) | NamespaceChildFilter = "@filter(has(isDeployment) OR has(isStatefulset) ...
constant NodeCheck (line 47) | NodeCheck = "isNode"
constant NodeType (line 48) | NodeType = "node"
constant PodCheck (line 50) | PodCheck = "isPod"
constant PodType (line 51) | PodType = "pod"
constant IsContainerFilter (line 52) | IsContainerFilter = "@filter(has(isContainer))"
constant PVCheck (line 54) | PVCheck = "isPersistentVolume"
constant PVType (line 55) | PVType = "pv"
constant IsPVCFilter (line 56) | IsPVCFilter = "@filter(has(isPersistentVolumeClaim))"
constant PVCCheck (line 58) | PVCCheck = "isPersistentVolumeClaim"
constant PVCType (line 59) | PVCType = "pvc"
constant ReplicasetCheck (line 61) | ReplicasetCheck = "isReplicaset"
constant ReplicasetType (line 62) | ReplicasetType = "replicaset"
constant StatefulsetCheck (line 64) | StatefulsetCheck = "isStatefulset"
constant StatefulsetType (line 65) | StatefulsetType = "statefulset"
type Resource (line 69) | type Resource struct
method RetrieveResourceHierarchy (line 77) | func (r *Resource) RetrieveResourceHierarchy() JSONDataWrapper {
method RetrieveResourceMetrics (line 87) | func (r *Resource) RetrieveResourceMetrics() JSONDataWrapper {
method getQueryForResourceMetrics (line 96) | func (r *Resource) getQueryForResourceMetrics() string {
function getJSONDataFromQuery (line 120) | func getJSONDataFromQuery(query string) JSONDataWrapper {
FILE: pkg/controller/dgraph/models/query/resource_test.go
function mockDgraphForResourceQueries (line 29) | func mockDgraphForResourceQueries(queryType, resourceName, resourceType ...
function TestRetrieveResourceHierarchyWithNameEmpty (line 106) | func TestRetrieveResourceHierarchyWithNameEmpty(t *testing.T) {
function TestRetrieveResourceHierarchy (line 119) | func TestRetrieveResourceHierarchy(t *testing.T) {
function TestRetrieveResourceHierarchyWithDgraphError (line 149) | func TestRetrieveResourceHierarchyWithDgraphError(t *testing.T) {
function TestRetrieveResourceMetricsWithNameEmpty (line 164) | func TestRetrieveResourceMetricsWithNameEmpty(t *testing.T) {
function TestRetrieveDaemonsetMetrics (line 176) | func TestRetrieveDaemonsetMetrics(t *testing.T) {
function TestRetrieveDeploymentMetrics (line 191) | func TestRetrieveDeploymentMetrics(t *testing.T) {
function TestRetrieveNamespacetMetrics (line 206) | func TestRetrieveNamespacetMetrics(t *testing.T) {
function TestRetrievePVMetrics (line 221) | func TestRetrievePVMetrics(t *testing.T) {
function TestRetrievePVCMetrics (line 236) | func TestRetrievePVCMetrics(t *testing.T) {
function TestRetrieveContainerMetrics (line 251) | func TestRetrieveContainerMetrics(t *testing.T) {
function TestRetrieveNodeMetrics (line 266) | func TestRetrieveNodeMetrics(t *testing.T) {
function TestRetrievePodMetrics (line 281) | func TestRetrievePodMetrics(t *testing.T) {
function getExpectedTestMetrics (line 295) | func getExpectedTestMetrics(name, resourceType string) JSONDataWrapper {
FILE: pkg/controller/dgraph/models/query/subscriber.go
type subscriberRoot (line 24) | type subscriberRoot struct
function RetrieveSubscribers (line 29) | func RetrieveSubscribers() ([]models.SubscriberCRD, error) {
FILE: pkg/controller/dgraph/models/query/subscriber_test.go
function mockDgraphForSubscriberQueries (line 29) | func mockDgraphForSubscriberQueries(queryType string) {
function TestRetrieveSubscribersWithDgraphError (line 52) | func TestRetrieveSubscribersWithDgraphError(t *testing.T) {
function TestRetrieveSubscribers (line 59) | func TestRetrieveSubscribers(t *testing.T) {
FILE: pkg/controller/dgraph/models/query/types.go
constant All (line 22) | All = ""
constant Name (line 23) | Name = "name"
constant Orphan (line 24) | Orphan = "orphan"
constant View (line 25) | View = "view"
constant Physical (line 26) | Physical = "physical"
constant Logical (line 27) | Logical = "logical"
constant False (line 28) | False = "false"
type Children (line 32) | type Children struct
type ParentWrapper (line 44) | type ParentWrapper struct
type JSONDataWrapper (line 64) | type JSONDataWrapper struct
FILE: pkg/controller/dgraph/models/rateCard.go
constant IsRateCard (line 29) | IsRateCard = "isRateCard"
constant IsNodePrice (line 30) | IsNodePrice = "isNodePrice"
constant IsStoragePrice (line 31) | IsStoragePrice = "isStoragePrice"
constant RateCardXID (line 32) | RateCardXID = "purser-rateCard"
type RateCard (line 36) | type RateCard struct
type NodePrice (line 47) | type NodePrice struct
type StoragePrice (line 60) | type StoragePrice struct
function StoreRateCard (line 69) | func StoreRateCard(rateCard *RateCard) {
function StoreNodePrice (line 87) | func StoreNodePrice(nodePrice *NodePrice, productXID string) string {
function StoreStoragePrice (line 107) | func StoreStoragePrice(storagePrice *StoragePrice, productXID string) st...
function retrieveNode (line 127) | func retrieveNode(name string) (*Node, error) {
function retrieveNodePrice (line 155) | func retrieveNodePrice(xid string) (*NodePrice, error) {
function getPerUnitResourcePriceForNode (line 181) | func getPerUnitResourcePriceForNode(nodeName string) (float64, float64) {
function getPricePerUnitResourceFromNodePrice (line 189) | func getPricePerUnitResourceFromNodePrice(node Node) (float64, float64) {
FILE: pkg/controller/dgraph/models/replicaset.go
constant IsReplicaset (line 30) | IsReplicaset = "isReplicaset"
type Replicaset (line 34) | type Replicaset struct
function createReplicasetObject (line 46) | func createReplicasetObject(replicaset ext_v1beta1.ReplicaSet) Replicaset {
function StoreReplicaset (line 69) | func StoreReplicaset(replicaset ext_v1beta1.ReplicaSet) (string, error) {
function setReplicasetOwners (line 84) | func setReplicasetOwners(r *Replicaset, replicaset ext_v1beta1.ReplicaSe...
function CreateOrGetReplicasetByID (line 104) | func CreateOrGetReplicasetByID(xid string) string {
FILE: pkg/controller/dgraph/models/service.go
constant IsService (line 32) | IsService = "isService"
type Service (line 36) | type Service struct
function newService (line 48) | func newService(svc api_v1.Service) (*api.Assigned, error) {
function StoreService (line 64) | func StoreService(service api_v1.Service) error {
function StoreServicesInteraction (line 92) | func StoreServicesInteraction(sourceServiceXID string, destinationServic...
function StorePodServiceEdges (line 109) | func StorePodServiceEdges(svcXID string, podsXIDsInService []string) err...
function RetrieveAllServices (line 124) | func RetrieveAllServices() ([]Service, error) {
function RetrieveAllServicesWithDstPods (line 150) | func RetrieveAllServicesWithDstPods() ([]Service, error) {
function RetrieveServiceList (line 177) | func RetrieveServiceList() ([]Service, error) {
function retrieveServicesFromServicesXIDs (line 196) | func retrieveServicesFromServicesXIDs(svcsXIDs []string) []*Service {
FILE: pkg/controller/dgraph/models/statefulset.go
constant IsStatefulset (line 30) | IsStatefulset = "isStatefulset"
type Statefulset (line 34) | type Statefulset struct
function createStatefulsetObject (line 45) | func createStatefulsetObject(statefulset apps_v1beta1.StatefulSet) State...
function StoreStatefulset (line 67) | func StoreStatefulset(statefulset apps_v1beta1.StatefulSet) (string, err...
function CreateOrGetStatefulsetByID (line 84) | func CreateOrGetStatefulsetByID(xid string) string {
FILE: pkg/controller/dgraph/models/subscriber.go
constant IsSubscriber (line 30) | IsSubscriber = "isSubscriber"
type SubscriberCRD (line 34) | type SubscriberCRD struct
type SubscriberSpec (line 45) | type SubscriberSpec struct
function createSubscriberCRDObject (line 51) | func createSubscriberCRDObject(subscriber subscribers_v1.Subscriber) Sub...
function StoreSubscriberCRD (line 73) | func StoreSubscriberCRD(subscriber subscribers_v1.Subscriber) (string, e...
FILE: pkg/controller/dgraph/purge.go
type resource (line 27) | type resource struct
function RemoveResourcesInactive (line 33) | func RemoveResourcesInactive() {
function removeOldDeletedResources (line 45) | func removeOldDeletedResources() error {
function removeOldDeletedPods (line 59) | func removeOldDeletedPods() error {
function retrieveResourcesWithEndTimeBeforeCurrentMonthStart (line 73) | func retrieveResourcesWithEndTimeBeforeCurrentMonthStart() ([]resource, ...
function retrievePodsWithEndTimeBeforeThreeMonths (line 91) | func retrievePodsWithEndTimeBeforeThreeMonths() ([]resource, error) {
FILE: pkg/controller/discovery/executer/exec.go
function ExecToPodThroughAPI (line 35) | func ExecToPodThroughAPI(conf controller.Config, pod corev1.Pod, command...
FILE: pkg/controller/discovery/generator/graph.go
type Node (line 33) | type Node struct
type Edge (line 46) | type Edge struct
function GetGraphNodes (line 59) | func GetGraphNodes() []Node {
function GetGraphEdges (line 64) | func GetGraphEdges() []Edge {
function GeneratePodNodesAndEdges (line 69) | func GeneratePodNodesAndEdges(pods []models.Pod) {
function getPodUniqueIDsAndNumConnections (line 78) | func getPodUniqueIDsAndNumConnections(pods []models.Pod) (map[string]int...
function setPodUniqueIDsAndNumConnections (line 88) | func setPodUniqueIDsAndNumConnections(pod models.Pod, uniqueIDs, numConn...
function createPodNodes (line 101) | func createPodNodes(pods []models.Pod, uniqueIDs, numConnections, inboun...
function createPodEdges (line 120) | func createPodEdges(pods []models.Pod, uniqueIDs map[string]int) []Edge {
function createPodNode (line 132) | func createPodNode(podName string, podID int, podConnections int, cid []...
function createPodEdge (line 143) | func createPodEdge(fromID int, toID int, count int) Edge {
function setGraphNodes (line 151) | func setGraphNodes(podNodes []Node) {
function setGraphEdges (line 155) | func setGraphEdges(podEdges []Edge) {
FILE: pkg/controller/discovery/linker/podlinks.go
type InteractionsWrapper (line 31) | type InteractionsWrapper struct
constant KeySpliter (line 50) | KeySpliter = ":"
function PopulatePodIPTable (line 54) | func PopulatePodIPTable(pods *corev1.PodList) {
function GenerateAndStorePodInteractions (line 62) | func GenerateAndStorePodInteractions() {
function PopulateMappingTables (line 80) | func PopulateMappingTables(tcpDump []string, pod corev1.Pod, process Pro...
function updatePodInteractions (line 94) | func updatePodInteractions(srcName, dstName string, interactions *Intera...
function UpdatePodToPodTable (line 110) | func UpdatePodToPodTable(podInteractions map[string](map[string]float64)) {
FILE: pkg/controller/discovery/linker/processlinks.go
type Process (line 28) | type Process struct
function StoreProcessInteractions (line 33) | func StoreProcessInteractions(containerProcessInteraction map[string][]s...
function populateContainerProcessTable (line 53) | func populateContainerProcessTable(containerXID, procXID string, interac...
function updatePodProcessInteractions (line 60) | func updatePodProcessInteractions(procXID, dstName string, interactions ...
FILE: pkg/controller/discovery/linker/servicelinks.go
function PopulatePodToServiceTable (line 35) | func PopulatePodToServiceTable(svc corev1.Service, pods *corev1.PodList) {
function GenerateAndStoreSvcInteractions (line 54) | func GenerateAndStoreSvcInteractions() {
function getDestinationPods (line 70) | func getDestinationPods(podsInService []*models.Pod) []*models.Pod {
function getServicesXIDsFromPods (line 78) | func getServicesXIDsFromPods(pods []*models.Pod) []string {
FILE: pkg/controller/discovery/processor/container.go
function processContainerDetails (line 34) | func processContainerDetails(conf controller.Config, pod corev1.Pod, con...
function getPIDList (line 50) | func getPIDList(conf controller.Config, pod corev1.Pod, containerName st...
function getProcessDump (line 74) | func getProcessDump(conf controller.Config, pod corev1.Pod, containerNam...
function executeCommandInPod (line 95) | func executeCommandInPod(conf controller.Config, pod corev1.Pod, command...
FILE: pkg/controller/discovery/processor/pod.go
constant maxGoRoutines (line 33) | maxGoRoutines = 20
function ProcessPodInteractions (line 39) | func ProcessPodInteractions(conf controller.Config) {
function processPodDetails (line 53) | func processPodDetails(conf controller.Config, pods *corev1.PodList) {
FILE: pkg/controller/discovery/processor/svc.go
function ProcessServiceInteractions (line 39) | func ProcessServiceInteractions(conf controller.Config) {
function processServiceDetails (line 52) | func processServiceDetails(client *kubernetes.Clientset, services *corev...
FILE: pkg/controller/eventprocessor/notifier.go
constant ReadSize (line 33) | ReadSize uint32 = 50
type notifier (line 35) | type notifier struct
method createNewRequest (line 58) | func (n notifier) createNewRequest(payload []*interface{}) (*http.Requ...
method setReqHeaders (line 90) | func (n *notifier) setReqHeaders(r *http.Request) {
function notifySubscribers (line 40) | func notifySubscribers(payload []*interface{}, subscribers []models.Subs...
function sendData (line 74) | func sendData(req *http.Request) error {
function getNotifiers (line 97) | func getNotifiers(subscribers []models.SubscriberCRD) []*notifier {
function retry (line 113) | func retry(attempts int, sleep time.Duration, fn func() error) error {
FILE: pkg/controller/eventprocessor/processor.go
function ProcessEvents (line 40) | func ProcessEvents(conf *controller.Config) {
function ProcessPayloads (line 70) | func ProcessPayloads(payloads []*interface{}, conf *controller.Config) {
function handlePayloadBasedOnResource (line 78) | func handlePayloadBasedOnResource(payload *controller.Payload, conf *con...
function unmarshalPayload (line 137) | func unmarshalPayload(payload *controller.Payload, resource interface{}) {
function checkDgraphError (line 144) | func checkDgraphError(resource string, err error) {
function handlePayloadForGroup (line 150) | func handlePayloadForGroup(payload *controller.Payload, conf *controller...
FILE: pkg/controller/eventprocessor/sync.go
function SyncCluster (line 36) | func SyncCluster(kubeClient *kubernetes.Clientset) {
function syncPods (line 42) | func syncPods(kubeClient *kubernetes.Clientset, endTime string) {
function handleDeadPodsAndNewPods (line 60) | func handleDeadPodsAndNewPods(livePodsFromDgraph []models.Pod, podsInClu...
FILE: pkg/controller/eventprocessor/updater.go
function UpdateGroups (line 36) | func UpdateGroups(groupCRDClient *groupsClient_v1.GroupClient) {
function UpdateGroup (line 50) | func UpdateGroup(group *groups_v1.Group, groupCRDClient *groupsClient_v1...
function getGroupMetrics (line 108) | func getGroupMetrics(group *groups_v1.Group) query.GroupMetrics {
function getPodUIDsFromAllExpressions (line 136) | func getPodUIDsFromAllExpressions(expressions map[string]map[string][]st...
function mapPodUIDsToNumberOfOccurences (line 149) | func mapPodUIDsToNumberOfOccurences(podsFromExpressions [][]string) map[...
function getUIDQueryForPods (line 165) | func getUIDQueryForPods(podsUIDsCounter map[string]int, expressionsCount...
FILE: pkg/controller/metrics/metrics.go
type Metrics (line 27) | type Metrics struct
function CalculatePodStatsFromContainers (line 35) | func CalculatePodStatsFromContainers(pod *api_v1.Pod) *Metrics {
function PrintPodStats (line 62) | func PrintPodStats(pod *api_v1.Pod, metrics *Metrics) {
FILE: pkg/controller/payload.go
type PayloadWrapper (line 23) | type PayloadWrapper struct
type Payload (line 28) | type Payload struct
FILE: pkg/controller/persistentVolume.go
function UpdatePodVolumeClaims (line 39) | func UpdatePodVolumeClaims(pod api_v1.Pod, podDetails groups_v1.PodDetai...
function getactivePodVolumeClaims (line 81) | func getactivePodVolumeClaims(pod api_v1.Pod) map[string]*groups_v1.Pers...
function collectPersistentVolumeClaim (line 95) | func collectPersistentVolumeClaim(claimName, namespace string) *groups_v...
function PvcHandlePodDeletion (line 116) | func PvcHandlePodDeletion(podDetails *groups_v1.PodDetails) {
function checkBounded (line 128) | func checkBounded(pvc *groups_v1.PersistentVolumeClaim) bool {
function isPVCError (line 133) | func isPVCError(err error, claimName string) bool {
FILE: pkg/controller/types.go
constant Create (line 30) | Create = "create"
constant Delete (line 31) | Delete = "delete"
constant Update (line 32) | Update = "update"
type Resource (line 36) | type Resource struct
type Config (line 53) | type Config struct
FILE: pkg/controller/utils/jsonutils.go
function JSONMarshal (line 28) | func JSONMarshal(obj interface{}) []byte {
function GetJSONResponse (line 38) | func GetJSONResponse(client *http.Client, url string, target interface{}...
function closeResponse (line 48) | func closeResponse(resp *http.Response) {
FILE: pkg/controller/utils/k8sUtils.go
constant StorageDefault (line 35) | StorageDefault = "purser-default"
function RetrievePodList (line 39) | func RetrievePodList(client *kubernetes.Clientset, options metav1.ListOp...
function RetrieveNodeList (line 49) | func RetrieveNodeList(client *kubernetes.Clientset, options metav1.ListO...
function RetrieveServiceList (line 59) | func RetrieveServiceList(client *kubernetes.Clientset, options metav1.Li...
function RetrieveGroupList (line 69) | func RetrieveGroupList(groupClient *groups.GroupClient, options metav1.L...
function RetrieveStorageClass (line 79) | func RetrieveStorageClass(client *kubernetes.Clientset, options metav1.G...
function GetFinalStorageTypeOfPV (line 98) | func GetFinalStorageTypeOfPV(pv api_v1.PersistentVolume, client *kuberne...
function getFinalTypeOfStorageClass (line 106) | func getFinalTypeOfStorageClass(client *kubernetes.Clientset, storageCla...
FILE: pkg/controller/utils/purge.go
function PurgeTCPData (line 30) | func PurgeTCPData(data string) []string {
function PurgeTCP6Data (line 50) | func PurgeTCP6Data(data string) []string {
function getTCPDumpHexFromData (line 68) | func getTCPDumpHexFromData(data string) []string {
function hexToDecIP (line 79) | func hexToDecIP(hexIP string) string {
function isLocalHost (line 87) | func isLocalHost(localIP, remoteIP string) bool {
FILE: pkg/controller/utils/purge_test.go
function TestHexToDecIP (line 26) | func TestHexToDecIP(t *testing.T) {
FILE: pkg/controller/utils/timeUtils.go
function GetCurrentMonthStartTime (line 23) | func GetCurrentMonthStartTime() time.Time {
function ConverTimeToRFC3339 (line 30) | func ConverTimeToRFC3339(queryTime time.Time) string {
function GetSecondsSince (line 35) | func GetSecondsSince(queryTime time.Time) float64 {
function GetHoursRemainingInCurrentMonth (line 40) | func GetHoursRemainingInCurrentMonth() float64 {
FILE: pkg/controller/utils/unitConversions.go
function BytesToGB (line 28) | func BytesToGB(val int64) float64 {
function ConvertToFloat64GB (line 33) | func ConvertToFloat64GB(quantity *resource.Quantity) float64 {
function ConvertToFloat64CPU (line 38) | func ConvertToFloat64CPU(quantity *resource.Quantity) float64 {
function AddResourceAToResourceB (line 43) | func AddResourceAToResourceB(resA, resB *resource.Quantity) {
function float64BytesToFloat64GB (line 50) | func float64BytesToFloat64GB(val float64) float64 {
function resourceToFloat64 (line 55) | func resourceToFloat64(quantity *resource.Quantity) float64 {
FILE: pkg/controller/utils/unitConversions_test.go
function TestBytesToGB (line 27) | func TestBytesToGB(t *testing.T) {
function TestConvertToFloat64GB (line 33) | func TestConvertToFloat64GB(t *testing.T) {
function TestConvertToFloat64CPU (line 42) | func TestConvertToFloat64CPU(t *testing.T) {
function getTestQuantities (line 51) | func getTestQuantities() [3]resource.Quantity {
FILE: pkg/plugin/costing.go
type Cost (line 31) | type Cost struct
function ProvideClientSetInstance (line 42) | func ProvideClientSetInstance(clientset *kubernetes.Clientset) {
function GetPodsCostForLabel (line 47) | func GetPodsCostForLabel(label string) {
function GetClusterSummary (line 54) | func GetClusterSummary() {
function GetSavings (line 106) | func GetSavings() {
function GetPodCost (line 125) | func GetPodCost(podName string) {
function GetAllNodesCost (line 132) | func GetAllNodesCost() {
function calculateCost (line 151) | func calculateCost(pods []*Pod, pvcs map[string]*PersistentVolumeClaim) ...
function calculateCostOfPod (line 160) | func calculateCostOfPod(pod Pod, pvcs map[string]*PersistentVolumeClaim,...
function getPvCostAndCapacity (line 183) | func getPvCostAndCapacity(pvs []v1.PersistentVolume) (float64, int64) {
function getPvcCostAndCapacity (line 194) | func getPvcCostAndCapacity(pvcs []v1.PersistentVolumeClaim) (float64, in...
function getPodsCost (line 205) | func getPodsCost(pods []*Pod) []*Pod {
FILE: pkg/plugin/grouping.go
function GetGroupByName (line 31) | func GetGroupByName(groupClient *groups.GroupClient, groupName string) *...
function PrintGroup (line 41) | func PrintGroup(group *groups_v1.Group) {
FILE: pkg/plugin/metrics/metrics.go
type Metrics (line 29) | type Metrics struct
type GroupMetrics (line 38) | type GroupMetrics struct
function CalculatePodStatsFromContainers (line 47) | func CalculatePodStatsFromContainers(pods []v1.Pod) *Metrics {
function CalculateNodeStats (line 76) | func CalculateNodeStats(nodes []v1.Node) *Metrics {
function PrintPodStats (line 94) | func PrintPodStats(pod *api_v1.Pod, metrics *Metrics) {
FILE: pkg/plugin/node.go
function GetClusterNodes (line 26) | func GetClusterNodes() []v1.Node {
FILE: pkg/plugin/pod.go
type Pod (line 32) | type Pod struct
function GetClusterPods (line 42) | func GetClusterPods() []v1.Pod {
function getPodDetailsFromClient (line 50) | func getPodDetailsFromClient(podName string) *Pod {
function getPodsForLabelThroughClient (line 71) | func getPodsForLabelThroughClient(label string) []*Pod {
function createPodObjects (line 86) | func createPodObjects(pods *v1.PodList) []*Pod {
function createPodObject (line 96) | func createPodObject(pod *v1.Pod) Pod {
function getPodVolumes (line 106) | func getPodVolumes(pod *v1.Pod) []*string {
function printPodsVerbose (line 117) | func printPodsVerbose(pods []*Pod) {
FILE: pkg/plugin/pricing.go
constant namespace (line 29) | namespace = "default"
constant userCostsConfigMap (line 30) | userCostsConfigMap = "purser-user-costs"
constant defaultCPUCostPerCPUPerHour (line 31) | defaultCPUCostPerCPUPerHour = 0.024
constant defaultMemCostPerGBPerHour (line 32) | defaultMemCostPerGBPerHour = 0.01
constant defaultStorageCostPerGBPerHour (line 33) | defaultStorageCostPerGBPerHour = 0.00013888888
type Price (line 38) | type Price struct
function SaveUserCosts (line 45) | func SaveUserCosts(cpuCostPerCPUPerHour, memCostPerGBPerHour, storageCos...
function GetUserCosts (line 81) | func GetUserCosts() *Price {
FILE: pkg/plugin/utils.go
function getCurrentTime (line 27) | func getCurrentTime() metav1.Time {
function getCurrentMonthStartTime (line 32) | func getCurrentMonthStartTime() metav1.Time {
function currentMonthActiveTimeInHours (line 44) | func currentMonthActiveTimeInHours(startTime, endTime metav1.Time) float...
function currentMonthActiveTimeInHoursMulti (line 55) | func currentMonthActiveTimeInHoursMulti(startTime, endTime, currentTime,...
function totalHoursTillNow (line 70) | func totalHoursTillNow() float64 {
function projectToMonth (line 76) | func projectToMonth(val float64) float64 {
function bytesToGB (line 81) | func bytesToGB(val int64) float64 {
FILE: pkg/plugin/volume.go
type PersistentVolumeClaim (line 29) | type PersistentVolumeClaim struct
function GetClusterVolumes (line 38) | func GetClusterVolumes() []v1.PersistentVolume {
function GetClusterPersistentVolumeClaims (line 47) | func GetClusterPersistentVolumeClaims() []v1.PersistentVolumeClaim {
function collectPersistentVolumeClaims (line 55) | func collectPersistentVolumeClaims(pvcs map[string]*PersistentVolumeClai...
function collectPersistentVolumeClaim (line 63) | func collectPersistentVolumeClaim(claimName string) *PersistentVolumeCla...
FILE: pkg/pricing/aws/aws.go
constant httpTimeout (line 29) | httpTimeout = 100 * time.Second
type Pricing (line 33) | type Pricing struct
type PlanList (line 39) | type PlanList struct
type TermAttributes (line 44) | type TermAttributes struct
type PricingData (line 49) | type PricingData struct
type Product (line 55) | type Product struct
type ProductAttributes (line 62) | type ProductAttributes struct
function GetAWSPricing (line 76) | func GetAWSPricing(region string) (*Pricing, error) {
function getURLForRegion (line 87) | func getURLForRegion(region string) string {
FILE: pkg/pricing/aws/convert.go
constant na (line 31) | na = "NA"
constant gbMonth (line 32) | gbMonth = "GB-Mo"
constant deliminator (line 33) | deliminator = "-"
constant storageInstance (line 34) | storageInstance = "Storage"
constant computeInstance (line 35) | computeInstance = "Compute Instance"
constant priceSplitRatio (line 38) | priceSplitRatio = 0.5
function GetRateCardForAWS (line 42) | func GetRateCardForAWS(region string) *models.RateCard {
function convertAWSPricingToPurserRateCard (line 50) | func convertAWSPricingToPurserRateCard(region string, awsPricing *Pricin...
function getResourcePricesFromAWSPricing (line 62) | func getResourcePricesFromAWSPricing(awsPricing *Pricing) ([]*models.Nod...
function getResourcePrice (line 81) | func getResourcePrice(product Product, planList PlanList) (float64, stri...
function updateComputeInstancePrices (line 97) | func updateComputeInstancePrices(product Product, priceInFloat64 float64...
function updateStorageInstancePrices (line 123) | func updateStorageInstancePrices(product Product, priceInFloat64 float64...
function getPriceForUnitResource (line 147) | func getPriceForUnitResource(product Product, priceInFloat64 float64) (f...
FILE: pkg/pricing/cloud.go
type Cloud (line 28) | type Cloud struct
method PopulateRateCard (line 44) | func (c *Cloud) PopulateRateCard() {
function GetClusterProviderAndRegion (line 35) | func GetClusterProviderAndRegion() (string, string) {
FILE: pkg/utils/fileutils.go
function OpenFile (line 28) | func OpenFile(filename string) *os.File {
function GetUsrHomeDir (line 37) | func GetUsrHomeDir() string {
FILE: pkg/utils/k8sutil.go
function GetKubeclient (line 29) | func GetKubeclient(config *rest.Config) *kubernetes.Clientset {
function GetKubeconfig (line 39) | func GetKubeconfig(kubeconfigPath string) (*rest.Config, error) {
FILE: pkg/utils/logutil.go
constant logFile (line 27) | logFile = "purser.log"
function InitializeLogger (line 30) | func InitializeLogger(logLevel string) {
FILE: test/controller/buffering/ring_buffer_test.go
function TestPut (line 28) | func TestPut(t *testing.T) {
function TestGet (line 41) | func TestGet(t *testing.T) {
FILE: test/pricing/pricing_aws_test.go
function TestAWSPricingFlow (line 33) | func TestAWSPricingFlow(t *testing.T) {
FILE: test/utils/checkUtil.go
function Assert (line 29) | func Assert(tb testing.TB, condition bool, msg string, v ...interface{}) {
function Ok (line 38) | func Ok(tb testing.TB, err error) {
function Equals (line 47) | func Equals(tb testing.TB, exp, act interface{}) {
FILE: ui/e2e/protractor.conf.js
method onPrepare (line 22) | onPrepare() {
FILE: ui/e2e/src/app.po.ts
class AppPage (line 3) | class AppPage {
method navigateTo (line 4) | navigateTo() {
method getParagraphText (line 8) | getParagraphText() {
FILE: ui/src/app/app.component.ts
class AppComponent (line 10) | class AppComponent implements OnInit {
method constructor (line 16) | constructor(public router: Router) {
method loadApp (line 22) | private loadApp() {
method navigationEventHandler (line 28) | private navigationEventHandler(event: RouterEvent): void {
method ngOnInit (line 45) | ngOnInit() {
FILE: ui/src/app/app.constants.ts
constant BACKEND_BASE_URL (line 1) | const BACKEND_BASE_URL = "http://10.112.141.194";
constant BACKEND_URL (line 2) | const BACKEND_URL = BACKEND_BASE_URL + '/api/'
constant BACKEND_AUTH_URL (line 3) | const BACKEND_AUTH_URL = BACKEND_BASE_URL + '/auth/'
FILE: ui/src/app/app.module.ts
class AppModule (line 45) | class AppModule { }
FILE: ui/src/app/app.routing.ts
constant ROUTES (line 11) | const ROUTES: Routes = [
constant ROUTING (line 21) | const ROUTING: ModuleWithProviders = RouterModule.forRoot(ROUTES);
FILE: ui/src/app/modules/capacity-graph/capacity-graph.module.ts
class CapacityGraphModule (line 19) | class CapacityGraphModule { }
FILE: ui/src/app/modules/capacity-graph/components/capactiy-graph.component.ts
constant STATUS_WAIT (line 6) | const STATUS_WAIT = 'WAIT',
constant STATUS_READY (line 6) | const STATUS_WAIT = 'WAIT',
constant STATUS_NODATA (line 6) | const STATUS_WAIT = 'WAIT',
class CapactiyGraphComponent (line 17) | class CapactiyGraphComponent implements OnInit {
method constructor (line 64) | constructor(private router: Router, private capacityGraphService: Capa...
method getCapacityData (line 66) | private getCapacityData() {
method computeAllocationRatios (line 82) | private computeAllocationRatios(data) {
method constructRoot (line 139) | private constructRoot(capaData) {
method pushToGraphData (line 156) | private pushToGraphData(item, parent) {
method collectFilterItems (line 170) | private collectFilterItems(item) {
method constructData (line 174) | private constructData(capaData) {
method onSelect (line 196) | public onSelect(element) {
method getAdditionalData (line 208) | private getAdditionalData(item) {
method filterItemChange (line 229) | public filterItemChange(evt) {
method metricChange (line 239) | public metricChange(evt) {
method reset (line 243) | public reset() {
method viewChange (line 250) | public viewChange() {
method ngOnInit (line 254) | ngOnInit() {
FILE: ui/src/app/modules/capacity-graph/services/capacity-graph.service.ts
class CapacityGraphService (line 6) | class CapacityGraphService {
method constructor (line 7) | constructor(private http: HttpClient) {
method getCapacityData (line 11) | public getCapacityData(view?, type?, name?) {
FILE: ui/src/app/modules/changepassword/changepassword.module.ts
class ChangepasswordModule (line 18) | class ChangepasswordModule { }
FILE: ui/src/app/modules/changepassword/components/changepassword.component.ts
class ChangepasswordComponent (line 12) | class ChangepasswordComponent implements OnInit {
method ngOnInit (line 16) | ngOnInit() {
method constructor (line 21) | constructor(private router: Router, private changepasswordService: Cha...
method submitChangePassword (line 23) | public submitChangePassword() {
FILE: ui/src/app/modules/changepassword/services/changepassword.service.ts
class ChangepasswordService (line 6) | class ChangepasswordService {
method constructor (line 8) | constructor(private http: HttpClient) {
method sendLoginCredentials (line 12) | public sendLoginCredentials(credentials) {
FILE: ui/src/app/modules/logical-group/components/logical-group.component.ts
constant STATUS_WAIT (line 6) | const STATUS_WAIT = 'WAIT',
constant STATUS_READY (line 6) | const STATUS_WAIT = 'WAIT',
constant STATUS_NODATA (line 6) | const STATUS_WAIT = 'WAIT',
class LogicalGroupComponent (line 15) | class LogicalGroupComponent implements OnInit {
method constructor (line 40) | constructor(private router: Router, private logicalGroupService: Logic...
method getCurrentMonthStatus (line 43) | public getCurrentMonthStatus() {
method getLastMonthStatus (line 51) | public getLastMonthStatus() {
method getLast3MonthStatus (line 59) | public getLast3MonthStatus() {
method getProjectedMonthStatus (line 67) | public getProjectedMonthStatus() {
method changeCurrentMonthView (line 75) | public changeCurrentMonthView() {
method changeLastMonthView (line 79) | public changeLastMonthView() {
method changeLast3MonthView (line 83) | public changeLast3MonthView() {
method changeProjectedMonthView (line 87) | public changeProjectedMonthView() {
method getLogicalGroupData (line 92) | private getLogicalGroupData() {
method fillGroupData (line 106) | public fillGroupData() {
method deleteGroupData (line 111) | public deleteGroupData() {
method createGroup (line 116) | public createGroup() {
method deleteGroup (line 131) | public deleteGroup() {
method setToBeDeletedGroup (line 146) | public setToBeDeletedGroup(grpName) {
method showGroupDetails (line 151) | public showGroupDetails(group) {
method reset (line 158) | public reset() {
method showMTD (line 169) | public showMTD() {
method showProjected (line 195) | public showProjected() {
method ngOnInit (line 221) | ngOnInit() {
FILE: ui/src/app/modules/logical-group/logical-group.module.ts
class LogicalGroupModule (line 19) | class LogicalGroupModule { }
FILE: ui/src/app/modules/logical-group/services/logical-group.service.ts
class LogicalGroupService (line 6) | class LogicalGroupService {
method constructor (line 7) | constructor(private http: HttpClient) {
method getLogicalGroupData (line 11) | public getLogicalGroupData(name?) {
method deleteCustomGroup (line 29) | public deleteCustomGroup(name) {
method createCustomGroup (line 34) | public createCustomGroup(groupDef) {
FILE: ui/src/app/modules/login/components/login.component.ts
class LoginComponent (line 12) | class LoginComponent implements OnInit {
method ngOnInit (line 15) | ngOnInit() {
method constructor (line 20) | constructor(private router: Router, private loginService: LoginService...
method submitLogin (line 22) | public submitLogin() {
FILE: ui/src/app/modules/login/login.module.ts
class LoginModule (line 18) | class LoginModule { }
FILE: ui/src/app/modules/login/services/login.service.ts
class LoginService (line 6) | class LoginService {
method constructor (line 9) | constructor(private http: HttpClient) {
method sendLoginCredential (line 13) | public sendLoginCredential(credentials) {
FILE: ui/src/app/modules/logout/components/logout.component.ts
class LogoutComponent (line 12) | class LogoutComponent implements OnInit {
method ngOnInit (line 15) | ngOnInit() {
method constructor (line 20) | constructor(private router: Router, private http: HttpClient, private ...
method handleLogout (line 22) | public handleLogout() {
FILE: ui/src/app/modules/logout/logout.module.ts
class LogoutModule (line 17) | class LogoutModule { }
FILE: ui/src/app/modules/options/components/options.component.ts
class OptionsComponent (line 10) | class OptionsComponent implements OnInit {
method ngOnInit (line 12) | ngOnInit() {
method constructor (line 16) | constructor(private http: HttpClient) { }
method initiateSync (line 18) | public initiateSync() {
FILE: ui/src/app/modules/options/options.module.ts
class OptionsModule (line 17) | class OptionsModule { }
FILE: ui/src/app/modules/topo-graph/components/topo-graph.component.ts
constant STATUS_WAIT (line 6) | const STATUS_WAIT = 'WAIT',
constant STATUS_READY (line 6) | const STATUS_WAIT = 'WAIT',
constant STATUS_NODATA (line 6) | const STATUS_WAIT = 'WAIT',
class TopoGraphComponent (line 15) | class TopoGraphComponent implements OnInit {
method constructor (line 84) | constructor(private router: Router, private topoService: TopoGraphServ...
method getTopoData (line 86) | private getTopoData() {
method getIcon (line 104) | private getIcon(type) {
method pushToGraphData (line 121) | private pushToGraphData(item, parent) {
method collectFilterItems (line 144) | private collectFilterItems(item) {
method constructData (line 148) | private constructData(topoData) {
method onSelect (line 177) | public onSelect(element) {
method getAdditionalData (line 189) | private getAdditionalData(item) {
method filterItemChange (line 211) | public filterItemChange(evt) {
method reset (line 223) | public reset() {
method viewChange (line 230) | public viewChange() {
method ngOnInit (line 234) | ngOnInit() {
FILE: ui/src/app/modules/topo-graph/modules.ts
class TopoGraphModule (line 17) | class TopoGraphModule {
FILE: ui/src/app/modules/topo-graph/services/topo-graph.service.ts
class TopoGraphService (line 7) | class TopoGraphService {
method constructor (line 8) | constructor(private http: HttpClient, private cookieService: CookieSer...
method getTopoData (line 12) | public getTopoData(view?, type?, name?) {
FILE: ui/src/app/modules/topologyGraph/components/topologyGraph.component.ts
constant STATUS_WAIT (line 7) | const STATUS_WAIT = 'WAIT',
constant STATUS_READY (line 7) | const STATUS_WAIT = 'WAIT',
constant STATUS_NODATA (line 7) | const STATUS_WAIT = 'WAIT',
class TopologyGraphComponent (line 17) | class TopologyGraphComponent implements OnInit {
method constructor (line 63) | constructor(private router: Router, private topologyService: TopologyG...
method getServiceList (line 67) | private getServiceList() {
method getNodes (line 79) | private getNodes() {
method getEdges (line 105) | private getEdges() {
method initNetwork (line 121) | private initNetwork() {
method reset (line 178) | public reset() {
method reload (line 184) | public reload() {
method loadApp (line 223) | private loadApp() {
method ngOnInit (line 228) | ngOnInit() {
method clusterByCid (line 233) | public clusterByCid() {
FILE: ui/src/app/modules/topologyGraph/modules.ts
class TopologyGraphModule (line 16) | class TopologyGraphModule {
FILE: ui/src/app/modules/topologyGraph/services/topologyGraph.service.ts
class TopologyGraphService (line 6) | class TopologyGraphService {
method constructor (line 7) | constructor(private http: HttpClient) {
method getNodes (line 11) | public getNodes(serviceName) {
method getEdges (line 25) | public getEdges(serviceName) {
method getServiceList (line 39) | public getServiceList() {
Condensed preview — 249 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (671K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 669,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
},
{
"path": ".github/ISSUE_TEMPLATE/custom.md",
"chars": 126,
"preview": "---\nname: Custom issue template\nabout: Describe this issue template's purpose here.\ntitle: ''\nlabels: ''\nassignees: ''\n\n"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 595,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 501,
"preview": "**Is this a BUG REPORT or FEATURE REQUEST?**:\n\n> Uncomment only one, leave it on its own line:\n>\n> /kind bug\n> /kind fea"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 745,
"preview": "<!-- Thanks for sending a pull request! Here are some tips for you:\n1. If this is your first time, read our contributo"
},
{
"path": ".gitignore",
"chars": 111,
"preview": ".idea\n.vscode\n.go/\nbin/\nvendor/\n*.log\n\n# compiled output\n/dist\n/tmp\n/out-tsc\ntmp/\n\n# dependencies\nnode_modules/"
},
{
"path": ".make/Makefile.deploy.controller",
"chars": 3149,
"preview": "IMAGE := $(DOCKER_REPO)/purser\nOUTPUT_DIR := tmp\nDOCKER_OUT := $(OUTPUT_DIR)/docker\n\n.PHONY: build\nbuild: $(DOCKER_OUT)/"
},
{
"path": ".make/Makefile.deploy.purser",
"chars": 1144,
"preview": "DEPLOY_DOCKERFILE?=ui/Dockerfile.deploy.purser\n\nCLUSTER_DIR?=${PWD}/cluster\n\nCOMMIT:=$(shell git rev-parse --short HEAD)"
},
{
"path": ".travis.yml",
"chars": 189,
"preview": "services:\n - docker\n\nlanguage: go\n\nos:\n - linux\n\ngo:\n - \"1.10\"\n\nscript:\n - make tools\n - make deps\n - make install"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 1359,
"preview": "Contributor Code of Conduct\n======================\n\nAs contributors and maintainers of this project, we pledge to respec"
},
{
"path": "CONTRIBUTING.md",
"chars": 7664,
"preview": "# Contributing to Purser\n\nWelcome! We gladly accept contributions from the community. If you wish\nto contribute code and"
},
{
"path": "Dockerfile.in",
"chars": 161,
"preview": "FROM ARG_FROM\n\nLABEL maintainer = \"VMware <kreddyj@vmware.com>\"\nLABEL author = \"Krishna Karthik <kreddyj@vmware.com>\"\n\nA"
},
{
"path": "Gopkg.toml",
"chars": 1169,
"preview": "# Gopkg.toml example\n#\n# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html\n# for detailed Gopkg.toml documentat"
},
{
"path": "LICENSE",
"chars": 9712,
"preview": "Purser\r\n\r\nCopyright (c) 2018 VMware, Inc. All rights reserved.\t\t\t\t\r\n\r\nThe Apache 2.0 license (the License) set forth be"
},
{
"path": "Makefile",
"chars": 4348,
"preview": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n# Licensed under the Apache"
},
{
"path": "NOTICE",
"chars": 467,
"preview": "Purser\r\n\r\nCopyright (c) 2018 VMware, Inc. All Rights Reserved. \r\n\r\nThis product is licensed to you under the Apache 2.0 "
},
{
"path": "README.md",
"chars": 5634,
"preview": "\n# K8s Exte"
},
{
"path": "build/build.sh",
"chars": 1135,
"preview": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n# Licensed under the Apache"
},
{
"path": "build/purser-binary-install.sh",
"chars": 2386,
"preview": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n# Licensed under the Apache"
},
{
"path": "build/purser-binary-uninstall.sh",
"chars": 812,
"preview": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n#\n# Licensed under the Apach"
},
{
"path": "build/purser-minimal-setup.sh",
"chars": 1607,
"preview": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n# Licensed under the Apache"
},
{
"path": "build/purser-setup.sh",
"chars": 1906,
"preview": "# Copyright (c) 2018 VMware Inc. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n# Licensed under the Apache"
},
{
"path": "cluster/artifacts/example-group.yaml",
"chars": 325,
"preview": "apiVersion: vmware.purser.com/v1\nkind: Group\nmetadata:\n name: example-group\nspec:\n name: example-group\n labels:\n e"
},
{
"path": "cluster/artifacts/example-subscriber.yaml",
"chars": 232,
"preview": "apiVersion: vmware.purser.com/v1\nkind: Subscriber\nmetadata:\n name: example-subscriber\nspec:\n name: example-subscriber\n"
},
{
"path": "cluster/artifacts/group-template.json",
"chars": 544,
"preview": "{\n \"apiVersion\": \"vmware.purser.com/v1\",\n \"kind\": \"Group\",\n \"metadata\": {\n \"name\": \"<enter-custom-group-name>\"\n }"
},
{
"path": "cluster/artifacts/purser-group-crd.yaml",
"chars": 373,
"preview": "apiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n name: groups.vmware.purser.com\nspec:"
},
{
"path": "cluster/artifacts/purser-subscriber-crd.yaml",
"chars": 418,
"preview": "apiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n name: subscribers.vmware.purser.com\n"
},
{
"path": "cluster/helm/chart/purser/.helmignore",
"chars": 342,
"preview": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation"
},
{
"path": "cluster/helm/chart/purser/Chart.yaml",
"chars": 99,
"preview": "apiVersion: v1\nappVersion: \"1.0\"\ndescription: A Helm chart for Purser \nname: purser\nversion: 0.1.0\n"
},
{
"path": "cluster/helm/chart/purser/README.md",
"chars": 1900,
"preview": "# [Purser](https://github.com/vmware/purser)\n\nPurser is an extension to Kubernetes tasked at providing an insight into c"
},
{
"path": "cluster/helm/chart/purser/templates/NOTES.txt",
"chars": 1537,
"preview": "1. Get the application URL by running these commands:\n{{- if .Values.ui.ingress.enabled }}\n{{- range $host := .Values.ui"
},
{
"path": "cluster/helm/chart/purser/templates/_helpers.tpl",
"chars": 1042,
"preview": "{{/* vim: set filetype=mustache: */}}\n{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"purser.name\" -}}\n{{- default ."
},
{
"path": "cluster/helm/chart/purser/templates/purser-controller-deployment.yaml",
"chars": 2190,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: {{ include \"purser.fullname\" . }}-controller\n namespace: {{ .Rel"
},
{
"path": "cluster/helm/chart/purser/templates/purser-controller-rbac.yaml",
"chars": 1449,
"preview": "apiVersion: rbac.authorization.k8s.io/v1beta1\nkind: ClusterRole\nmetadata:\n name: {{ include \"purser.fullname\" . }}\n la"
},
{
"path": "cluster/helm/chart/purser/templates/purser-controller-serviceaccount.yaml",
"chars": 352,
"preview": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n name: {{ include \"purser.fullname\" . }}\n namespace: {{ .Release.Namespa"
},
{
"path": "cluster/helm/chart/purser/templates/purser-controller-svc.yaml",
"chars": 611,
"preview": "apiVersion: v1\nkind: Service\nmetadata:\n name: {{ include \"purser.fullname\" . }}-controller\n namespace: {{ .Release.Nam"
},
{
"path": "cluster/helm/chart/purser/templates/purser-database-statefulset.yaml",
"chars": 2907,
"preview": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n name: {{ include \"purser.fullname\" . }}-database\n namespace: {{ .Rele"
},
{
"path": "cluster/helm/chart/purser/templates/purser-database-svc.yaml",
"chars": 773,
"preview": "apiVersion: v1\nkind: Service\nmetadata:\n name: {{ include \"purser.fullname\" . }}-database\n namespace: {{ .Release.Names"
},
{
"path": "cluster/helm/chart/purser/templates/purser-ui-configmap.yaml",
"chars": 826,
"preview": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: {{ include \"purser.fullname\" . }}-ui\n namespace: {{ .Release.Namespace"
},
{
"path": "cluster/helm/chart/purser/templates/purser-ui-deployment.yaml",
"chars": 1840,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: {{ include \"purser.fullname\" . }}-ui\n namespace: {{ .Release.Nam"
},
{
"path": "cluster/helm/chart/purser/templates/purser-ui-ingress.yaml",
"chars": 1066,
"preview": "{{- if .Values.ui.ingress.enabled -}}\n{{- $fullName := include \"purser.fullname\" . -}}\napiVersion: extensions/v1beta1\nki"
},
{
"path": "cluster/helm/chart/purser/templates/purser-ui-svc.yaml",
"chars": 632,
"preview": "apiVersion: v1\nkind: Service\nmetadata:\n name: {{ include \"purser.fullname\" . }}-ui\n namespace: {{ .Release.Namespace }"
},
{
"path": "cluster/helm/chart/purser/values.yaml",
"chars": 3258,
"preview": "# Default values for purser.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\nname"
},
{
"path": "cluster/minimal/purser-controller-setup.yaml",
"chars": 1799,
"preview": "# Service account\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n name: purser-service-account\n---\n# RBAC\napiVersion: rb"
},
{
"path": "cluster/minimal/purser-database-setup.yaml",
"chars": 2156,
"preview": "# Service Dgraph - This is the service that should be used by the clients of Dgraph to talk to the server.\napiVersion: v"
},
{
"path": "cluster/minimal/purser-ui-setup.yaml",
"chars": 645,
"preview": "apiVersion: v1\nkind: Service\nmetadata:\n name: purser-ui\n labels:\n run: purser-ui\n app: purser\nspec:\n selector:\n"
},
{
"path": "cluster/purser-controller-setup.yaml",
"chars": 1968,
"preview": "# Service account\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n name: purser-service-account\n---\n# RBAC\napiVersion: rb"
},
{
"path": "cluster/purser-database-setup.yaml",
"chars": 2467,
"preview": "# Service Dgraph - This is the service that should be used by the clients of Dgraph to talk to the server.\napiVersion: v"
},
{
"path": "cluster/purser-ui-setup.yaml",
"chars": 799,
"preview": "apiVersion: v1\nkind: Service\nmetadata:\n name: purser-ui\n labels:\n run: purser-ui\n app: purser\nspec:\n selector:\n"
},
{
"path": "cmd/controller/api/api.go",
"chars": 1283,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "cmd/controller/api/apiHandlers/authenticationHandlers.go",
"chars": 3620,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "cmd/controller/api/apiHandlers/customGroupHandlers.go",
"chars": 2943,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "cmd/controller/api/apiHandlers/helpers.go",
"chars": 2265,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "cmd/controller/api/apiHandlers/hierarchyAndMetricAPIHandlers.go",
"chars": 20162,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "cmd/controller/api/logger.go",
"chars": 1060,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "cmd/controller/api/router.go",
"chars": 1070,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "cmd/controller/api/routes.go",
"chars": 4533,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "cmd/controller/config/config.go",
"chars": 2141,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "cmd/controller/purserctrl.go",
"chars": 3567,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "cmd/plugin/purser.go",
"chars": 6794,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "cmd/plugin/types.go",
"chars": 994,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "docs/architecture.md",
"chars": 1685,
"preview": "# Architecture of Purser\n\nThe following diagram represents the architecture of Purser.\n\n\n- [Workspace Setup](#workspace-setup)\n- [Database Setup](#database"
},
{
"path": "docs/manual-installation.md",
"chars": 3349,
"preview": "# Manual Installation\n\nTo install Purser manually from the Binary follow the steps described below.\n\n## Purser Setup\nThe"
},
{
"path": "docs/plugin-installation.md",
"chars": 860,
"preview": "# Purser Plugin Setup\n_NOTE: This Plugin installation is optional. Install it if you want to use CLI of Purser._\n\n## Lin"
},
{
"path": "docs/plugin-usage.md",
"chars": 2482,
"preview": "# Purser Plugin Usage\n\nOnce installed, Purser is ready for use right away. You can query using native Kubernetes groupin"
},
{
"path": "docs/purser-deployment.md",
"chars": 1429,
"preview": "# Purser Deployment\n\nIn order to deploy the Purser UI and DGraph database service, follow the below listed steps:\n\n1. Sw"
},
{
"path": "docs/sourcecode-installation.md",
"chars": 3729,
"preview": "# Installation Through Source Code\n\n- [Prerequisites](#prerequisites)\n- [Server Side Installation (Controller Installati"
},
{
"path": "openapi.yaml",
"chars": 21228,
"preview": "---\nopenapi: 3.0.1\ninfo:\n title: Purser\n description: Purser runs on server port `:3030` and exposes API endpoints to "
},
{
"path": "pkg/apis/groups/v1/deepcopy.go",
"chars": 1512,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/apis/groups/v1/docs.go",
"chars": 670,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/apis/groups/v1/register.go",
"chars": 1695,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/apis/groups/v1/types.go",
"chars": 4069,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/apis/subscriber/v1/deepcopy.go",
"chars": 1547,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/apis/subscriber/v1/docs.go",
"chars": 670,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/apis/subscriber/v1/register.go",
"chars": 1743,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/apis/subscriber/v1/types.go",
"chars": 1719,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/client/clientset/typed/groups/v1/group.go",
"chars": 2953,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/client/clientset/typed/groups/v1/group_client.go",
"chars": 3563,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/client/clientset/typed/subscriber/v1/subsciber_client.go",
"chars": 3747,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/client/clientset/typed/subscriber/v1/subscriber.go",
"chars": 3098,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/client/clientset.go",
"chars": 1388,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/buffering/ring_buffer.go",
"chars": 2767,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/controller.go",
"chars": 13967,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/controller_test.go",
"chars": 1594,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/dgraph.go",
"chars": 5469,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/login.go",
"chars": 1621,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/constants.go",
"chars": 462,
"preview": "package models\n\n// Cost and other cloud constants\nconst (\n\t// Cost constants\n\tDefaultCPUCostPerCPUPerHour = \"0.024\"\n\t"
},
{
"path": "pkg/controller/dgraph/models/container.go",
"chars": 5559,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/daemonset.go",
"chars": 3154,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/deployment.go",
"chars": 3188,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/group.go",
"chars": 5354,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/job.go",
"chars": 2811,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/label.go",
"chars": 2106,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/namespace.go",
"chars": 2782,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/node.go",
"chars": 4194,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/pod.go",
"chars": 9965,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/pod_test.go",
"chars": 1457,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/process.go",
"chars": 3505,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/pv.go",
"chars": 3356,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/pvc.go",
"chars": 4100,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/cluster.go",
"chars": 4800,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/cluster_test.go",
"chars": 8115,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/constants_test.go",
"chars": 1573,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/group.go",
"chars": 4360,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/group_test.go",
"chars": 4545,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/helpers.go",
"chars": 7133,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/helpers_test.go",
"chars": 1213,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/label.go",
"chars": 1422,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/label_test.go",
"chars": 1586,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/login.go",
"chars": 2668,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/pod.go",
"chars": 3866,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/pod_test.go",
"chars": 4222,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/queries.go",
"chars": 15210,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/resource.go",
"chars": 3804,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/resource_test.go",
"chars": 8469,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/subscriber.go",
"chars": 1118,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/subscriber_test.go",
"chars": 1938,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/query/types.go",
"chars": 2466,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/rateCard.go",
"chars": 6009,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/replicaset.go",
"chars": 3894,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/service.go",
"chars": 5429,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/statefulset.go",
"chars": 3275,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/models/subscriber.go",
"chars": 2758,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/dgraph/purge.go",
"chars": 2548,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/discovery/executer/exec.go",
"chars": 2377,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/discovery/generator/graph.go",
"chars": 4698,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/discovery/linker/podlinks.go",
"chars": 4110,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/discovery/linker/processlinks.go",
"chars": 2505,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/discovery/linker/servicelinks.go",
"chars": 2782,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/discovery/processor/container.go",
"chars": 3710,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/discovery/processor/pod.go",
"chars": 2984,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/discovery/processor/svc.go",
"chars": 2419,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/eventprocessor/notifier.go",
"chars": 3175,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/eventprocessor/processor.go",
"chars": 4814,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/eventprocessor/sync.go",
"chars": 3728,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/eventprocessor/updater.go",
"chars": 7090,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/metrics/metrics.go",
"chars": 2115,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/payload.go",
"chars": 1142,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/persistentVolume.go",
"chars": 5571,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/types.go",
"chars": 2072,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/utils/jsonutils.go",
"chars": 1476,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/utils/k8sUtils.go",
"chars": 4375,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/utils/purge.go",
"chars": 2661,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/utils/purge_test.go",
"chars": 854,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/utils/timeUtils.go",
"chars": 1518,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/utils/unitConversions.go",
"chars": 1858,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/controller/utils/unitConversions_test.go",
"chars": 1696,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/plugin/costing.go",
"chars": 7175,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/plugin/grouping.go",
"chars": 2914,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/plugin/metrics/metrics.go",
"chars": 2906,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/plugin/node.go",
"chars": 995,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/plugin/pod.go",
"chars": 4323,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/plugin/pricing.go",
"chars": 4085,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/plugin/utils.go",
"chars": 2757,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/plugin/volume.go",
"chars": 2791,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/pricing/aws/aws.go",
"chars": 2216,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/pricing/aws/convert.go",
"chars": 6088,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/pricing/cloud.go",
"chars": 1617,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/utils/fileutils.go",
"chars": 1250,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/utils/k8sutil.go",
"chars": 1368,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "pkg/utils/logutil.go",
"chars": 1102,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "plugin.yaml",
"chars": 279,
"preview": "name: \"purser\"\nshortDesc: \"Cost Insight integration with kubernetes\"\nlongDesc: >\n Purser gives cost insights of kuberne"
},
{
"path": "test/controller/buffering/ring_buffer_test.go",
"chars": 1525,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "test/pricing/pricing_aws_test.go",
"chars": 967,
"preview": "package pricing\n\nimport (\n\t\"testing\"\n\n\t\"github.com/vmware/purser/test/utils\"\n\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com"
},
{
"path": "test/utils/checkUtil.go",
"chars": 1626,
"preview": "/*\n * Copyright (c) 2018 VMware Inc. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under th"
},
{
"path": "ui/Dockerfile.deploy.purser",
"chars": 685,
"preview": "FROM node:9.6.1 as builder\n\nLABEL maintainer = \"VMware <kreddyj@vmware.com>\"\nLABEL author = \"Krishna Karthik <kreddyj@vm"
},
{
"path": "ui/README.md",
"chars": 1249,
"preview": "# Purser UI\n\nPurser UI is designed to provide a visual representation to a host of features provided by Purser such as *"
},
{
"path": "ui/angular.json",
"chars": 3887,
"preview": "{\n \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n \"version\": 1,\n \"newProjectRoot\": \"projects\",\n \""
},
{
"path": "ui/e2e/protractor.conf.js",
"chars": 752,
"preview": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib"
},
{
"path": "ui/e2e/src/app.e2e-spec.ts",
"chars": 302,
"preview": "import { AppPage } from './app.po';\n\ndescribe('workspace-project App', () => {\n let page: AppPage;\n\n beforeEach(() => "
},
{
"path": "ui/e2e/src/app.po.ts",
"chars": 208,
"preview": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n navigateTo() {\n return browser.get('/');"
},
{
"path": "ui/e2e/tsconfig.e2e.json",
"chars": 213,
"preview": "{\n \"extends\": \"../tsconfig.json\",\n \"compilerOptions\": {\n \"outDir\": \"../out-tsc/app\",\n \"module\": \"commonjs\",\n "
},
{
"path": "ui/nginx.conf",
"chars": 370,
"preview": "upstream purser {\n server purser.purser.svc.cluster.local:3030;\n}\n\nserver {\n listen 4200;\n\n location /auth {\n "
},
{
"path": "ui/package.json",
"chars": 1658,
"preview": "{\n \"name\": \"app-dapp\",\n \"version\": \"0.0.0\",\n \"scripts\": {\n \"ng\": \"ng\",\n \"start\": \"ng serve\",\n \"startdev\": \"n"
},
{
"path": "ui/proxy.conf.json",
"chars": 111,
"preview": "{\n \"/api\": {\n \"target\": \"http://localhost:3030/\",\n \"changeOrigin\": true,\n \"secure\":false\n }\n}"
},
{
"path": "ui/src/app/app.component.html",
"chars": 1872,
"preview": "<div class=\"main-container\">\n <header class=\"header header-6\">\n <div class=\"branding appHeader\">\n <span>{{messa"
},
{
"path": "ui/src/app/app.component.scss",
"chars": 2602,
"preview": ".main-container{\n .appHeader{\n font-size: 20px;\n align-items: center;\n }\n}\n.content-container {\n "
},
{
"path": "ui/src/app/app.component.spec.ts",
"chars": 995,
"preview": "import { TestBed, async } from '@angular/core/testing';\nimport { AppComponent } from './app.component';\ndescribe('AppCom"
},
{
"path": "ui/src/app/app.component.ts",
"chars": 1275,
"preview": "import { Component, OnInit } from '@angular/core';\nimport { NavigationCancel, NavigationEnd, NavigationError, Navigation"
},
{
"path": "ui/src/app/app.constants.ts",
"chars": 164,
"preview": "const BACKEND_BASE_URL = \"http://10.112.141.194\";\nexport const BACKEND_URL = BACKEND_BASE_URL + '/api/'\nexport const BAC"
},
{
"path": "ui/src/app/app.module.ts",
"chars": 1662,
"preview": "import { HttpClientModule } from '@angular/common/http';\nimport { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core"
},
{
"path": "ui/src/app/app.routing.ts",
"chars": 1254,
"preview": "/*Framework imports, 3rd party imports */\nimport { ModuleWithProviders } from '@angular/core';\nimport { RouterModule, Ro"
},
{
"path": "ui/src/app/common/messages/common.messages.ts",
"chars": 71,
"preview": "export const MCommon: any = Object.freeze({\n appHeader: 'PURSER'\n});"
},
{
"path": "ui/src/app/common/messages/left-navigation.messages.ts",
"chars": 69,
"preview": "export const MLeftNav: any = Object.freeze({\n homeText: 'Home'\n});"
},
{
"path": "ui/src/app/modules/capacity-graph/capacity-graph.module.ts",
"chars": 709,
"preview": "import { CommonModule } from '@angular/common';\nimport { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';\nimport"
},
{
"path": "ui/src/app/modules/capacity-graph/components/capactiy-graph.component.html",
"chars": 8543,
"preview": "<div class=\"row\">\n <div class=\"col-xs-12\">\n <div class=\"card\">\n <div class=\"card-block graphCardBlo"
},
{
"path": "ui/src/app/modules/capacity-graph/components/capactiy-graph.component.scss",
"chars": 674,
"preview": ".graphCardBlock{\n ::ng-deep .googleChart{\n width: 100%;\n }\n .headerBlock{\n display: flex;\n "
},
{
"path": "ui/src/app/modules/capacity-graph/components/capactiy-graph.component.spec.ts",
"chars": 678,
"preview": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { CapactiyGraphComponent } from './cap"
},
{
"path": "ui/src/app/modules/capacity-graph/components/capactiy-graph.component.ts",
"chars": 8659,
"preview": "import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';\nimport { Router } from '@angular/router';\nimpo"
},
{
"path": "ui/src/app/modules/capacity-graph/services/capacity-graph.service.ts",
"chars": 885,
"preview": "import { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { BACKEND_URL } fr"
},
{
"path": "ui/src/app/modules/changepassword/changepassword.module.ts",
"chars": 650,
"preview": "import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport"
},
{
"path": "ui/src/app/modules/changepassword/components/changepassword.component.html",
"chars": 1929,
"preview": "<div class=\"login-wrapper\">\n <form class=\"login\">\n <section class=\"title\">\n <h3 class=\"welcome\">Wel"
},
{
"path": "ui/src/app/modules/changepassword/components/changepassword.component.scss",
"chars": 0,
"preview": ""
},
{
"path": "ui/src/app/modules/changepassword/components/changepassword.component.spec.ts",
"chars": 683,
"preview": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { ChangepasswordComponent } from './ch"
},
{
"path": "ui/src/app/modules/changepassword/components/changepassword.component.ts",
"chars": 1309,
"preview": "import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';\nimport { HttpClient } from \"@angular/common/h"
},
{
"path": "ui/src/app/modules/changepassword/services/changepassword.service.ts",
"chars": 438,
"preview": "import { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { BACKEND_AUTH_URL"
},
{
"path": "ui/src/app/modules/logical-group/components/logical-group.component.css",
"chars": 222,
"preview": ".header-wrapper {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n margin-bottom: 15px;\n}\n\n"
}
]
// ... and 49 more files (download for full content)
About this extraction
This page contains the full source code of the vmware/purser GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 249 files (600.1 KB), approximately 164.0k tokens, and a symbol index with 823 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.