├── .dockerignore ├── .editorconfig ├── .gen └── api │ └── graphql │ └── exec.go ├── .github ├── .editorconfig ├── dependabot.yml └── workflows │ ├── ci.yaml │ ├── docker.yaml │ └── helm_chart.yaml ├── .gitignore ├── .golangci.yml ├── .idea ├── cloudinfo.iml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── copyright │ ├── Banzai_Cloud.xml │ └── profiles_settings.xml ├── dataSources.xml ├── modules.xml ├── runConfigurations │ └── Debug.xml └── scopes │ └── Go_files.xml ├── .licensei.toml ├── .tool-versions ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── api ├── graphql │ ├── .graphqlconfig │ ├── filters.graphql │ ├── instance_types.graphql │ └── schema.graphql └── openapi-spec │ ├── cloudinfo.json │ └── cloudinfo.yaml ├── charts ├── .editorconfig ├── .gitignore └── cloudinfo │ ├── .gitignore │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── requirements.lock │ ├── requirements.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── deployment-frontend.yaml │ ├── deployment-scraper.yaml │ ├── ingress.yaml │ ├── secret.yaml │ ├── service-monitor.yaml │ ├── service.yaml │ └── tests │ │ └── test-connection.yaml │ └── values.yaml ├── cmd └── cloudinfo │ ├── config.go │ ├── main.go │ └── vars.go ├── config.toml.dist ├── configs ├── amazon-service-data.yaml ├── azure-service-data.yaml ├── services.yaml └── vsphere-service-data.yaml ├── credentials ├── amazon_cloudinfo_role.json └── azure_cloudinfo_role.json ├── doc.go ├── docker-compose.override.yml.dist ├── docker-compose.yml ├── docs ├── management │ └── management.md ├── store │ └── store.md └── tracing │ ├── tracing.md │ └── tracing_screenshot.png ├── go.mod ├── go.sum ├── gqlgen.yml ├── internal ├── app │ └── cloudinfo │ │ ├── api │ │ ├── classifier.go │ │ ├── errorresponse.go │ │ ├── filesystem.go │ │ ├── handlers.go │ │ ├── routes.go │ │ ├── types.go │ │ ├── validate.go │ │ └── validate_test.go │ │ ├── cistore │ │ ├── cassandra.go │ │ ├── cassandra_test.go │ │ ├── config.go │ │ ├── go-cache.go │ │ ├── integration_test.go │ │ ├── redis.go │ │ └── redis_test.go │ │ ├── loader │ │ ├── config.go │ │ ├── data.go │ │ ├── loader.go │ │ ├── loader_test.go │ │ └── manager.go │ │ ├── management │ │ ├── config.go │ │ └── handler.go │ │ ├── messaging │ │ └── eventbus.go │ │ ├── problems │ │ └── problems.go │ │ └── tracing │ │ ├── noop.go │ │ └── tracing.go ├── cloudinfo │ ├── cloudinfo.go │ ├── cloudinfo_test.go │ ├── cloudinfoadapter │ │ ├── logger.go │ │ └── logger_test.go │ ├── cloudinfodriver │ │ ├── endpoint.go │ │ ├── endpoints.go │ │ ├── provider.go │ │ ├── provider_endpoints.go │ │ ├── region.go │ │ ├── region_endpoints.go │ │ ├── service.go │ │ ├── service_endpoints.go │ │ └── transport_graphql.go │ ├── distribution │ │ └── config.go │ ├── error_handler.go │ ├── executor.go │ ├── executor_test.go │ ├── infoer.go │ ├── instance_type.go │ ├── instance_type_store.go │ ├── instance_type_test.go │ ├── logger.go │ ├── logger_test.go │ ├── metrics │ │ └── metrics.go │ ├── provider.go │ ├── provider_store.go │ ├── provider_test.go │ ├── providers │ │ ├── alibaba │ │ │ ├── category_mapper.go │ │ │ ├── cloudinfo.go │ │ │ ├── config.go │ │ │ ├── ecs_endpoints.go │ │ │ ├── network_mapper.go │ │ │ └── requests.go │ │ ├── amazon │ │ │ ├── cloudinfo.go │ │ │ ├── cloudinfo_test.go │ │ │ ├── config.go │ │ │ ├── image_helper.go │ │ │ ├── network_mapper.go │ │ │ ├── network_mapper_test.go │ │ │ └── types.go │ │ ├── azure │ │ │ ├── category_mapper.go │ │ │ ├── cloudinfo.go │ │ │ ├── cloudinfo_test.go │ │ │ └── config.go │ │ ├── digitalocean │ │ │ ├── cloudinfo.go │ │ │ └── config.go │ │ ├── google │ │ │ ├── cloudinfo.go │ │ │ ├── config.go │ │ │ └── network_mapper.go │ │ └── oracle │ │ │ ├── client │ │ │ ├── ce.go │ │ │ ├── client.go │ │ │ ├── compute.go │ │ │ ├── helper.go │ │ │ ├── identity.go │ │ │ └── supported.go │ │ │ ├── cloudinfo.go │ │ │ ├── config.go │ │ │ ├── itra.go │ │ │ └── network_mapper.go │ ├── query.go │ ├── region.go │ ├── region_store.go │ ├── region_test.go │ ├── scrape.go │ ├── service.go │ ├── service_store.go │ ├── service_test.go │ ├── store.go │ ├── testdata │ │ └── products.json │ └── types │ │ └── types.go └── platform │ ├── buildinfo │ └── buildinfo.go │ ├── cassandra │ ├── cassandra.go │ └── config.go │ ├── errorhandler │ ├── error_handler.go │ └── panic_handler.go │ ├── jaeger │ ├── config.go │ ├── config_test.go │ └── jaeger.go │ ├── log │ ├── config.go │ ├── logger.go │ ├── middleware.go │ └── standard_logger.go │ ├── prometheus │ ├── config.go │ └── prometheus.go │ └── redis │ ├── config.go │ ├── config_test.go │ └── pool.go ├── main-targets.mk ├── scripts ├── check-header.sh └── fmt-check.sh └── web ├── .browserslistrc ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── package-lock.json ├── package.json ├── proxy.conf.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── constants │ │ └── providers.ts │ ├── models │ │ ├── product.ts │ │ ├── provider.ts │ │ └── region.ts │ ├── modules │ │ ├── banzai-components │ │ │ ├── banzai-components.module.ts │ │ │ └── components │ │ │ │ ├── banzai-copy-icon │ │ │ │ ├── banzai-copy-icon.component.html │ │ │ │ ├── banzai-copy-icon.component.scss │ │ │ │ └── banzai-copy-icon.component.ts │ │ │ │ ├── banzai-selector │ │ │ │ ├── banzai-selector.component.html │ │ │ │ ├── banzai-selector.component.scss │ │ │ │ ├── banzai-selector.component.ts │ │ │ │ └── model │ │ │ │ │ └── selector-item.ts │ │ │ │ └── banzai-table │ │ │ │ ├── animation │ │ │ │ └── expand-and-collapse.animation.ts │ │ │ │ ├── banzai-table.component.html │ │ │ │ ├── banzai-table.component.scss │ │ │ │ ├── banzai-table.component.ts │ │ │ │ ├── directives │ │ │ │ ├── banzai-table-border-details-status.directive.ts │ │ │ │ ├── banzai-table-cell-config.directive.ts │ │ │ │ ├── banzai-table-cell-text-overflow.directive.ts │ │ │ │ ├── banzai-table-cell-width.directive.ts │ │ │ │ ├── banzai-table-row-style.directive.ts │ │ │ │ └── banzai-table-style.directive.ts │ │ │ │ ├── factories │ │ │ │ ├── base-factory.ts │ │ │ │ └── products-list-factory.ts │ │ │ │ ├── model │ │ │ │ └── tabledata.ts │ │ │ │ └── pipe │ │ │ │ ├── to-fixed-number.pipe.ts │ │ │ │ └── truncate-at-middle.pipe.ts │ │ ├── core │ │ │ ├── components │ │ │ │ └── not-found │ │ │ │ │ ├── not-found.component.html │ │ │ │ │ ├── not-found.component.scss │ │ │ │ │ └── not-found.component.ts │ │ │ └── core.module.ts │ │ ├── home │ │ │ ├── components │ │ │ │ ├── product-category-icon │ │ │ │ │ ├── model │ │ │ │ │ │ └── ProductCategory.ts │ │ │ │ │ ├── pipe │ │ │ │ │ │ └── category-icon.pipe.ts │ │ │ │ │ ├── product-category-icon.component.html │ │ │ │ │ ├── product-category-icon.component.scss │ │ │ │ │ └── product-category-icon.component.ts │ │ │ │ └── product-list │ │ │ │ │ ├── product-list.component.html │ │ │ │ │ ├── product-list.component.scss │ │ │ │ │ └── product-list.component.ts │ │ │ ├── containers │ │ │ │ └── home │ │ │ │ │ ├── home.component.html │ │ │ │ │ ├── home.component.scss │ │ │ │ │ └── home.component.ts │ │ │ ├── home.module.ts │ │ │ └── home.routing.ts │ │ ├── nav-bar │ │ │ ├── components │ │ │ │ ├── navigation-bar │ │ │ │ │ ├── navigation-bar.component.html │ │ │ │ │ ├── navigation-bar.component.scss │ │ │ │ │ └── navigation-bar.component.ts │ │ │ │ └── page-title-section │ │ │ │ │ ├── page-title-section.component.html │ │ │ │ │ ├── page-title-section.component.scss │ │ │ │ │ └── page-title-section.component.ts │ │ │ └── nav-bar.module.ts │ │ └── search │ │ │ ├── components │ │ │ └── search │ │ │ │ ├── search.component.html │ │ │ │ ├── search.component.scss │ │ │ │ └── search.component.ts │ │ │ └── search.module.ts │ └── services │ │ └── cloud-info.service.ts ├── assets │ ├── .gitkeep │ └── images │ │ ├── 404.svg │ │ ├── banzai-logo.svg │ │ ├── bg.png │ │ ├── ic_copy.svg │ │ ├── ic_instance_category_compute.svg │ │ ├── ic_instance_category_general.svg │ │ ├── ic_instance_category_gpu.svg │ │ ├── ic_instance_category_memory.svg │ │ ├── ic_instance_category_storage.svg │ │ └── ic_warn.svg ├── environments │ ├── environment.prod.ts │ ├── environment.proxy.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── test.ts ├── theme │ ├── animations.scss │ ├── banzai-table.scss │ ├── styles.scss │ ├── theme-variables.scss │ └── tooltip.scss ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json ├── tslint.json └── web.go /.dockerignore: -------------------------------------------------------------------------------- 1 | /api/ 2 | /bin/ 3 | /build/ 4 | /charts/ 5 | /config.toml 6 | /config.toml.dist 7 | /Dockerfile 8 | /docs/ 9 | /.golangci.yml 10 | /gqlgen.yml 11 | /.licensei.toml 12 | /scripts/ 13 | pkged.go 14 | /web/dist/ 15 | /web/node_modules/ 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.go] 12 | indent_style = tab 13 | 14 | [{Makefile,*.mk}] 15 | indent_style = tab 16 | -------------------------------------------------------------------------------- /.github/.editorconfig: -------------------------------------------------------------------------------- 1 | [{*.yml,*.yaml}] 2 | indent_size = 2 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "npm" 5 | directory: "/web" 6 | labels: 7 | - "dependencies" 8 | schedule: 9 | interval: "daily" 10 | 11 | - package-ecosystem: "gomod" 12 | directory: "/" 13 | labels: 14 | - "dependencies" 15 | schedule: 16 | interval: "daily" 17 | 18 | - package-ecosystem: "docker" 19 | directory: "/" 20 | labels: 21 | - "dependencies" 22 | schedule: 23 | interval: "daily" 24 | 25 | - package-ecosystem: "github-actions" 26 | directory: "/" 27 | labels: 28 | - "dependencies" 29 | schedule: 30 | interval: "daily" 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /build/ 3 | /config.* 4 | !config.toml.dist 5 | /docker-compose.override.yml 6 | /vendor/ 7 | 8 | /.licensei.cache 9 | pkged.go 10 | 11 | # IDE integration 12 | /.vscode/* 13 | !/.vscode/launch.json 14 | !/.vscode/tasks.json 15 | /.idea/* 16 | !/.idea/codeStyles/ 17 | !/.idea/copyright/ 18 | !/.idea/dataSources.xml 19 | !/.idea/*.iml 20 | !/.idea/externalDependencies.xml 21 | !/.idea/go.imports.xml 22 | !/.idea/modules.xml 23 | !/.idea/runConfigurations/ 24 | !/.idea/scopes/ 25 | !/.idea/sqldialects.xml 26 | 27 | .DS_Store 28 | *.swp 29 | test.txt 30 | client_test.go 31 | /.gen/cloudinfo-client/ 32 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | skip-dirs: 3 | - .gen 4 | - client 5 | 6 | linters-settings: 7 | golint: 8 | min-confidence: 0.9 9 | gocyclo: 10 | min-complexity: 15 11 | goimports: 12 | local-prefixes: github.com/banzaicloud/cloudinfo 13 | 14 | linters: 15 | disable-all: true 16 | enable: 17 | - bodyclose 18 | - deadcode 19 | - dogsled 20 | - errcheck 21 | # - exportloopref 22 | - gochecknoinits 23 | - gofmt 24 | - goimports 25 | - golint 26 | - goprintffuncname 27 | - govet 28 | - ineffassign 29 | - misspell 30 | - nakedret 31 | - nolintlint 32 | - rowserrcheck 33 | - staticcheck 34 | - structcheck 35 | - typecheck 36 | - unconvert 37 | - unused 38 | - varcheck 39 | - whitespace 40 | 41 | # disabled for now 42 | # - dupl 43 | # - gochecknoglobals 44 | # - gocognit 45 | # - goconst 46 | # - gocritic 47 | # - gocyclo 48 | # - godot 49 | # - gosec 50 | # - gosimple 51 | # - lll 52 | # - prealloc 53 | # - scopelint 54 | # - stylecheck 55 | # - unparam 56 | 57 | # unused 58 | # - depguard 59 | # - gomodguard 60 | 61 | # don't enable: 62 | # - asciicheck 63 | # - funlen 64 | # - godox 65 | # - goerr113 66 | # - gomnd 67 | # - interfacer 68 | # - maligned 69 | # - nestif 70 | # - testpackage 71 | # - wsl 72 | -------------------------------------------------------------------------------- /.idea/cloudinfo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/copyright/Banzai_Cloud.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cassandra 6 | true 7 | com.dbschema.CassandraJdbcDriver 8 | jdbc:cassandra://localhost:9042 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Debug.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/scopes/Go_files.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.licensei.toml: -------------------------------------------------------------------------------- 1 | approved = [ 2 | "mit", 3 | "apache-2.0", 4 | "bsd-3-clause", 5 | "bsd-2-clause", 6 | "mpl-2.0", 7 | ] 8 | 9 | ignored = [ 10 | "github.com/aliyun/aliyun-oss-go-sdk", 11 | "github.com/ghodss/yaml", # MIT 12 | "sigs.k8s.io/yaml", # MIT 13 | "github.com/gogo/protobuf", # Google 14 | "github.com/golang/protobuf", 15 | "google.golang.org/protobuf", 16 | 17 | "github.com/davecgh/go-spew", # ISC license 18 | "github.com/howeyc/gopass", # ISC license 19 | "github.com/oracle/oci-go-sdk", # UPL-1.0 20 | "gopkg.in/square/go-jose.v2", # Apache 2.0 21 | "github.com/alecthomas/template", # BSD-3 22 | "github.com/alecthomas/units", # MIT 23 | "gopkg.in/alecthomas/kingpin.v2", # MIT 24 | "github.com/prometheus/statsd_exporter", # Apache 2.0 25 | "github.com/form3tech-oss/jwt-go", # MIT 26 | ] 27 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 16.0.0 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # UI build image 2 | FROM node:16.5.0 as frontend 3 | 4 | WORKDIR /web 5 | 6 | COPY web/package.json web/package-lock.json /web/ 7 | 8 | RUN npm install --legacy-peer-deps 9 | 10 | COPY web/ /web/ 11 | 12 | RUN npm run build-prod 13 | 14 | 15 | # Build image 16 | FROM golang:1.16-alpine3.13 AS builder 17 | 18 | ENV GOFLAGS="-mod=readonly" 19 | 20 | RUN apk add --update --no-cache ca-certificates make git curl mercurial 21 | 22 | RUN mkdir -p /workspace 23 | WORKDIR /workspace 24 | 25 | ARG GOPROXY 26 | 27 | COPY go.* /workspace/ 28 | RUN go mod download 29 | 30 | COPY Makefile main-targets.mk /workspace/ 31 | 32 | COPY --from=frontend /web/dist/web /workspace/web/dist/web 33 | COPY . /workspace 34 | 35 | ARG BUILD_TARGET 36 | 37 | RUN set -xe && \ 38 | if [[ "${BUILD_TARGET}" == "debug" ]]; then \ 39 | cd /tmp; GOBIN=/workspace/build/debug go get github.com/go-delve/delve/cmd/dlv; cd -; \ 40 | make build-debug; \ 41 | mv build/debug /build; \ 42 | else \ 43 | make build-release; \ 44 | mv build/release /build; \ 45 | fi 46 | 47 | 48 | # Final image 49 | FROM alpine:3.14.0 50 | 51 | RUN apk add --update --no-cache ca-certificates tzdata bash curl 52 | 53 | SHELL ["/bin/bash", "-c"] 54 | 55 | # set up nsswitch.conf for Go's "netgo" implementation 56 | # https://github.com/gliderlabs/docker-alpine/issues/367#issuecomment-424546457 57 | RUN test ! -e /etc/nsswitch.conf && echo 'hosts: files dns' > /etc/nsswitch.conf 58 | 59 | ARG BUILD_TARGET 60 | 61 | RUN if [[ "${BUILD_TARGET}" == "debug" ]]; then apk add --update --no-cache libc6-compat; fi 62 | 63 | COPY --from=builder /build/* /usr/local/bin/ 64 | 65 | COPY configs /etc/cloudinfo/serviceconfig 66 | 67 | RUN sed -i "s|dataLocation: ./configs/|dataLocation: /etc/cloudinfo/serviceconfig/|g" /etc/cloudinfo/serviceconfig/services.yaml 68 | 69 | ENV CLOUDINFO_SERVICELOADER_SERVICECONFIGLOCATION "/etc/cloudinfo/serviceconfig" 70 | 71 | CMD ["cloudinfo"] 72 | -------------------------------------------------------------------------------- /api/graphql/.graphqlconfig: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cloudinfo GraphQL Schema", 3 | "schemaPath": "schema.graphql", 4 | "extensions": { 5 | "endpoints": { 6 | "Local": { 7 | "url": "http://localhost:8000/graphql", 8 | "headers": { 9 | "user-agent": "JS GraphQL" 10 | }, 11 | "introspect": false 12 | }, 13 | "Debug": { 14 | "url": "http://localhost:8000/cloudinfo/graphql", 15 | "headers": { 16 | "user-agent": "JS GraphQL" 17 | }, 18 | "introspect": false 19 | }, 20 | "Alpha": { 21 | "url": "https://alpha.dev.banzaicloud.com/cloudinfo/graphql", 22 | "headers": { 23 | "user-agent": "JS GraphQL" 24 | }, 25 | "introspect": false 26 | }, 27 | "Beta": { 28 | "url": "https://beta.banzaicloud.io/cloudinfo/graphql", 29 | "headers": { 30 | "user-agent": "JS GraphQL" 31 | }, 32 | "introspect": false 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/graphql/filters.graphql: -------------------------------------------------------------------------------- 1 | input IntFilter { 2 | lt: Int 3 | lte: Int 4 | gt: Int 5 | gte: Int 6 | eq: Int 7 | ne: Int 8 | in: [Int!] 9 | nin: [Int!] 10 | } 11 | 12 | input FloatFilter { 13 | lt: Float 14 | lte: Float 15 | gt: Float 16 | gte: Float 17 | eq: Float 18 | ne: Float 19 | in: [Float!] 20 | nin: [Float!] 21 | } 22 | -------------------------------------------------------------------------------- /api/graphql/instance_types.graphql: -------------------------------------------------------------------------------- 1 | enum NetworkCategory { 2 | LOW 3 | MODERATE 4 | HIGH 5 | } 6 | 7 | enum InstanceTypeCategory { 8 | GENERAL_PURPOSE 9 | MEMORY_OPTIMIZED 10 | STORAGE_OPTIMIZED 11 | COMPUTE_OPTIMIZED 12 | # GPU_INSTANCE 13 | # FPGA_INSTANCES 14 | } 15 | 16 | type InstanceType { 17 | name: String! 18 | region: String! 19 | zone: String! 20 | price: Float! 21 | spotPrice: Float! 22 | cpu: Float! 23 | memory: Float! 24 | gpu: Float! 25 | networkCategory: NetworkCategory! 26 | category: InstanceTypeCategory! 27 | } 28 | 29 | input NetworkCategoryFilter { 30 | eq: NetworkCategory 31 | ne: NetworkCategory 32 | in: [NetworkCategory!] 33 | nin: [NetworkCategory!] 34 | } 35 | input InstanceTypeCategoryFilter { 36 | eq: InstanceTypeCategory 37 | ne: InstanceTypeCategory 38 | in: [InstanceTypeCategory!] 39 | nin: [InstanceTypeCategory!] 40 | } 41 | 42 | input InstanceTypeQueryInput { 43 | price: FloatFilter 44 | spotPrice: FloatFilter 45 | spot: Boolean 46 | cpu: FloatFilter 47 | memory: FloatFilter 48 | gpu: FloatFilter 49 | networkCategory: NetworkCategoryFilter 50 | category: InstanceTypeCategoryFilter 51 | } 52 | -------------------------------------------------------------------------------- /api/graphql/schema.graphql: -------------------------------------------------------------------------------- 1 | type Provider { 2 | code: String! 3 | name: String! 4 | services: [Service!]! 5 | } 6 | 7 | type Service { 8 | code: String! 9 | regions: [Region!]! 10 | } 11 | 12 | type Region { 13 | code: String! 14 | name: String! 15 | zones: [Zone!]! 16 | } 17 | 18 | type Zone { 19 | code: String! 20 | } 21 | 22 | type Query { 23 | providers: [Provider!]! 24 | instanceTypes(provider: String!, service: String!, region: String, zone: String, filter: InstanceTypeQueryInput): [InstanceType!]! 25 | } 26 | -------------------------------------------------------------------------------- /charts/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.yaml] 2 | indent_size = 2 3 | -------------------------------------------------------------------------------- /charts/.gitignore: -------------------------------------------------------------------------------- 1 | values.local.yaml 2 | -------------------------------------------------------------------------------- /charts/cloudinfo/.gitignore: -------------------------------------------------------------------------------- 1 | /charts 2 | -------------------------------------------------------------------------------- /charts/cloudinfo/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /charts/cloudinfo/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: Cloud instance type and price information as a service 3 | name: cloudinfo 4 | version: 0.10.0 5 | 6 | appVersion: "0.18.0" 7 | 8 | home: https://banzaicloud.com 9 | 10 | maintainers: 11 | - name: Banzai Cloud 12 | email: info@banzaicloud.com 13 | 14 | sources: 15 | - https://banzaicloud.com 16 | - https://github.com/banzaicloud 17 | - https://github.com/banzaicloud/cloudinfo 18 | 19 | icon: https://banzaicloud.com/img/banzai-cloud-logo.png 20 | -------------------------------------------------------------------------------- /charts/cloudinfo/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: redis 3 | repository: https://charts.helm.sh/stable 4 | version: 10.5.7 5 | digest: sha256:7e85d6305283ac9ca01f9a5b878e4e2500443ee6857a7fe3bddae20cc576974a 6 | generated: "2020-11-11T16:06:09.716019+01:00" 7 | -------------------------------------------------------------------------------- /charts/cloudinfo/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: redis 3 | version: 10.5.7 4 | repository: https://charts.helm.sh/stable 5 | condition: redis.enabled 6 | -------------------------------------------------------------------------------- /charts/cloudinfo/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.frontend.ingress.enabled }} 3 | {{- range .Values.frontend.ingress.hosts }} 4 | http{{ if $.Values.frontend.ingress.tls }}s{{ end }}://{{ . }} 5 | {{- end }} 6 | {{- else if contains "NodePort" .Values.frontend.service.type }} 7 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "cloudinfo.frontend.fullname" . }}) 8 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 9 | echo http://$NODE_IP:$NODE_PORT 10 | {{- else if contains "LoadBalancer" .Values.frontend.service.type }} 11 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 12 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "cloudinfo.frontend.fullname" . }}' 13 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "cloudinfo.frontend.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 14 | echo http://$SERVICE_IP:{{ .Values.frontend.service.port }} 15 | {{- else if contains "ClusterIP" .Values.frontend.service.type }} 16 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "cloudinfo.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=frontend" -o jsonpath="{.items[0].metadata.name}") 17 | echo "Visit http://127.0.0.1:8080 to use your application" 18 | kubectl port-forward $POD_NAME 8080:80 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /charts/cloudinfo/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: {{ template "cloudinfo.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "cloudinfo.name" . }} 7 | helm.sh/chart: {{ include "cloudinfo.chart" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: {{ .Release.Service }} 10 | type: Opaque 11 | stringData: 12 | config.yaml: | 13 | app: 14 | basePath: {{ .Values.app.basePath }} 15 | log: 16 | level: {{ .Values.app.logLevel }} 17 | metrics: 18 | enabled: {{ .Values.metrics.enabled }} 19 | config: 20 | vault: 21 | {{ toYaml .Values.app.vault | indent 8 }} 22 | store: 23 | redis: 24 | enabled: {{ .Values.store.redis.enabled }} 25 | host: {{ include "cloudinfo.redis.host" . }} 26 | port: {{ include "cloudinfo.redis.port" . }} 27 | provider: 28 | {{ toYaml .Values.providers | indent 6 }} 29 | -------------------------------------------------------------------------------- /charts/cloudinfo/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "cloudinfo.frontend.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "cloudinfo.name" . }} 7 | app.kubernetes.io/component: "frontend" 8 | helm.sh/chart: {{ include "cloudinfo.chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | spec: 12 | type: {{ .Values.frontend.service.type }} 13 | ports: 14 | - port: {{ .Values.frontend.service.port }} 15 | targetPort: http 16 | protocol: TCP 17 | name: http 18 | selector: 19 | app.kubernetes.io/name: {{ include "cloudinfo.name" . }} 20 | app.kubernetes.io/component: "frontend" 21 | app.kubernetes.io/instance: {{ .Release.Name }} 22 | 23 | {{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} 24 | --- 25 | apiVersion: v1 26 | kind: Service 27 | metadata: 28 | name: {{ template "cloudinfo.frontend.fullname" . }}-metrics 29 | labels: 30 | app.kubernetes.io/name: {{ include "cloudinfo.name" . }} 31 | app.kubernetes.io/component: "frontend" 32 | helm.sh/chart: {{ include "cloudinfo.chart" . }} 33 | app.kubernetes.io/instance: {{ .Release.Name }} 34 | app.kubernetes.io/managed-by: {{ .Release.Service }} 35 | spec: 36 | type: ClusterIP 37 | ports: 38 | - port: {{ .Values.metrics.port }} 39 | name: metrics 40 | protocol: TCP 41 | targetPort: metrics 42 | selector: 43 | app.kubernetes.io/name: {{ include "cloudinfo.name" . }} 44 | app.kubernetes.io/component: "frontend" 45 | app.kubernetes.io/instance: {{ .Release.Name }} 46 | 47 | --- 48 | apiVersion: v1 49 | kind: Service 50 | metadata: 51 | name: {{ template "cloudinfo.scraper.fullname" . }}-metrics 52 | labels: 53 | app.kubernetes.io/name: {{ include "cloudinfo.name" . }} 54 | app.kubernetes.io/component: "scraper" 55 | helm.sh/chart: {{ include "cloudinfo.chart" . }} 56 | app.kubernetes.io/instance: {{ .Release.Name }} 57 | app.kubernetes.io/managed-by: {{ .Release.Service }} 58 | spec: 59 | type: ClusterIP 60 | ports: 61 | - port: {{ .Values.metrics.port }} 62 | name: metrics 63 | protocol: TCP 64 | targetPort: metrics 65 | selector: 66 | app.kubernetes.io/name: {{ include "cloudinfo.name" . }} 67 | app.kubernetes.io/component: "scraper" 68 | app.kubernetes.io/instance: {{ .Release.Name }} 69 | {{- end }} 70 | -------------------------------------------------------------------------------- /charts/cloudinfo/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "cloudinfo.frontend.fullname" . }}-test-connection" 5 | labels: 6 | app.kubernetes.io/name: {{ include "cloudinfo.name" . }} 7 | app.kubernetes.io/component: "frontend" 8 | helm.sh/chart: {{ include "cloudinfo.chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | annotations: 12 | "helm.sh/hook": test-success 13 | spec: 14 | containers: 15 | - name: wget 16 | image: busybox 17 | command: ['wget'] 18 | args: ['http://{{ include "cloudinfo.fullname" . }}:{{ .Values.frontend.service.port }}{{ printf "%s/status" .Values.app.basePath | clean }}'] 19 | restartPolicy: Never 20 | -------------------------------------------------------------------------------- /cmd/cloudinfo/vars.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | const ( 18 | // appName is an identifier-like name used anywhere this app needs to be identified. 19 | // 20 | // It identifies the application itself, the actual instance needs to be identified via environment 21 | // and other details. 22 | appName = "cloudinfo" 23 | 24 | // firendlyAppName is the visible name of the application. 25 | friendlyAppName = "Banzai Cloud Cloudinfo Service" 26 | 27 | // envPrefix is prepended to environment variables when processing configuration. 28 | envPrefix = "cloudinfo" 29 | ) 30 | -------------------------------------------------------------------------------- /configs/services.yaml: -------------------------------------------------------------------------------- 1 | # part of the application configuration this file lists the supported services and related meta information 2 | # services define cloud product information available for a given cloud provider offered service (eg: vm-s that can be part 3 | # of kubernetes clusters with a given kubernetes version 4 | amazon: 5 | - 6 | name: compute 7 | isstatic: false 8 | - 9 | name: eks 10 | isstatic: false 11 | - 12 | name: pke 13 | isstatic: true 14 | dataLocation: ./configs/ 15 | dataFile: amazon-service-data 16 | dataType: yaml 17 | alibaba: 18 | - 19 | name: compute 20 | isstatic: false 21 | - 22 | name: ack 23 | isstatic: false 24 | azure: 25 | - 26 | name: compute 27 | isstatic: false 28 | - 29 | name: aks 30 | isstatic: false 31 | - 32 | name: pke 33 | isstatic: true 34 | dataLocation: ./configs/ 35 | dataFile: azure-service-data 36 | dataType: yaml 37 | google: 38 | - 39 | name: compute 40 | isstatic: false 41 | - 42 | name: gke 43 | isstatic: false 44 | oracle: 45 | - 46 | name: compute 47 | isstatic: false 48 | - 49 | name: oke 50 | isstatic: false 51 | digitalocean: 52 | - 53 | name: compute 54 | isstatic: false 55 | - 56 | name: dok 57 | isstatic: false 58 | vsphere: 59 | - 60 | name: pke 61 | isstatic: true 62 | dataLocation: ./configs/ 63 | dataFile: vsphere-service-data 64 | dataType: yaml 65 | -------------------------------------------------------------------------------- /credentials/amazon_cloudinfo_role.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "pricing:GetAttributeValues", 8 | "pricing:GetProducts" 9 | ], 10 | "Resource": [ 11 | "*" 12 | ] 13 | }, 14 | { 15 | "Effect": "Allow", 16 | "Action": [ 17 | "ec2:DescribeAvailabilityZones", 18 | "ec2:DescribeImages", 19 | "ec2:DescribeSpotPriceHistory" 20 | ], 21 | "Resource": [ 22 | "*" 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /credentials/azure_cloudinfo_role.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "Cloudinfo", 3 | "IsCustom": true, 4 | "Description": "minimum roles for using Cloudinfo", 5 | "Actions": [ 6 | "Microsoft.Compute/virtualMachines/vmSizes/read", 7 | "Microsoft.Resources/subscriptions/locations/read", 8 | "Microsoft.Resources/providers/read", 9 | "Microsoft.ContainerService/containerServices/read", 10 | "Microsoft.Commerce/RateCard/read" 11 | ], 12 | "AssignableScopes": [ 13 | "/subscriptions/" 14 | ] 15 | } -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | -------------------------------------------------------------------------------- /docker-compose.override.yml.dist: -------------------------------------------------------------------------------- 1 | version: "3.1" 2 | 3 | services: 4 | jaeger: 5 | ports: 6 | - 127.0.0.1:5778:5778 7 | - 127.0.0.1:16686:16686 8 | - 127.0.0.1:14268:14268 9 | 10 | redis: 11 | ports: 12 | - 127.0.0.1:6379:6379 13 | 14 | cassandra: 15 | ports: 16 | - 127.0.0.1:9042:9042 17 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.1" 2 | 3 | services: 4 | jaeger: 5 | image: jaegertracing/all-in-one:${JAEGER_TAG:-latest} 6 | 7 | redis: 8 | image: redis:5.0 9 | 10 | cassandra: 11 | image: cassandra:3.11 12 | 13 | -------------------------------------------------------------------------------- /docs/management/management.md: -------------------------------------------------------------------------------- 1 | ### Cloudinfo Management Service 2 | 3 | Cloudinfo contains an internal management api to help administrators operating the service. 4 | The management service is internally exposed on a RESTful API (completely separated from the public API) 5 | 6 | These operations mainly affect the Cloud Info Store that backs the Cloudinfo application 7 | 8 | The management service can be configured with the following environment variables: 9 | 10 | ``management.enabled`` true by default 11 | 12 | ``management.address`` :8001 by default 13 | 14 | If enabled, along with the Cloudinfo application there will be another service started which listens at the address specified in the second env var. 15 | 16 | #### Management operations: 17 | 18 | The context path for management operations is: 19 | 20 | :/management/store 21 | 22 | * Export 23 | 24 | This operation exports the content of the Cloud Product Store 25 | ```bash 26 | curl -X GET \ 27 | http://localhost:8001/management/store/export > store.txt 28 | ``` 29 | 30 | * Import 31 | 32 | The operation loads data into the Cloud Product Store 33 | 34 | ```bash 35 | curl -X PUT -F "data=@store.txt" \ 36 | http://localhost:8001/management/store/import 37 | ``` 38 | 39 | * Refresh 40 | Initiates a scraping process for the given provider for cloud product information. The refresh operation is performed asynchronously so it should only be used to trigger it. 41 | ```bash 42 | curl -X PUT \ 43 | http://localhost:8001/management/store/refresh/ 44 | ``` 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/store/store.md: -------------------------------------------------------------------------------- 1 | ### Cloudinfo Store configuration 2 | 3 | The Cloudinfo application persists information scraped form cloud providers in a component named 'Store' 4 | 5 | This store may be backed by various solutions that can be selected using configuration. 6 | 7 | The default Store implementation is an in memory KV store. 8 | 9 | Suppored Stores: 10 | 11 | #### Redis 12 | 13 | 14 | 15 | #### Cassandra -------------------------------------------------------------------------------- /docs/tracing/tracing.md: -------------------------------------------------------------------------------- 1 | #### Tracing 2 | 3 | The cloudinfo application contains code instrumentation that makes possible tracing trough opencensus / jaeger 4 | 5 | For the time being the tracing solution only instruments the background processes in the application (collecting cloud information from the providers) 6 | 7 | #### Enable tracing 8 | 9 | The application comes with tracing disabled by default. Tracing can be configured through the following environment variables: 10 | 11 | | Envvar name | default value | 12 | | -------------------------------------------| --------------------------------------------------------| 13 | | INSTRUMENTATION_JAEGER_ENABLED | false | 14 | | INSTRUMENTATION_JAEGER_COLLECTORENDPOINT | http://localhost:14268/api/traces?format=jaeger.thrift | 15 | | INSTRUMENTATION_JAEGER_AGENTENDPOINT | localhost:6832 | 16 | | INSTRUMENTATION_JAEGER_USERNAME | | 17 | | INSTRUMENTATION_JAEGER_PASSWORD | | 18 | 19 | #### Requirements 20 | 21 | Cludinfo tracing reports to a jaeger installation that needs to be reachable by the application. The related configuration entries need to be set accordingly. 22 | 23 | #### Reference 24 | 25 | https://opencensus.io/quickstart/go/tracing/ 26 | https://opencensus.io/exporters/supported-exporters/go/jaeger/ 27 | https://www.jaegertracing.io/docs/1.9/ 28 | -------------------------------------------------------------------------------- /docs/tracing/tracing_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banzaicloud/cloudinfo/22150862eee0b6b9a21230a9074fc7c38afe68a9/docs/tracing/tracing_screenshot.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/banzaicloud/cloudinfo 2 | 3 | go 1.16 4 | 5 | require ( 6 | contrib.go.opencensus.io/exporter/jaeger v0.2.1 7 | contrib.go.opencensus.io/exporter/prometheus v0.3.0 8 | emperror.dev/emperror v0.33.0 9 | emperror.dev/errors v0.8.0 10 | emperror.dev/handler/logur v0.5.0 11 | github.com/99designs/gqlgen v0.13.0 12 | github.com/Azure/azure-sdk-for-go v55.5.0+incompatible 13 | github.com/Azure/go-autorest/autorest v0.11.19 14 | github.com/Azure/go-autorest/autorest/azure/auth v0.5.7 15 | github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect 16 | github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect 17 | github.com/aliyun/alibaba-cloud-sdk-go v1.61.1190 18 | github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef 19 | github.com/aws/aws-sdk-go v1.39.0 20 | github.com/banzaicloud/go-gin-prometheus v0.1.0 21 | github.com/digitalocean/godo v1.62.0 22 | github.com/gin-contrib/cors v1.3.1 23 | github.com/gin-contrib/static v0.0.1 24 | github.com/gin-gonic/gin v1.7.2 25 | github.com/go-kit/kit v0.10.0 26 | github.com/go-playground/validator/v10 v10.6.1 27 | github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556 28 | github.com/gofrs/uuid v4.0.0+incompatible 29 | github.com/gomodule/redigo v2.0.0+incompatible 30 | github.com/mitchellh/mapstructure v1.4.1 31 | github.com/moogar0880/problems v0.1.1 32 | github.com/oracle/oci-go-sdk v24.3.0+incompatible 33 | github.com/patrickmn/go-cache v2.1.0+incompatible 34 | github.com/prometheus/client_golang v1.11.0 35 | github.com/prometheus/common v0.26.0 36 | github.com/sagikazarmark/viperx v0.8.0 37 | github.com/shopspring/decimal v1.2.0 // indirect 38 | github.com/sirupsen/logrus v1.8.1 39 | github.com/spf13/pflag v1.0.5 40 | github.com/spf13/viper v1.8.1 41 | github.com/stretchr/testify v1.7.0 42 | github.com/vektah/gqlparser/v2 v2.2.0 43 | go.opencensus.io v0.23.0 44 | golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 45 | google.golang.org/api v0.79.0 46 | logur.dev/adapter/logrus v0.5.0 47 | logur.dev/logur v0.17.0 48 | ) 49 | -------------------------------------------------------------------------------- /gqlgen.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | - "api/graphql/*.graphql" 3 | 4 | exec: 5 | filename: .gen/api/graphql/exec.go 6 | package: graphql 7 | 8 | model: 9 | filename: .gen/api/graphql/generated.go 10 | package: graphql 11 | 12 | struct_tag: json 13 | omit_slice_element_pointers: true 14 | 15 | models: 16 | Provider: 17 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.Provider 18 | 19 | Service: 20 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.Service 21 | 22 | Region: 23 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.Region 24 | 25 | Zone: 26 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.Zone 27 | 28 | InstanceType: 29 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.InstanceType 30 | 31 | NetworkCategory: 32 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.NetworkCategory 33 | 34 | InstanceTypeCategory: 35 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.InstanceTypeCategory 36 | 37 | IntFilter: 38 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.IntFilter 39 | 40 | FloatFilter: 41 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.FloatFilter 42 | 43 | NetworkCategoryFilter: 44 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.NetworkCategoryFilter 45 | 46 | InstanceTypeCategoryFilter: 47 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.InstanceTypeCategoryFilter 48 | 49 | InstanceTypeQueryInput: 50 | model: github.com/banzaicloud/cloudinfo/internal/cloudinfo.InstanceTypeQueryFilter 51 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/api/errorresponse.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package api 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | 22 | "github.com/banzaicloud/cloudinfo/internal/app/cloudinfo/problems" 23 | ) 24 | 25 | // Responder marks responders 26 | type Responder interface { 27 | // Respond implements the responding logic / it's intended to be self-contained 28 | Respond(ginCtx *gin.Context, err error) 29 | } 30 | 31 | // errorResponder struct in charge for assembling classified error responses 32 | type errorResponder struct { 33 | errClassifier Classifier 34 | } 35 | 36 | // Respond assembles the error response corresponding to the passed in error 37 | func (er *errorResponder) Respond(ginCtx *gin.Context, err error) { 38 | if responseData, e := er.errClassifier.Classify(err); e == nil { 39 | er.respond(ginCtx, responseData) 40 | return 41 | } 42 | 43 | er.respond(ginCtx, problems.NewUnknownProblem(err)) 44 | } 45 | 46 | // respond sets the response in the gin context 47 | func (er *errorResponder) respond(ginCtx *gin.Context, d interface{}) { 48 | if problems.IsDefaultProblem(d) { 49 | ginCtx.AbortWithStatusJSON(problems.ProblemStatus(d), d) 50 | return 51 | } 52 | 53 | ginCtx.AbortWithStatusJSON(http.StatusInternalServerError, problems.NewUnknownProblem(d)) 54 | } 55 | 56 | // NewErrorResponder configures a new error responder 57 | func NewErrorResponder() Responder { 58 | return &errorResponder{ 59 | errClassifier: NewErrorClassifier(), 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/api/filesystem.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package api 16 | 17 | import ( 18 | "io/fs" 19 | "net/http" 20 | "strings" 21 | 22 | "github.com/gin-contrib/static" 23 | ) 24 | 25 | func fileSystem(fsys fs.FS) static.ServeFileSystem { 26 | return staticFileSystem{ 27 | httpfsys: http.FS(fsys), 28 | fsys: fsys, 29 | } 30 | } 31 | 32 | type staticFileSystem struct { 33 | httpfsys http.FileSystem 34 | fsys fs.FS 35 | } 36 | 37 | func (f staticFileSystem) Open(name string) (http.File, error) { 38 | return f.httpfsys.Open(name) 39 | } 40 | 41 | func (f staticFileSystem) Exists(prefix string, filePath string) bool { 42 | if p := strings.TrimPrefix(filePath, prefix); len(p) < len(filePath) { 43 | _, err := fs.Stat(f.fsys, strings.TrimLeft(p, "/")) 44 | 45 | return err == nil 46 | } 47 | 48 | return false 49 | } 50 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/api/validate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package api 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/go-playground/validator/v10" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestGetProviderPathParamsValidation(t *testing.T) { 25 | tests := []struct { 26 | name string 27 | pathParam interface{} 28 | check func(t *testing.T, err error) 29 | }{ 30 | { 31 | name: "getProvider path params validation should fail when provider not specified", 32 | pathParam: &GetProviderPathParams{}, 33 | check: func(t *testing.T, err error) { 34 | assert.NotNil(t, err, "validation should fail", err) 35 | }, 36 | }, 37 | { 38 | name: "getProvider path params validation should fail when provider is not supported", 39 | pathParam: &GetProviderPathParams{Provider: "unsupported"}, 40 | check: func(t *testing.T, err error) { 41 | assert.NotNil(t, err, "validation should fail %#V", err) 42 | }, 43 | }, 44 | { 45 | name: "getProvider path params validation should pass when provider is supported", 46 | pathParam: &GetProviderPathParams{Provider: "test-provider-1"}, 47 | check: func(t *testing.T, err error) { 48 | assert.Nil(t, err, "validation should not fail") 49 | }, 50 | }, 51 | } 52 | 53 | // setup the validator 54 | v := validator.New() 55 | v.SetTagName("binding") 56 | if err := v.RegisterValidation("provider", providerValidator([]string{"test-provider-1", "test-provider-2"})); err != nil { 57 | t.Fatal("failed to register provider validator") 58 | } 59 | 60 | for _, test := range tests { 61 | t.Run(test.name, func(t *testing.T) { 62 | test.check(t, v.Struct(test.pathParam)) 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/cistore/cassandra_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cistore 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | "time" 21 | 22 | "github.com/stretchr/testify/assert" 23 | "logur.dev/logur" 24 | 25 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/cloudinfoadapter" 26 | "github.com/banzaicloud/cloudinfo/internal/platform/cassandra" 27 | ) 28 | 29 | func testCassandraStore(t *testing.T) { 30 | cps := NewCassandraProductStore( 31 | cassandra.Config{ 32 | Hosts: []string{"localhost"}, 33 | Port: 9042, 34 | Keyspace: "test", 35 | Table: "testPi", 36 | }, 37 | cloudinfoadapter.NewLogger(&logur.TestLogger{}), 38 | ) 39 | 40 | ctx, cancelFunction := context.WithTimeout(context.Background(), 10*time.Second) 41 | defer cancelFunction() 42 | loop: 43 | for { 44 | select { 45 | case <-ctx.Done(): 46 | t.Fatalf("waiting for Cassandra product store to become ready failed, err: %s", ctx.Err().Error()) 47 | default: 48 | if cps.Ready() { 49 | break loop 50 | } else { 51 | time.Sleep(time.Second) 52 | } 53 | } 54 | } 55 | 56 | // insert an entry 57 | cps.StoreStatus("amazon", "status") 58 | 59 | // retrieve it 60 | status, ok := cps.GetStatus("amazon") 61 | assert.True(t, ok) 62 | assert.Equal(t, "status", status) 63 | } 64 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/cistore/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cistore 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 21 | "github.com/banzaicloud/cloudinfo/internal/platform/cassandra" 22 | "github.com/banzaicloud/cloudinfo/internal/platform/redis" 23 | ) 24 | 25 | // CloudInfoStore configuration 26 | type Config struct { 27 | Redis redis.Config 28 | GoCache GoCacheConfig 29 | Cassandra cassandra.Config 30 | } 31 | 32 | // GoCacheConfig configuration 33 | type GoCacheConfig struct { 34 | expiration time.Duration 35 | cleanupInterval time.Duration 36 | } 37 | 38 | // NewCloudInfoStore builds a new cloudinfo store based on the passed in configuration 39 | // This method is in charge to create the appropriate store instance eventually to implement a fallback mechanism to the default store 40 | func NewCloudInfoStore(conf Config, log cloudinfo.Logger) cloudinfo.CloudInfoStore { 41 | // use redis if enabled 42 | if conf.Redis.Enabled { 43 | log.Info("using Redis as product store") 44 | return NewRedisProductStore(conf.Redis, log) 45 | } 46 | 47 | if conf.Cassandra.Enabled { 48 | log.Info("using Cassandra as product store") 49 | return NewCassandraProductStore(conf.Cassandra, log) 50 | } 51 | 52 | // fallback to the "initial" implementation 53 | log.Info("using in-mem cache as product store") 54 | return NewCacheProductStore(conf.GoCache.expiration, conf.GoCache.cleanupInterval, log) 55 | } 56 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/cistore/integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cistore 16 | 17 | import ( 18 | "flag" 19 | "regexp" 20 | "testing" 21 | ) 22 | 23 | // TestIntegration test suite for functional testing cloud 24 | // Note: use the attached docker-compose file for quickly setup the environment 25 | func TestIntegration(t *testing.T) { 26 | if m := flag.Lookup("test.run").Value.String(); m == "" || !regexp.MustCompile(m).MatchString(t.Name()) { 27 | t.Skip("skipping as execution was not requested explicitly using go test -run") 28 | } 29 | 30 | t.Parallel() 31 | 32 | t.Run("testCassandraStore", testCassandraStore) 33 | t.Run("testRedisStore", testRedisStore) 34 | } 35 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/cistore/redis_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cistore 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | "time" 21 | 22 | "github.com/stretchr/testify/assert" 23 | "logur.dev/logur" 24 | 25 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/cloudinfoadapter" 26 | "github.com/banzaicloud/cloudinfo/internal/platform/redis" 27 | ) 28 | 29 | // Skeleton for dev testing the redis store 30 | // use the attached docker-compose.yaml compose file 31 | func testRedisStore(t *testing.T) { 32 | cfg := redis.Config{ 33 | Host: "localhost", 34 | Port: 6379, 35 | } 36 | 37 | ps := NewRedisProductStore(cfg, cloudinfoadapter.NewLogger(&logur.TestLogger{})) 38 | 39 | ctx, cancelFunction := context.WithTimeout(context.Background(), 10*time.Second) 40 | defer cancelFunction() 41 | loop: 42 | for { 43 | select { 44 | case <-ctx.Done(): 45 | t.Fatalf("waiting for Redis product store to become ready failed, err: %s", ctx.Err().Error()) 46 | default: 47 | if ps.Ready() { 48 | break loop 49 | } else { 50 | time.Sleep(time.Second) 51 | } 52 | } 53 | } 54 | 55 | // insert an entry 56 | ps.StoreStatus("amazon", "status") 57 | 58 | // retrieve it 59 | status, ok := ps.GetStatus("amazon") 60 | assert.True(t, ok) 61 | assert.Equal(t, "status", status) 62 | } 63 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/loader/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package loader 16 | 17 | type Config struct { 18 | // the location of the yaml(s) defining services for providers 19 | ServiceConfigLocation string 20 | 21 | // the name of the file with the data 22 | ServiceConfigName string 23 | 24 | // the format of the data file (json / yaml) 25 | Format string 26 | } 27 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/loader/data.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package loader 16 | 17 | import ( 18 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 19 | ) 20 | 21 | const ( 22 | exact = "exact" 23 | exclude = "exclude" 24 | include = "include" 25 | ) 26 | 27 | // ServiceData service data representation corresponding to the data to parsed from the external yaml / json 28 | type ServiceData struct { 29 | // embedded service 30 | Service `mapstructure:",squash"` 31 | Provider string 32 | Regions []Region 33 | } 34 | 35 | type Region struct { 36 | Name string 37 | Id string 38 | Data RegionData 39 | } 40 | 41 | type RegionData struct { 42 | Zones ZoneData 43 | Images ImageData 44 | Versions VersionData 45 | Vms VmData 46 | } 47 | 48 | type ZoneData struct { 49 | Strategy string 50 | Data []string 51 | } 52 | 53 | type ImageData struct { 54 | Strategy string 55 | Data []types.Image 56 | } 57 | 58 | type VersionData struct { 59 | Strategy string 60 | Data []types.LocationVersion 61 | } 62 | 63 | type VmData struct { 64 | Strategy string 65 | Data []types.VMInfo 66 | } 67 | 68 | func (wmd VmData) ContainsVM(VMType string) bool { 69 | for _, VMInfo := range wmd.Data { 70 | if VMInfo.Type == VMType { 71 | return true 72 | } 73 | } 74 | 75 | return false 76 | } 77 | 78 | type Service struct { 79 | Name string 80 | IsStatic bool 81 | Source string 82 | DataLocation string 83 | DataFile string 84 | DataType string 85 | } 86 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/loader/loader_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package loader 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestDefaultServiceLoader_Load(t *testing.T) { 22 | 23 | // config := Config{ 24 | // SvcDataLocation: ".", 25 | // SvcDefinitionsLocation: ".", 26 | // Name: "service-definition", 27 | // } 28 | // l := logrus.New() 29 | // level, _ := logrus.ParseLevel("debug") 30 | // l.SetLevel(level) 31 | // 32 | // log := logrusadapter.New(l) 33 | // 34 | // store := cloudinfo.NewCacheProductStore(10*time.Minute, 10*time.Minute, log) 35 | // 36 | // loader := NewDefaultServiceLoader(config, store, log) 37 | // 38 | // //loader.LoadServiceInformation(context.Background()) 39 | // loader.ConfigureServices(context.Background()) 40 | // 41 | // reg, _ := store.GetRegions("test-prv", "test-svc") 42 | // log.Info("stored", map[string]interface{}{"cnt": reg}) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/management/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package management 16 | 17 | import ( 18 | "emperror.dev/emperror" 19 | "emperror.dev/errors" 20 | ) 21 | 22 | type Config struct { 23 | Enabled bool 24 | Address string 25 | } 26 | 27 | func (cfg *Config) Validate() error { 28 | if cfg.Address == "" { 29 | return emperror.With(errors.New("management address must be set"), "validation", "management.address") 30 | } 31 | 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/messaging/eventbus.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package messaging 16 | 17 | import ( 18 | "strings" 19 | 20 | "emperror.dev/emperror" 21 | evbus "github.com/asaskevich/EventBus" 22 | ) 23 | 24 | // EventBus event bus abstraction for the application to decouple vendor or lib specifics 25 | 26 | type EventBus interface { 27 | // PublishScrapingComplete emits a "scraping complete" message for the given provider 28 | PublishScrapingComplete(provider string) 29 | 30 | // SubscribeScrapingComplete 31 | SubscribeScrapingComplete(provider string, callback interface{}) 32 | } 33 | 34 | const ( 35 | topicPrefix = "load:service" 36 | ) 37 | 38 | // defaultEventBus default EventBus component implementation backed by https://github.com/asaskevich/EventBus 39 | type defaultEventBus struct { 40 | eventBus evbus.Bus 41 | errorHandler emperror.ErrorHandler 42 | } 43 | 44 | func (eb *defaultEventBus) PublishScrapingComplete(provider string) { 45 | eb.eventBus.Publish(eb.providerScrapingTopic(provider)) 46 | } 47 | 48 | func (eb *defaultEventBus) SubscribeScrapingComplete(provider string, callback interface{}) { 49 | if err := eb.eventBus.SubscribeAsync(eb.providerScrapingTopic(provider), callback, false); err != nil { 50 | eb.errorHandler.Handle(err) 51 | } 52 | } 53 | 54 | func (eb *defaultEventBus) providerScrapingTopic(provider string) string { 55 | return strings.Join([]string{topicPrefix, provider}, ":") 56 | } 57 | 58 | // NewDefaultEventBus creates an event bus backed by https://github.com/asaskevich/EventBus 59 | func NewDefaultEventBus(_ emperror.ErrorHandler) EventBus { 60 | return &defaultEventBus{ 61 | eventBus: evbus.New(), 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/problems/problems.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package problems 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "net/http" 21 | 22 | "github.com/moogar0880/problems" 23 | ) 24 | 25 | const ( 26 | validationProblemTitle = "validation problem" 27 | providerProblemTitle = "cloud provider problem" 28 | ) 29 | 30 | type ProblemWrapper struct { 31 | *problems.DefaultProblem 32 | } 33 | 34 | func (pw *ProblemWrapper) String() string { 35 | str, _ := json.Marshal(pw.DefaultProblem) 36 | return string(str) 37 | } 38 | 39 | func NewValidationProblem(code int, details string) *ProblemWrapper { 40 | pb := problems.NewDetailedProblem(code, details) 41 | pb.Title = validationProblemTitle 42 | return &ProblemWrapper{pb} 43 | } 44 | 45 | func NewProviderProblem(code int, details string) *ProblemWrapper { 46 | pb := problems.NewDetailedProblem(code, details) 47 | pb.Title = providerProblemTitle 48 | return &ProblemWrapper{pb} 49 | } 50 | 51 | func NewUnknownProblem(un interface{}) *ProblemWrapper { 52 | return &ProblemWrapper{problems.NewDetailedProblem(http.StatusInternalServerError, fmt.Sprintf("%s", un))} 53 | } 54 | 55 | func IsDefaultProblem(d interface{}) bool { 56 | _, ok := d.(*ProblemWrapper) 57 | return ok 58 | } 59 | 60 | func NewDetailedProblem(status int, details string) *ProblemWrapper { 61 | return &ProblemWrapper{problems.NewDetailedProblem(status, details)} 62 | } 63 | 64 | func ProblemStatus(d interface{}) int { 65 | if pb, ok := d.(*ProblemWrapper); ok { 66 | return pb.Status 67 | } 68 | return -1 69 | } 70 | -------------------------------------------------------------------------------- /internal/app/cloudinfo/tracing/noop.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tracing 16 | 17 | import "context" 18 | 19 | type noOpTracer struct { 20 | } 21 | 22 | func (*noOpTracer) StartAndLink(parentCtx context.Context, name string) (context.Context, *CiSpan) { 23 | return parentCtx, nil 24 | } 25 | 26 | func (*noOpTracer) EndSpanInstance(span *CiSpan) { 27 | // do noting 28 | } 29 | 30 | func (*noOpTracer) StartSpan(ctx context.Context, name string) (context.Context, *CiSpan) { 31 | return ctx, nil 32 | } 33 | 34 | func (*noOpTracer) StartWithTags(ctx context.Context, name string, tags map[string]interface{}) (context.Context, *CiSpan) { 35 | return ctx, nil 36 | } 37 | 38 | func (*noOpTracer) EndSpan(ctx context.Context) { 39 | } 40 | 41 | func NewNoOpTracer() Tracer { 42 | return &noOpTracer{} 43 | } 44 | -------------------------------------------------------------------------------- /internal/cloudinfo/cloudinfodriver/endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfodriver 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "github.com/go-kit/kit/endpoint" 22 | 23 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 24 | ) 25 | 26 | // LogEndpointDefaultName is the default endpoint name to use for logging. 27 | const LogEndpointDefaultName = "gokit/endpoint" 28 | 29 | // LogEndpoint returns an Endpoint middleware, logging a Go kit endpoint. 30 | func LogEndpoint(name string, logger cloudinfo.Logger) endpoint.Middleware { 31 | if name == "" { 32 | name = LogEndpointDefaultName 33 | } 34 | 35 | return func(next endpoint.Endpoint) endpoint.Endpoint { 36 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 37 | logger := logger.WithContext(ctx) 38 | 39 | logger.Debug("processing request", map[string]interface{}{ 40 | "endpoint": name, 41 | }) 42 | 43 | defer func(begin time.Time) { 44 | logger.Debug("processing request finished", map[string]interface{}{ 45 | "endpoint": name, 46 | "took": time.Since(begin), 47 | }) 48 | }(time.Now()) 49 | 50 | response, err = next(ctx, request) 51 | return 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /internal/cloudinfo/cloudinfodriver/provider.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfodriver 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 21 | ) 22 | 23 | const ( 24 | OperationProviderListProviders = "cloudinfo.Provider.ListProviders" 25 | ) 26 | 27 | // ProviderService returns the list of supported providers and relevant information. 28 | type ProviderService interface { 29 | // ListProviders returns a list of providers. 30 | ListProviders(ctx context.Context) ([]cloudinfo.Provider, error) 31 | } 32 | -------------------------------------------------------------------------------- /internal/cloudinfo/cloudinfodriver/provider_endpoints.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfodriver 16 | 17 | import ( 18 | "context" 19 | 20 | "emperror.dev/errors" 21 | "github.com/go-kit/kit/endpoint" 22 | kitoc "github.com/go-kit/kit/tracing/opencensus" 23 | 24 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 25 | ) 26 | 27 | // Endpoints collects all of the endpoints that compose a service service. 28 | // It's meant to be used as a helper struct, to collect all of the endpoints into a 29 | // single parameter. 30 | type ProviderEndpoints struct { 31 | List endpoint.Endpoint 32 | } 33 | 34 | // MakeProviderEndpoints returns an Endpoints struct where each endpoint invokes 35 | // the corresponding method on the provided service. 36 | func MakeProviderEndpoints(s ProviderService, logger cloudinfo.Logger) ProviderEndpoints { 37 | return ProviderEndpoints{ 38 | List: endpoint.Chain( 39 | kitoc.TraceEndpoint(OperationProviderListProviders), 40 | LogEndpoint(OperationProviderListProviders, logger), 41 | )(MakeListProvidersEndpoint(s)), 42 | } 43 | } 44 | 45 | type listProvidersResponse struct { 46 | Providers []cloudinfo.Provider 47 | Err error 48 | } 49 | 50 | func (r listProvidersResponse) Failed() error { 51 | return r.Err 52 | } 53 | 54 | // MakeListProvidersEndpoint returns an endpoint for the matching method of the underlying service. 55 | func MakeListProvidersEndpoint(s ProviderService) endpoint.Endpoint { 56 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 57 | providers, err := s.ListProviders(ctx) 58 | 59 | if err != nil { 60 | if b, ok := errors.Cause(err).(businessError); ok && b.IsBusinessError() { 61 | return listProvidersResponse{ 62 | Err: err, 63 | }, nil 64 | } 65 | 66 | return nil, err 67 | } 68 | 69 | resp := listProvidersResponse{ 70 | Providers: providers, 71 | } 72 | 73 | return resp, nil 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /internal/cloudinfo/cloudinfodriver/region.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfodriver 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 21 | ) 22 | 23 | const ( 24 | OperationRegionListRegions = "cloudinfo.Region.ListRegions" 25 | OperationRegionListZones = "cloudinfo.Region.ListZones" 26 | ) 27 | 28 | // RegionService provides access to regions supported by a service. 29 | type RegionService interface { 30 | // ListRegions returns a list of regions supported by a service. 31 | ListRegions(ctx context.Context, provider string, service string) ([]cloudinfo.Region, error) 32 | 33 | // ListZones returns a list of zones within a region. 34 | ListZones(ctx context.Context, provider string, service string, region string) ([]cloudinfo.Zone, error) 35 | } 36 | -------------------------------------------------------------------------------- /internal/cloudinfo/cloudinfodriver/service.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfodriver 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 21 | ) 22 | 23 | const ( 24 | OperationServiceListServices = "cloudinfo.Service.ListServices" 25 | ) 26 | 27 | // ServiceService returns the list of supported services. 28 | type ServiceService interface { 29 | // ListServices returns a list of services supported by a provider. 30 | ListServices(ctx context.Context, provider string) ([]cloudinfo.Service, error) 31 | } 32 | -------------------------------------------------------------------------------- /internal/cloudinfo/distribution/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package distribution 16 | 17 | type Config struct { 18 | Pke struct { 19 | Amazon struct { 20 | Enabled bool 21 | } 22 | 23 | Azure struct { 24 | Enabled bool 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/cloudinfo/error_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | // ErrorHandler handles an error. 18 | type ErrorHandler interface { 19 | // Handle handles an error. 20 | Handle(err error) 21 | } 22 | -------------------------------------------------------------------------------- /internal/cloudinfo/executor.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "context" 19 | "time" 20 | ) 21 | 22 | // TaskFn function type for executing task logic 23 | type TaskFn func(c context.Context) 24 | 25 | // Executor 26 | type Executor interface { 27 | Execute(ctx context.Context, sf TaskFn) error 28 | } 29 | 30 | // PeriodicExecutor Executor that periodically executes the passed in task function 31 | type PeriodicExecutor struct { 32 | // interval specifies the time interval within the task function will be executed once 33 | interval time.Duration 34 | log Logger 35 | } 36 | 37 | // Execute executes the task function periodically in a new goroutine 38 | // For tasks that need to be periodically executed within a defined deadline, the appropriate context needs to be passed in 39 | func (ps *PeriodicExecutor) Execute(ctx context.Context, sf TaskFn) error { 40 | go sf(ctx) 41 | 42 | ticker := time.NewTicker(ps.interval) 43 | go func(c context.Context) { 44 | for { 45 | select { 46 | case <-ticker.C: 47 | sf(c) 48 | case <-c.Done(): 49 | ps.log.Debug("stopping periodic execution") 50 | ticker.Stop() 51 | return 52 | } 53 | } 54 | }(ctx) 55 | 56 | return nil 57 | } 58 | 59 | // NewPeriodicExecutor creates a new Executor with the given time period 60 | func NewPeriodicExecutor(period time.Duration, log Logger) Executor { 61 | return &PeriodicExecutor{ 62 | interval: period, 63 | log: log, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /internal/cloudinfo/executor_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | "time" 21 | ) 22 | 23 | func TestPeriodicExecutor_Execute(t *testing.T) { 24 | tests := []struct { 25 | name string 26 | period time.Duration 27 | ctx func() (context.Context, context.CancelFunc) 28 | task TaskFn 29 | 30 | checker func(context.Context, context.CancelFunc) 31 | }{ 32 | { 33 | name: "execute periodically till deadline exceeded", 34 | period: 1 * time.Second, 35 | ctx: func() (context.Context, context.CancelFunc) { 36 | return context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) 37 | }, 38 | task: func(c context.Context) { 39 | println("do nothing ...") 40 | c.Done() 41 | }, 42 | 43 | checker: func(c context.Context, cFn context.CancelFunc) { 44 | // wait till done 45 | <-c.Done() 46 | }, 47 | }, 48 | { 49 | name: "execute periodically no deadline", 50 | period: 1 * time.Second, 51 | ctx: func() (context.Context, context.CancelFunc) { 52 | return context.WithCancel(context.Background()) 53 | }, 54 | 55 | task: func(c context.Context) { 56 | println("do nothing ...") 57 | c.Done() 58 | }, 59 | checker: func(c context.Context, cancelFunc context.CancelFunc) { 60 | // cancel after 5 sec 61 | time.Sleep(5 * time.Second) 62 | cancelFunc() 63 | }, 64 | }, 65 | } 66 | for _, test := range tests { 67 | t.Run(test.name, func(t *testing.T) { 68 | ctx, cFn := test.ctx() 69 | if err := NewPeriodicExecutor(test.period, cloudinfoLogger).Execute(ctx, test.task); err != nil { 70 | t.Fail() 71 | } 72 | test.checker(ctx, cFn) 73 | }) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /internal/cloudinfo/instance_type_store.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 19 | ) 20 | 21 | // InMemoryInstanceTypeStore keeps instance types in the memory. 22 | // Use it in tests or for development/demo purposes. 23 | type InMemoryInstanceTypeStore struct { 24 | products map[string]map[string]map[string][]types.ProductDetails 25 | } 26 | 27 | // NewInMemoryInstanceTypeStore returns a new InMemoryInstanceTypeStore. 28 | func NewInMemoryInstanceTypeStore() *InMemoryInstanceTypeStore { 29 | return &InMemoryInstanceTypeStore{ 30 | products: make(map[string]map[string]map[string][]types.ProductDetails), 31 | } 32 | } 33 | 34 | // GetProductDetails retrieves product details from the given provider and region. 35 | func (s *InMemoryInstanceTypeStore) GetProductDetails(provider string, service string, region string) ([]types.ProductDetails, error) { 36 | return s.products[provider][service][region], nil 37 | } 38 | 39 | // GetZones returns all the availability zones for a region. 40 | func (s *InMemoryInstanceTypeStore) GetZones(provider, service, region string) ([]string, error) { 41 | return []string{}, nil 42 | } 43 | -------------------------------------------------------------------------------- /internal/cloudinfo/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "context" 19 | ) 20 | 21 | // Logger is the fundamental interface for all log operations. 22 | type Logger interface { 23 | // Trace logs a debug event. 24 | Trace(msg string, fields ...map[string]interface{}) 25 | 26 | // Debug logs a debug event. 27 | Debug(msg string, fields ...map[string]interface{}) 28 | 29 | // Info logs an info event. 30 | Info(msg string, fields ...map[string]interface{}) 31 | 32 | // Warn logs a warning event. 33 | Warn(msg string, fields ...map[string]interface{}) 34 | 35 | // Error logs an error event. 36 | Error(msg string, fields ...map[string]interface{}) 37 | 38 | // WithFields annotates a logger with key-value pairs. 39 | WithFields(fields map[string]interface{}) Logger 40 | 41 | // WithContext annotates a logger with a context. 42 | WithContext(ctx context.Context) Logger 43 | } 44 | -------------------------------------------------------------------------------- /internal/cloudinfo/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "context" 19 | ) 20 | 21 | // for testing purposess 22 | type noOpLogger struct { 23 | } 24 | 25 | func (d noOpLogger) Trace(msg string, fields ...map[string]interface{}) { 26 | } 27 | 28 | func (d noOpLogger) Debug(msg string, fields ...map[string]interface{}) { 29 | } 30 | 31 | func (d noOpLogger) Info(msg string, fields ...map[string]interface{}) { 32 | } 33 | 34 | func (d noOpLogger) Warn(msg string, fields ...map[string]interface{}) { 35 | } 36 | 37 | func (d noOpLogger) Error(msg string, fields ...map[string]interface{}) { 38 | } 39 | 40 | func (d noOpLogger) WithFields(fields map[string]interface{}) Logger { 41 | return d 42 | } 43 | 44 | func (d noOpLogger) WithContext(ctx context.Context) Logger { 45 | return d 46 | } 47 | 48 | func NoOpLogger() Logger { 49 | return noOpLogger{} 50 | } 51 | -------------------------------------------------------------------------------- /internal/cloudinfo/provider.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "context" 19 | 20 | "emperror.dev/emperror" 21 | 22 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 23 | ) 24 | 25 | // nolint: gochecknoglobals 26 | var providerNames = map[string]string{ 27 | "amazon": "Amazon Web Services", 28 | "google": "Google Cloud", 29 | "alibaba": "Alibaba Cloud", 30 | "oracle": "Oracle", 31 | "azure": "Microsoft Azure", 32 | } 33 | 34 | // ProviderStore retrieves providers. 35 | type ProviderStore interface { 36 | // GetProviders returns the supported providers. 37 | GetProviders() ([]types.Provider, error) 38 | } 39 | 40 | // ProviderService returns the list of supported providers and relevant information. 41 | type ProviderService struct { 42 | store ProviderStore 43 | } 44 | 45 | // NewProviderService returns a new ProviderService. 46 | func NewProviderService(store ProviderStore) *ProviderService { 47 | return &ProviderService{ 48 | store: store, 49 | } 50 | } 51 | 52 | // Provider represents a single cloud provider. 53 | type Provider struct { 54 | Code string 55 | Name string 56 | } 57 | 58 | // ListProviders returns a list of providers. 59 | func (s *ProviderService) ListProviders(ctx context.Context) ([]Provider, error) { 60 | cloudProviders, err := s.store.GetProviders() 61 | if err != nil { 62 | return nil, emperror.Wrap(err, "failed to list providers") 63 | } 64 | 65 | providers := make([]Provider, len(cloudProviders)) 66 | 67 | for i, provider := range cloudProviders { 68 | name, ok := providerNames[provider.Provider] 69 | if !ok { 70 | name = provider.Provider 71 | } 72 | 73 | providers[i] = Provider{ 74 | Code: provider.Provider, 75 | Name: name, 76 | } 77 | } 78 | 79 | return providers, nil 80 | } 81 | -------------------------------------------------------------------------------- /internal/cloudinfo/provider_store.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 19 | ) 20 | 21 | // InMemoryProviderStore keeps providers in the memory. 22 | // Use it in tests or for development/demo purposes. 23 | type InMemoryProviderStore struct { 24 | providers []types.Provider 25 | } 26 | 27 | // NewInMemoryProviderStore returns a new InMemoryProviderStore. 28 | func NewInMemoryProviderStore() *InMemoryProviderStore { 29 | return &InMemoryProviderStore{ 30 | providers: []types.Provider{}, 31 | } 32 | } 33 | 34 | func (s *InMemoryProviderStore) GetProviders() ([]types.Provider, error) { 35 | return s.providers, nil 36 | } 37 | -------------------------------------------------------------------------------- /internal/cloudinfo/provider_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/stretchr/testify/require" 23 | 24 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 25 | ) 26 | 27 | func TestProviderService_ListProviders(t *testing.T) { 28 | store := NewInMemoryProviderStore() 29 | store.providers = []types.Provider{ 30 | { 31 | Provider: "amazon", 32 | }, 33 | { 34 | Provider: "google", 35 | }, 36 | } 37 | 38 | providerService := NewProviderService(store) 39 | 40 | providers, err := providerService.ListProviders(context.Background()) 41 | require.NoError(t, err) 42 | 43 | assert.Equal( 44 | t, 45 | []Provider{ 46 | {Code: "amazon", Name: "Amazon Web Services"}, 47 | {Code: "google", Name: "Google Cloud"}, 48 | }, 49 | providers, 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/alibaba/category_mapper.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package alibaba 16 | 17 | import ( 18 | "strings" 19 | 20 | "emperror.dev/emperror" 21 | "emperror.dev/errors" 22 | 23 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 24 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 25 | ) 26 | 27 | var ( 28 | categoryMap = map[string][]string{ 29 | types.CategoryGeneral: {"g5", "sn2ne", "hfg5", "ebmhfg5", "ebmg5", "ebmg5s", "sccg5", "t5", "xn4", "n4", "mn4", "sn2", "n1", "n2", "s2", "t1", "s1", "s3"}, 30 | types.CategoryCompute: {"ic5", "c5", "sn1ne", "hfc5", "ebmc4", "scch5", "sn1", "c4", "ce4", "cm4", "c1", "c2"}, 31 | types.CategoryMemory: {"r5", "re4", "re4e", "se1ne", "se1", "e4", "e3", "m1", "m2"}, 32 | types.CategoryStorage: {"d1ne", "d1", "i2", "i2g", "i1"}, 33 | types.CategoryGpu: {"gn6v", "gn5", "gn5i", "gn4", "ga1", "f1", "f3", "vgn5i"}, 34 | } 35 | ) 36 | 37 | // mapCategory maps the family of the alibaba instance to category 38 | func (a *AlibabaInfoer) mapCategory(name string) (string, error) { 39 | family := strings.Split(name, ".")[1] 40 | if strings.Contains(family, "-") { 41 | family = strings.Split(family, "-")[0] 42 | } 43 | 44 | for category, strVals := range categoryMap { 45 | if cloudinfo.Contains(strVals, family) { 46 | return category, nil 47 | } 48 | } 49 | return "", emperror.Wrap(errors.New(family), "could not determine the category") 50 | } 51 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/alibaba/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package alibaba 16 | 17 | type Config struct { 18 | Region string 19 | AccessKey string 20 | SecretKey string 21 | } 22 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/alibaba/network_mapper.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package alibaba 16 | 17 | import ( 18 | "emperror.dev/errors" 19 | 20 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 21 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 22 | ) 23 | 24 | var ( 25 | ntwPerfMap = map[string][]string{ 26 | types.NtwLow: {"0.0 Gbit/s", "0.1 Gbit/s", "0.2 Gbit/s", "0.4 Gbit/s", "0.5 Gbit/s", "0.8 Gbit/s", "1.0 Gbit/s", "1.2 Gbit/s", "1.5 Gbit/s", "2.0 Gbit/s"}, 27 | types.NtwMedium: {"2.5 Gbit/s", "3.0 Gbit/s", "4.0 Gbit/s", "4.5 Gbit/s", "5.0 Gbit/s", "6.0 Gbit/s", "7.5 Gbit/s", "8.0 Gbit/s"}, 28 | types.NtwHight: {"10.0 Gbit/s", "12.0 Gbit/s"}, 29 | types.NtwExtra: {"15.0 Gbit/s", "16.0 Gbit/s", "17.0 Gbit/s", "20.0 Gbit/s", "25.0 Gbit/s", "30.0 Gbit/s", "35.0 Gbit/s"}, 30 | } 31 | ) 32 | 33 | // AlibabaNetworkMapper module object for handling Alibaba Cloud specific VM to Networking capabilities mapping 34 | type AlibabaNetworkMapper struct { 35 | } 36 | 37 | // newAlibabaNetworkMapper initializes the network performance mapper struct 38 | func newAlibabaNetworkMapper() *AlibabaNetworkMapper { 39 | return &AlibabaNetworkMapper{} 40 | } 41 | 42 | // MapNetworkPerf maps the network performance of the alibaba instance to the category supported by telescopes 43 | func (nm *AlibabaNetworkMapper) MapNetworkPerf(ntwPerf string) (string, error) { 44 | for perfCat, strVals := range ntwPerfMap { 45 | if cloudinfo.Contains(strVals, ntwPerf) { 46 | return perfCat, nil 47 | } 48 | } 49 | return "", errors.Wrap(errors.New(ntwPerf), "could not determine network performance") 50 | } 51 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/amazon/network_mapper.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package amazon 16 | 17 | import ( 18 | "emperror.dev/errors" 19 | 20 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 21 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 22 | ) 23 | 24 | var ( 25 | networkPerformanceCategories = map[string][]string{ 26 | // available categories 27 | // "Up to 25 Gigabit" 28 | // "50 Gigabit" 29 | // "100 Gigabit" 30 | // "10 Gigabit" 31 | // "20 Gigabit" 32 | // "25 Gigabit" 33 | // "High" 34 | // "Low to Moderate" 35 | // "Low" 36 | // "Moderate" 37 | // "NA" 38 | // "Up to 10 Gigabit" 39 | // "Very Low" 40 | 41 | types.NtwLow: {"Very Low", "Low", "Low to Moderate"}, 42 | types.NtwMedium: {"Moderate", "High"}, 43 | types.NtwHight: {"Up to 10 Gigabit", "10 Gigabit"}, 44 | types.NtwExtra: {"20 Gigabit", "25 Gigabit", "Up to 25 Gigabit", "50 Gigabit", "100 Gigabit"}, 45 | } 46 | ) 47 | 48 | // AmazonNetworkMapper module object for handling amazon specific VM to Networking capabilities mapping 49 | type AmazonNetworkMapper struct { 50 | } 51 | 52 | // newAmazonNetworkMapper initializes the network performance mapper struct 53 | func newAmazonNetworkMapper() AmazonNetworkMapper { 54 | return AmazonNetworkMapper{} 55 | } 56 | 57 | // MapNetworkPerf maps the network performance of the ec2 to the category supported ny telescope 58 | func (nm *AmazonNetworkMapper) MapNetworkPerf(networkPerformance string) (string, error) { 59 | for category, values := range networkPerformanceCategories { 60 | if cloudinfo.Contains(values, networkPerformance) { 61 | return category, nil 62 | } 63 | } 64 | return "", errors.NewWithDetails("could not determine network performance", "networkPerf", networkPerformance) 65 | } 66 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/amazon/network_mapper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package amazon 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | 22 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 23 | ) 24 | 25 | func TestEc2NetworkMapper_MapNetworkPerf(t *testing.T) { 26 | mapper := AmazonNetworkMapper{} 27 | tests := []struct { 28 | name string 29 | vm types.VMInfo 30 | check func(cat string, err error) 31 | }{ 32 | { 33 | name: "success - mapper maps to the lowest category", 34 | vm: types.VMInfo{ 35 | NtwPerf: "Very Low", 36 | }, 37 | check: func(cat string, err error) { 38 | assert.Equal(t, types.NtwLow, cat, "not mapped to the right category") 39 | }, 40 | }, 41 | { 42 | name: "error - mapper doesn't map to a category", 43 | vm: types.VMInfo{ 44 | NtwPerf: "invalid", 45 | }, 46 | check: func(cat string, err error) { 47 | assert.Equal(t, "", cat, "not mapped to the right category") 48 | assert.EqualError(t, err, "could not determine network performance") 49 | }, 50 | }, 51 | } 52 | for _, test := range tests { 53 | t.Run(test.name, func(t *testing.T) { 54 | test.check(mapper.MapNetworkPerf(test.vm.NtwPerf)) 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/amazon/types.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package amazon 16 | 17 | import ( 18 | "emperror.dev/emperror" 19 | "github.com/aws/aws-sdk-go/aws" 20 | "github.com/aws/aws-sdk-go/aws/session" 21 | "github.com/aws/aws-sdk-go/service/pricing" 22 | ) 23 | 24 | // PricingSource list of operations for retrieving pricing information 25 | // Decouples the pricing logic from the amazon api 26 | type PricingSource interface { 27 | GetPriceList(input *pricing.GetProductsInput) ([]aws.JSONValue, error) 28 | } 29 | 30 | // pricingDetails wraps a pricing client, and implements the PricingSource interface 31 | type pricingDetails struct { 32 | // embedded pricing client 33 | pricing.Pricing 34 | } 35 | 36 | func NewPricingSource(s *session.Session, cfg ...*aws.Config) *pricingDetails { 37 | return &pricingDetails{ 38 | *pricing.New(s, cfg...), 39 | } 40 | } 41 | 42 | func (pd *pricingDetails) GetPriceList(input *pricing.GetProductsInput) ([]aws.JSONValue, error) { 43 | list := make([]aws.JSONValue, 0) 44 | 45 | if err := pd.GetProductsPages(input, func(output *pricing.GetProductsOutput, b bool) bool { 46 | list = append(list, output.PriceList...) 47 | return !b 48 | }); err != nil { 49 | return nil, emperror.Wrap(err, "failed to retrieve pricelist") 50 | } 51 | 52 | return list, nil 53 | } 54 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/azure/category_mapper.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package azure 16 | 17 | import ( 18 | "strings" 19 | 20 | "emperror.dev/emperror" 21 | "emperror.dev/errors" 22 | 23 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 24 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 25 | ) 26 | 27 | var ( 28 | categoryMap = map[string][]string{ 29 | types.CategoryGeneral: {"Dv2", "Av2", "Dv3", "DSv2", "DSv3", "BS", "DS", "D", "A0_A7", "A", "A8_A11", "DCS"}, 30 | types.CategoryCompute: {"H", "FSv2", "FS", "", "HCS", "HBS"}, 31 | types.CategoryMemory: {"Ev3", "ESv3", "MS", "G", "GS", "EIv3", "EISv3", "PBS", "MSv2"}, 32 | types.CategoryStorage: {"LS", "LSv2"}, 33 | types.CategoryGpu: {"NC", "NV", "NCSv3", "NCSv2", "NDS", "NVSv2", "NVSv3"}, 34 | } 35 | ) 36 | 37 | // mapCategory maps the family of the azure instance to category 38 | func (a *AzureInfoer) mapCategory(name string) (string, error) { 39 | family := strings.TrimRight(name, "Family") 40 | family = strings.TrimLeft(family, "standard") // nolint: staticcheck 41 | family = strings.TrimRight(family, "Promo") // nolint: staticcheck 42 | family = strings.TrimLeft(family, "basic") 43 | 44 | for category, strVals := range categoryMap { 45 | if cloudinfo.Contains(strVals, family) { 46 | return category, nil 47 | } 48 | } 49 | return "", emperror.Wrap(errors.New(family), "could not determine the category") 50 | } 51 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/azure/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package azure 16 | 17 | type Config struct { 18 | SubscriptionID string 19 | 20 | ClientID string 21 | ClientSecret string 22 | TenantID string 23 | } 24 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/digitalocean/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package digitalocean 16 | 17 | type Config struct { 18 | AccessToken string 19 | } 20 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/google/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package google 16 | 17 | type Config struct { 18 | Credentials string 19 | CredentialsFile string 20 | 21 | Project string 22 | } 23 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/google/network_mapper.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package google 16 | 17 | import ( 18 | "emperror.dev/errors" 19 | 20 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 21 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 22 | ) 23 | 24 | var ( 25 | ntwPerfMap = map[string][]string{ 26 | types.NtwLow: {"1 Gbit/s", "2 Gbit/s"}, 27 | types.NtwMedium: {"4 Gbit/s", "6 Gbit/s", "8 Gbit/s"}, 28 | types.NtwHight: {"10 Gbit/s", "12 Gbit/s", "14 Gbit/s"}, 29 | types.NtwExtra: {"16 Gbit/s"}, 30 | } 31 | ) 32 | 33 | // GceNetworkMapper module object for handling Google Cloud specific VM to Networking capabilities mapping 34 | type GceNetworkMapper struct { 35 | } 36 | 37 | // newGceNetworkMapper initializes the network performance mapper struct 38 | func newGceNetworkMapper() *GceNetworkMapper { 39 | return &GceNetworkMapper{} 40 | } 41 | 42 | // MapNetworkPerf maps the network performance of the google instance to the category supported by telescopes 43 | func (nm *GceNetworkMapper) MapNetworkPerf(ntwPerf string) (string, error) { 44 | for perfCat, strVals := range ntwPerfMap { 45 | if cloudinfo.Contains(strVals, ntwPerf) { 46 | return perfCat, nil 47 | } 48 | } 49 | return "", errors.Wrap(errors.New(ntwPerf), "could not determine network performance") 50 | } 51 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/oracle/client/ce.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/oracle/oci-go-sdk/containerengine" 21 | ) 22 | 23 | // ContainerEngine is for managing OKE related calls of OCI 24 | type ContainerEngine struct { 25 | CompartmentOCID string 26 | 27 | oci *OCI 28 | client *containerengine.ContainerEngineClient 29 | } 30 | 31 | // NewContainerEngineClient creates a new ContainerEngine 32 | func (oci *OCI) NewContainerEngineClient() (client *ContainerEngine, err error) { 33 | 34 | client = &ContainerEngine{} 35 | 36 | oClient, err := containerengine.NewContainerEngineClientWithConfigurationProvider(oci.config) 37 | if err != nil { 38 | return client, err 39 | } 40 | 41 | client.client = &oClient 42 | client.oci = oci 43 | client.CompartmentOCID = *oci.Tenancy.Id 44 | 45 | return client, nil 46 | } 47 | 48 | // GetDefaultNodePoolOptions gets default node pool options 49 | func (ce *ContainerEngine) GetDefaultNodePoolOptions() (options NodePoolOptions, err error) { 50 | 51 | return ce.GetNodePoolOptions("all") 52 | } 53 | 54 | // GetNodePoolOptions gets available node pool options for a specified cluster OCID 55 | func (ce *ContainerEngine) GetNodePoolOptions(clusterID string) (options NodePoolOptions, err error) { 56 | 57 | request := containerengine.GetNodePoolOptionsRequest{ 58 | NodePoolOptionId: &clusterID, 59 | } 60 | 61 | r, err := ce.client.GetNodePoolOptions(context.Background(), request) 62 | 63 | return NodePoolOptions{ 64 | Images: Strings{strings: r.Images}, 65 | KubernetesVersions: Strings{strings: r.KubernetesVersions}, 66 | Shapes: Strings{strings: r.Shapes}, 67 | }, err 68 | } 69 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/oracle/client/helper.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | // Strings holds strings in an array 18 | type Strings struct { 19 | strings []string 20 | } 21 | 22 | // NodePoolOptions holds node pool options as Strings 23 | type NodePoolOptions struct { 24 | Images Strings 25 | KubernetesVersions Strings 26 | Shapes Strings 27 | } 28 | 29 | // Has checks if the strings array has a value 30 | func (s Strings) Has(value string) bool { 31 | 32 | for _, v := range s.strings { 33 | if v == value { 34 | return true 35 | } 36 | } 37 | 38 | return false 39 | } 40 | 41 | // Get gets the raw array 42 | func (s Strings) Get() []string { 43 | 44 | return s.strings 45 | } 46 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/oracle/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package oracle 16 | 17 | type Config struct { 18 | Tenancy string 19 | User string 20 | Region string 21 | Fingerprint string 22 | PrivateKey string 23 | PrivateKeyPassphrase *string 24 | 25 | ConfigFilePath string 26 | Profile string 27 | } 28 | -------------------------------------------------------------------------------- /internal/cloudinfo/providers/oracle/network_mapper.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package oracle 16 | 17 | import ( 18 | "emperror.dev/errors" 19 | 20 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo" 21 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 22 | ) 23 | 24 | var ( 25 | ntwPerfMap = map[string][]string{ 26 | types.NtwLow: {"0.6 Gbps", "0.7 Gbps"}, 27 | types.NtwMedium: {"1 Gbps", "1.2 Gbps", "1.4 Gbps", "2 Gbps", "2.4 Gbps"}, 28 | types.NtwHight: {"4.1 Gbps", "4.8 Gbps", "8.2 Gbps"}, 29 | types.NtwExtra: {"16.4 Gbps", "24.6 Gbps"}, 30 | } 31 | ) 32 | 33 | // OCINetworkMapper module object for handling Oracle specific VM to Networking capabilities mapping 34 | type OCINetworkMapper struct { 35 | } 36 | 37 | // newNetworkMapper initializes the network performance mapper struct 38 | func newNetworkMapper() *OCINetworkMapper { 39 | return &OCINetworkMapper{} 40 | } 41 | 42 | // MapNetworkPerf maps the network performance of the instance to the category supported by telescopes 43 | func (nm *OCINetworkMapper) MapNetworkPerf(ntwPerf string) (string, error) { 44 | for perfCat, strVals := range ntwPerfMap { 45 | if cloudinfo.Contains(strVals, ntwPerf) { 46 | return perfCat, nil 47 | } 48 | } 49 | return "", errors.Wrap(errors.New(ntwPerf), "could not determine network performance") 50 | } 51 | -------------------------------------------------------------------------------- /internal/cloudinfo/region_store.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | // InMemoryRegionStore keeps regions in the memory. 18 | // Use it in tests or for development/demo purposes. 19 | type InMemoryRegionStore struct { 20 | regions map[string]map[string]map[string]string 21 | zones map[string]map[string]map[string][]string 22 | } 23 | 24 | // NewInMemoryRegionStore returns a new InMemoryRegionStore. 25 | func NewInMemoryRegionStore() *InMemoryRegionStore { 26 | return &InMemoryRegionStore{ 27 | regions: make(map[string]map[string]map[string]string), 28 | zones: make(map[string]map[string]map[string][]string), 29 | } 30 | } 31 | 32 | func (s *InMemoryRegionStore) GetRegions(provider string, service string) (map[string]string, error) { 33 | return s.regions[provider][service], nil 34 | } 35 | 36 | func (s *InMemoryRegionStore) GetZones(provider string, service string, region string) ([]string, error) { 37 | return s.zones[provider][service][region], nil 38 | } 39 | -------------------------------------------------------------------------------- /internal/cloudinfo/region_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | func TestRegionService_ListRegions(t *testing.T) { 26 | store := NewInMemoryRegionStore() 27 | store.regions = map[string]map[string]map[string]string{ 28 | "amazon": { 29 | "compute": { 30 | "eu-west-1": "EU (Ireland)", 31 | }, 32 | }, 33 | } 34 | 35 | serviceService := NewRegionService(store) 36 | 37 | regions, err := serviceService.ListRegions(context.Background(), "amazon", "compute") 38 | require.NoError(t, err) 39 | 40 | assert.Equal( 41 | t, 42 | []Region{ 43 | {Code: "eu-west-1", Name: "EU (Ireland)", providerName: "amazon", serviceName: "compute"}, 44 | }, 45 | regions, 46 | ) 47 | } 48 | 49 | func TestRegionService_ListZones(t *testing.T) { 50 | store := NewInMemoryRegionStore() 51 | store.zones = map[string]map[string]map[string][]string{ 52 | "amazon": { 53 | "compute": { 54 | "eu-west-1": []string{"eu-west-1a", "eu-west-1b", "eu-west-1c"}, 55 | }, 56 | }, 57 | } 58 | 59 | serviceService := NewRegionService(store) 60 | 61 | zones, err := serviceService.ListZones(context.Background(), "amazon", "compute", "eu-west-1") 62 | require.NoError(t, err) 63 | 64 | assert.Equal( 65 | t, 66 | []Zone{ 67 | {Code: "eu-west-1a"}, 68 | {Code: "eu-west-1b"}, 69 | {Code: "eu-west-1c"}, 70 | }, 71 | zones, 72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /internal/cloudinfo/service.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "context" 19 | 20 | "emperror.dev/emperror" 21 | 22 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 23 | ) 24 | 25 | // ServiceStore retrieves services. 26 | type ServiceStore interface { 27 | // GetServices returns the supported services for a provider. 28 | GetServices(provider string) ([]types.Service, error) 29 | } 30 | 31 | // ServiceService returns the list of supported services. 32 | type ServiceService struct { 33 | store ServiceStore 34 | } 35 | 36 | // NewServiceService returns a new ServiceService. 37 | func NewServiceService(store ServiceStore) *ServiceService { 38 | return &ServiceService{ 39 | store: store, 40 | } 41 | } 42 | 43 | // Service represents a single service. 44 | type Service struct { 45 | Code string 46 | 47 | providerName string 48 | } 49 | 50 | // ProviderName returns the provider name of the service. 51 | // Used when resolving regions based on a service. 52 | func (s *Service) ProviderName() string { 53 | return s.providerName 54 | } 55 | 56 | // ListServices returns a list of services supported by a provider. 57 | func (s *ServiceService) ListServices(ctx context.Context, provider string) ([]Service, error) { 58 | cloudServices, err := s.store.GetServices(provider) 59 | if err != nil { 60 | return nil, emperror.Wrap(err, "failed to list services") 61 | } 62 | 63 | services := make([]Service, len(cloudServices)) 64 | 65 | for i, service := range cloudServices { 66 | services[i] = Service{ 67 | Code: service.Service, 68 | providerName: provider, 69 | } 70 | } 71 | 72 | return services, nil 73 | } 74 | -------------------------------------------------------------------------------- /internal/cloudinfo/service_store.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 19 | ) 20 | 21 | // InMemoryServiceStore keeps services in the memory. 22 | // Use it in tests or for development/demo purposes. 23 | type InMemoryServiceStore struct { 24 | services map[string][]types.Service 25 | } 26 | 27 | // NewInMemoryServiceStore returns a new InMemoryServiceStore. 28 | func NewInMemoryServiceStore() *InMemoryServiceStore { 29 | return &InMemoryServiceStore{ 30 | services: make(map[string][]types.Service), 31 | } 32 | } 33 | 34 | func (s *InMemoryServiceStore) GetServices(provider string) ([]types.Service, error) { 35 | return s.services[provider], nil 36 | } 37 | -------------------------------------------------------------------------------- /internal/cloudinfo/service_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudinfo 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/stretchr/testify/require" 23 | 24 | "github.com/banzaicloud/cloudinfo/internal/cloudinfo/types" 25 | ) 26 | 27 | func TestServiceService_ListServices(t *testing.T) { 28 | store := NewInMemoryServiceStore() 29 | store.services = map[string][]types.Service{ 30 | "amazon": { 31 | { 32 | Service: "compute", 33 | }, 34 | { 35 | Service: "eks", 36 | }, 37 | }, 38 | "google": { 39 | { 40 | Service: "compute", 41 | }, 42 | { 43 | Service: "gke", 44 | }, 45 | }, 46 | } 47 | 48 | serviceService := NewServiceService(store) 49 | 50 | services, err := serviceService.ListServices(context.Background(), "amazon") 51 | require.NoError(t, err) 52 | 53 | assert.Equal( 54 | t, 55 | []Service{ 56 | {Code: "compute", providerName: "amazon"}, {Code: "eks", providerName: "amazon"}, 57 | }, 58 | services, 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /internal/platform/buildinfo/buildinfo.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package buildinfo 16 | 17 | import ( 18 | "runtime" 19 | ) 20 | 21 | // BuildInfo represents all available build information. 22 | type BuildInfo struct { 23 | Version string `json:"version"` 24 | CommitHash string `json:"commit_hash"` 25 | BuildDate string `json:"build_date"` 26 | GoVersion string `json:"go_version"` 27 | Os string `json:"os"` 28 | Arch string `json:"arch"` 29 | Compiler string `json:"compiler"` 30 | } 31 | 32 | // New returns all available build information. 33 | func New(version string, commitHash string, buildDate string) BuildInfo { 34 | return BuildInfo{ 35 | Version: version, 36 | CommitHash: commitHash, 37 | BuildDate: buildDate, 38 | GoVersion: runtime.Version(), 39 | Os: runtime.GOOS, 40 | Arch: runtime.GOARCH, 41 | Compiler: runtime.Compiler, 42 | } 43 | } 44 | 45 | // Fields returns the build information in a log context format. 46 | func (bi BuildInfo) Fields() map[string]interface{} { 47 | return map[string]interface{}{ 48 | "version": bi.Version, 49 | "commit_hash": bi.CommitHash, 50 | "build_date": bi.BuildDate, 51 | "go_version": bi.GoVersion, 52 | "os": bi.Os, 53 | "arch": bi.Arch, 54 | "compiler": bi.Compiler, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/platform/cassandra/cassandra.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cassandra 16 | 17 | import "github.com/gocql/gocql" 18 | 19 | func NewCluster(config Config) *gocql.ClusterConfig { 20 | return gocql.NewCluster(config.Hosts...) 21 | } 22 | -------------------------------------------------------------------------------- /internal/platform/cassandra/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cassandra 16 | 17 | // Cassandra related configuration collector 18 | type Config struct { 19 | Enabled bool 20 | Hosts []string 21 | Port int 22 | Keyspace string 23 | Table string 24 | } 25 | -------------------------------------------------------------------------------- /internal/platform/errorhandler/error_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package errorhandler 16 | 17 | import ( 18 | "emperror.dev/emperror" 19 | logurhandler "emperror.dev/handler/logur" 20 | "logur.dev/logur" 21 | ) 22 | 23 | // New returns a new error handler. 24 | func New(logger logur.Logger) emperror.ErrorHandler { 25 | return logurhandler.New(logger) 26 | } 27 | -------------------------------------------------------------------------------- /internal/platform/errorhandler/panic_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package errorhandler 16 | 17 | import ( 18 | "emperror.dev/emperror" 19 | ) 20 | 21 | type panicHandler struct{} 22 | 23 | // NewPanicHandler returns an error handler that panics. 24 | func NewPanicHandler() emperror.ErrorHandler { 25 | return &panicHandler{} 26 | } 27 | 28 | func (*panicHandler) Handle(err error) { 29 | emperror.Panic(err) 30 | } 31 | -------------------------------------------------------------------------------- /internal/platform/jaeger/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package jaeger 16 | 17 | import "emperror.dev/errors" 18 | 19 | // Config holds information necessary for sending trace to Jaeger. 20 | type Config struct { 21 | // CollectorEndpoint is the Jaeger HTTP Thrift endpoint. 22 | // For example, http://localhost:14268. 23 | CollectorEndpoint string 24 | 25 | // AgentEndpoint instructs exporter to send spans to Jaeger agent at this address. 26 | // For example, http://localhost:14268/api/traces?format=jaeger.thrift. 27 | AgentEndpoint string 28 | 29 | // Username to be used if basic auth is required. 30 | // Optional. 31 | Username string 32 | 33 | // Password to be used if basic auth is required. 34 | // Optional. 35 | Password string 36 | 37 | // ServiceName is the name of the process. 38 | ServiceName string 39 | } 40 | 41 | // Validate checks that the configuration is valid. 42 | func (c Config) Validate() error { 43 | if c.CollectorEndpoint == "" && c.AgentEndpoint == "" { 44 | return errors.New("either collector endpoint or agent endpoint must be configured") 45 | } 46 | 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /internal/platform/jaeger/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package jaeger 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestConfig_Validate(t *testing.T) { 24 | tests := map[string]Config{ 25 | "either collector endpoint or agent endpoint must be configured": {}, 26 | } 27 | 28 | for name, test := range tests { 29 | name, test := name, test 30 | 31 | t.Run(name, func(t *testing.T) { 32 | err := test.Validate() 33 | 34 | assert.EqualError(t, err, name) 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /internal/platform/jaeger/jaeger.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package jaeger 16 | 17 | import ( 18 | "contrib.go.opencensus.io/exporter/jaeger" 19 | "emperror.dev/emperror" 20 | "emperror.dev/errors" 21 | ) 22 | 23 | // NewExporter creates a new, configured Jaeger exporter. 24 | func NewExporter(config Config, errorHandler emperror.ErrorHandler) (*jaeger.Exporter, error) { 25 | exporter, err := jaeger.NewExporter(jaeger.Options{ 26 | CollectorEndpoint: config.CollectorEndpoint, 27 | AgentEndpoint: config.AgentEndpoint, 28 | Username: config.Username, 29 | Password: config.Password, 30 | OnError: emperror.HandlerWith( 31 | errorHandler, 32 | "component", "opencensus", 33 | "exporter", "jaeger", 34 | ).Handle, 35 | Process: jaeger.Process{ 36 | ServiceName: config.ServiceName, 37 | }, 38 | }) 39 | 40 | return exporter, errors.Wrap(err, "failed to create jaeger exporter") 41 | } 42 | -------------------------------------------------------------------------------- /internal/platform/log/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package log 16 | 17 | // Config holds details necessary for logging. 18 | type Config struct { 19 | // Format specifies the output log format. 20 | // Accepted values are: json, logfmt 21 | Format string 22 | 23 | // Level is the minimum log level that should appear on the output. 24 | Level string 25 | 26 | // NoColor makes sure that no log output gets colorized. 27 | NoColor bool 28 | } 29 | -------------------------------------------------------------------------------- /internal/platform/log/standard_logger.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package log 16 | 17 | import ( 18 | "log" 19 | 20 | "logur.dev/logur" 21 | ) 22 | 23 | // NewErrorStandardLogger returns a new standard logger logging on error level. 24 | func NewErrorStandardLogger(logger logur.Logger) *log.Logger { 25 | return logur.NewErrorStandardLogger(logger, "", 0) 26 | } 27 | 28 | // SetStandardLogger sets the global logger's output to a custom logger instance. 29 | func SetStandardLogger(logger logur.Logger) { 30 | log.SetOutput(logur.NewLevelWriter(logger, logur.Info)) 31 | } 32 | -------------------------------------------------------------------------------- /internal/platform/prometheus/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package prometheus 16 | 17 | // Config holds information for configuring the Prometheus exporter 18 | type Config struct { 19 | Namespace string 20 | } 21 | 22 | // Validate checks that the configuration is valid. 23 | func (c Config) Validate() error { 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /internal/platform/prometheus/prometheus.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package prometheus 15 | 16 | import ( 17 | "contrib.go.opencensus.io/exporter/prometheus" 18 | "emperror.dev/emperror" 19 | "emperror.dev/errors" 20 | ) 21 | 22 | // NewExporter creates a new, configured Prometheus exporter. 23 | func NewExporter(config Config, errorHandler emperror.ErrorHandler) (*prometheus.Exporter, error) { 24 | exporter, err := prometheus.NewExporter(prometheus.Options{ 25 | Namespace: config.Namespace, 26 | OnError: errorHandler.Handle, 27 | }) 28 | 29 | return exporter, errors.Wrap(err, "failed to create prometheus exporter") 30 | } 31 | -------------------------------------------------------------------------------- /internal/platform/redis/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "fmt" 19 | 20 | "emperror.dev/errors" 21 | ) 22 | 23 | // Config holds information necessary for connecting to Redis. 24 | type Config struct { 25 | // Host is the Redis host. 26 | Host string 27 | 28 | // Port is the Redis port. 29 | Port int 30 | 31 | // Password list supports passing multiple passwords making password changes easier 32 | Password []string 33 | 34 | Enabled bool 35 | } 36 | 37 | // Validate checks that the configuration is valid. 38 | func (c Config) Validate() error { 39 | if !c.Enabled { 40 | return nil 41 | } 42 | if c.Host == "" { 43 | return errors.New("redis host is required") 44 | } 45 | 46 | if c.Port == 0 { 47 | return errors.New("redis port is required") 48 | } 49 | 50 | return nil 51 | } 52 | 53 | // Server returns the host-port pair for the connection. 54 | func (c Config) Server() string { 55 | return fmt.Sprintf("%s:%d", c.Host, c.Port) 56 | } 57 | -------------------------------------------------------------------------------- /internal/platform/redis/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestConfig_Validate(t *testing.T) { 24 | tests := map[string]Config{ 25 | "redis host is required": { 26 | Enabled: true, 27 | Port: 6379, 28 | }, 29 | "redis port is required": { 30 | Enabled: true, 31 | Host: "127.0.0.1", 32 | }, 33 | } 34 | 35 | for name, test := range tests { 36 | name, test := name, test 37 | 38 | t.Run(name, func(t *testing.T) { 39 | err := test.Validate() 40 | 41 | assert.EqualError(t, err, name) 42 | }) 43 | } 44 | } 45 | 46 | func TestConfig_Server(t *testing.T) { 47 | config := Config{ 48 | Enabled: true, 49 | Host: "127.0.0.1", 50 | Port: 6379, 51 | } 52 | 53 | server := config.Server() 54 | 55 | assert.Equal(t, "127.0.0.1:6379", server) 56 | } 57 | -------------------------------------------------------------------------------- /internal/platform/redis/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "time" 19 | 20 | "emperror.dev/errors" 21 | "github.com/gomodule/redigo/redis" 22 | ) 23 | 24 | type PoolWrapper struct { 25 | *redis.Pool 26 | } 27 | 28 | // NewPool creates a new redis connection pool. 29 | func NewPool(config Config) *redis.Pool { 30 | return &redis.Pool{ 31 | MaxIdle: 10, 32 | Wait: true, // Wait for the connection pool, no connection pool exhausted error 33 | Dial: func() (redis.Conn, error) { 34 | c, err := redis.Dial( 35 | "tcp", 36 | config.Server(), 37 | ) 38 | if err != nil { 39 | return nil, errors.Wrap(err, "failed to dial redis server") 40 | } 41 | 42 | if len(config.Password) > 0 { 43 | var err error 44 | 45 | for _, password := range config.Password { 46 | _, err = c.Do("AUTH", password) 47 | if err == nil { 48 | break 49 | } 50 | } 51 | 52 | if err != nil { 53 | c.Close() 54 | 55 | return nil, errors.Wrap(err, "none of the provided passwords were accepted by the server") 56 | } 57 | } 58 | 59 | return c, nil 60 | }, 61 | TestOnBorrow: func(c redis.Conn, t time.Time) error { 62 | _, err := c.Do("PING") 63 | 64 | return err 65 | }, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /scripts/check-header.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | read -r -d '' EXPECTED < 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # Web 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.7. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /web/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /web/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to web!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /web/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /web/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng serve", 7 | "start-with-proxy": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng serve --configuration=proxy --proxy-config=proxy.conf.json", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "build-prod": "ng build --configuration=production --base-href=/", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "^12.2.5", 17 | "@angular/cdk": "^12.2.5", 18 | "@angular/common": "~12.2.5", 19 | "@angular/compiler": "~12.2.5", 20 | "@angular/core": "~12.2.5", 21 | "@angular/forms": "~12.2.5", 22 | "@angular/material": "^12.2.5", 23 | "@angular/platform-browser": "~12.2.5", 24 | "@angular/platform-browser-dynamic": "~12.2.5", 25 | "@angular/router": "~12.2.5", 26 | "ngx-clipboard": "^14.0.1", 27 | "rxjs": "~6.6.7", 28 | "time-ago-pipe": "^1.3.2", 29 | "tslib": "^2.3.1", 30 | "zone.js": "~0.11.4" 31 | }, 32 | "devDependencies": { 33 | "@angular-devkit/build-angular": "~12.2.5", 34 | "@angular/cli": "~12.2.5", 35 | "@angular/compiler-cli": "~12.2.5", 36 | "@types/jasmine": "~3.8.2", 37 | "@types/node": "^16.9.1", 38 | "codelyzer": "^6.0.2", 39 | "jasmine-core": "~3.8.0", 40 | "jasmine-spec-reporter": "~7.0.0", 41 | "karma": "~6.3.4", 42 | "karma-chrome-launcher": "~3.1.0", 43 | "karma-coverage": "~2.0.3", 44 | "karma-jasmine": "~4.0.0", 45 | "karma-jasmine-html-reporter": "^1.7.0", 46 | "protractor": "~7.0.0", 47 | "ts-node": "~10.2.1", 48 | "tslint": "~6.1.0", 49 | "typescript": "~4.2.4" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /web/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/cloudinfo": { 3 | "target": "https://alpha.dev.banzaicloud.com/cloudinfo", 4 | "secure": false, 5 | "changeOrigin": true, 6 | "pathRewrite": { 7 | "^/cloudinfo": "" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /web/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { NotFoundComponent } from './modules/core/components/not-found/not-found.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | loadChildren: () => import('./modules/home/home.module').then(m => m.HomeModule), 9 | }, 10 | { path: '**', component: NotFoundComponent }, 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })], 15 | exports: [RouterModule], 16 | }) 17 | export class AppRoutingModule { 18 | } 19 | -------------------------------------------------------------------------------- /web/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banzaicloud/cloudinfo/22150862eee0b6b9a21230a9074fc7c38afe68a9/web/src/app/app.component.scss -------------------------------------------------------------------------------- /web/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(waitForAsync(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'web'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('web'); 27 | }); 28 | 29 | it('should render title in a h1 tag', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to web!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /web/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'Banzai Cloud Telescopes - Machine Type information'; 10 | } 11 | -------------------------------------------------------------------------------- /web/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { CoreModule } from './modules/core/core.module'; 7 | import { HttpClientModule } from '@angular/common/http'; 8 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | AppComponent, 13 | ], 14 | imports: [ 15 | BrowserModule, 16 | BrowserAnimationsModule, 17 | AppRoutingModule, 18 | CoreModule, 19 | HttpClientModule, 20 | ], 21 | providers: [], 22 | bootstrap: [AppComponent], 23 | }) 24 | export class AppModule { 25 | } 26 | -------------------------------------------------------------------------------- /web/src/app/constants/providers.ts: -------------------------------------------------------------------------------- 1 | export const PROVIDERS = { 2 | amazon: { provider: 'amazon', name: 'Amazon' }, 3 | alibaba: { provider: 'alibaba', name: 'Alibaba Cloud' }, 4 | google: { provider: 'google', name: 'Google Cloud' }, 5 | azure: { provider: 'azure', name: 'Microsoft Azure' }, 6 | oracle: { provider: 'oracle', name: 'Oracle' }, 7 | digitalocean: { provider: 'digitalocean', name: 'DigitalOcean' }, 8 | }; 9 | -------------------------------------------------------------------------------- /web/src/app/models/product.ts: -------------------------------------------------------------------------------- 1 | export class Products { 2 | products: Product[]; 3 | scrapingTime: string; 4 | } 5 | 6 | export class Product { 7 | category: string; 8 | type: string; 9 | cpusPerVm: number; 10 | memPerVm: number; 11 | onDemandPrice: number; 12 | spotPrice: SpotPrice[]; 13 | ntwPerf: string; 14 | } 15 | 16 | export class SpotPrice { 17 | zone: string; 18 | price: number; 19 | } 20 | 21 | export interface DisplayedProduct { 22 | category: string; 23 | type: string; 24 | cpu: number; 25 | mem: number; 26 | regularPrice: number; 27 | spotPrice: number | string; 28 | ntwPerf: string; 29 | } 30 | -------------------------------------------------------------------------------- /web/src/app/models/provider.ts: -------------------------------------------------------------------------------- 1 | export interface Provider { 2 | provider: string; 3 | name?: string; 4 | services: Array<{ service: string }>; 5 | } 6 | -------------------------------------------------------------------------------- /web/src/app/models/region.ts: -------------------------------------------------------------------------------- 1 | export class Region { 2 | id: string; 3 | name: string; 4 | } 5 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-copy-icon/banzai-copy-icon.component.html: -------------------------------------------------------------------------------- 1 | copy 17 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-copy-icon/banzai-copy-icon.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../../theme/tooltip"; 2 | 3 | .copy { 4 | position: relative; 5 | cursor: pointer; 6 | 7 | -webkit-transition-duration: 0.4s; /* Safari */ 8 | transition-duration: 0.4s; 9 | } 10 | 11 | .disabled { 12 | @extend .copy; 13 | cursor: no-drop; 14 | } 15 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-copy-icon/banzai-copy-icon.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-banzai-copy-icon', 5 | templateUrl: './banzai-copy-icon.component.html', 6 | styleUrls: ['./banzai-copy-icon.component.scss'], 7 | }) 8 | export class BanzaiCopyIconComponent implements OnInit { 9 | 10 | @Input() value: string; 11 | @Input() disabled: boolean; 12 | @Input() size = 24; 13 | @Input() paddingBottom = 0; 14 | 15 | constructor() { } 16 | 17 | ngOnInit() { 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-selector/banzai-selector.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 | 7 | 8 |
9 |
{{ selectorLabel }}
10 |
11 | 14 | 18 | 23 | {{ item.label }} 24 | 25 | 26 | 27 | {{ selectedValue.display }} 28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 |
36 |
{{ selectorLabel }}
37 |
38 | 41 |
42 | 47 | {{ item.label }} 48 | 49 |
50 |
51 |
52 |
53 |
54 | 55 | 56 |
57 | 60 |
61 |
62 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-selector/banzai-selector.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../../theme/animations"; 2 | 3 | .loader-wrapper { 4 | display: flex; 5 | min-width: 260px; 6 | min-height: 66px; 7 | 8 | mat-spinner { 9 | margin-top: auto; 10 | margin-bottom: auto; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-selector/model/selector-item.ts: -------------------------------------------------------------------------------- 1 | export interface SelectorItem { 2 | label: string; // label in select option(s) 3 | display: string; // select title after selection 4 | value: string; // select option content 5 | id: number; // this is necessary, multiple pke values 6 | } 7 | 8 | export interface SelectorGroup { 9 | label?: string; 10 | value?: string; 11 | items: SelectorItem[]; 12 | } 13 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-table/animation/expand-and-collapse.animation.ts: -------------------------------------------------------------------------------- 1 | import { animate, state, style, transition, trigger } from '@angular/animations'; 2 | 3 | export const expandAndCollapseAnimation = 4 | trigger('expandAndCollapseAnimation', [ 5 | state('collapsed, void', style({ height: '0px', minHeight: '0', opacity: 0, display: 'none' })), 6 | state('expanded', style({ height: '*', opacity: 1 })), 7 | transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), 8 | transition('expanded <=> void', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), 9 | ]); 10 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-table/banzai-table.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../../theme/banzai-table"; 2 | @import "../../../../../theme/tooltip"; 3 | @import "../../../../../theme/animations"; 4 | 5 | // Cell borders 6 | 7 | .table-cell-left-border { 8 | padding-left: $table-cell-padding-to-border; 9 | border-left: $table-cell-border; 10 | } 11 | 12 | .table-cell-right-border { 13 | padding-right: $table-cell-padding-to-border; 14 | border-right: $table-cell-border; 15 | } 16 | 17 | .table-cell-edge-border { 18 | padding-right: $table-cell-padding-to-border; 19 | padding-left: $table-cell-padding-to-border; 20 | border-right: $table-cell-border; 21 | border-left: $table-cell-border; 22 | } 23 | 24 | // -------- 25 | 26 | .table-cell-padding-left { 27 | padding-left: $table-cell-padding-to-border; 28 | } 29 | 30 | .table-cell-padding-left-null { 31 | padding-left: 0 !important; 32 | } 33 | 34 | .table-cell-padding-right { 35 | padding-right: $table-cell-padding-to-border; 36 | } 37 | 38 | .table-cell-padding-edges { 39 | padding-left: $table-cell-padding-to-border; 40 | padding-right: $table-cell-padding-to-border; 41 | } 42 | 43 | // More cell 44 | .mat-table-last-row-with-action { 45 | display: flex; 46 | flex-direction: row; 47 | align-items: center; 48 | height: $table-cell-height; 49 | width: $table-action-cell-width; 50 | 51 | .mat-icon-button { 52 | margin-left: auto; 53 | margin-right: auto; 54 | } 55 | 56 | } 57 | 58 | .mat-table-last-row-with-action-small { 59 | @extend .mat-table-last-row-with-action; 60 | width: $table-action-cell-width-small; 61 | } 62 | 63 | // -------- 64 | 65 | .no-result { 66 | display: flex; 67 | flex-direction: row; 68 | align-items: center; 69 | width: 100%; 70 | 71 | div { 72 | margin: auto; 73 | font-weight: bold; 74 | font-style: normal; 75 | font-stretch: normal; 76 | line-height: 1.33; 77 | letter-spacing: normal; 78 | text-align: left; 79 | color: #7f7f7f; 80 | } 81 | } 82 | 83 | .sub-header { 84 | font-size: 10px; 85 | margin-left: 2px; 86 | } 87 | 88 | .cell-text-overflow { 89 | white-space: nowrap; 90 | overflow: hidden; 91 | text-overflow: ellipsis; 92 | padding-right: 16px; 93 | } 94 | 95 | .table-cell-position-relative { 96 | position: relative; 97 | } 98 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-table/directives/banzai-table-border-details-status.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, HostBinding, Input } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[appBorderDetailsStatus]', 5 | }) 6 | export class BanzaiTableBorderDetailsStatusDirective { 7 | 8 | private _isOpen: boolean; 9 | private lastClass: string; // this is necessary because of updating 10 | 11 | @Input() set isOpen(value: boolean) { 12 | this._isOpen = value; 13 | this.updateClasses(); 14 | } 15 | 16 | @HostBinding('class') hostClass; 17 | 18 | constructor( 19 | private el: ElementRef, 20 | ) { } 21 | 22 | get isOpen(): boolean { 23 | return this._isOpen; 24 | } 25 | 26 | private getStyle(): string { 27 | let baseClass = 'table-detail-row'; 28 | if (this.isOpen) { 29 | baseClass = baseClass.concat(' open'); 30 | } else { 31 | baseClass = baseClass.concat(' closed'); 32 | } 33 | return baseClass; 34 | } 35 | 36 | private updateClasses() { 37 | let currentClasses = this.el.nativeElement.className.toString(); 38 | const statusClass = this.getStyle(); 39 | if (this.lastClass) { 40 | currentClasses = currentClasses.replace(this.lastClass, ''); 41 | } 42 | this.lastClass = statusClass; 43 | this.hostClass = `${statusClass} ${currentClasses}`; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-table/directives/banzai-table-cell-config.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, HostBinding, Input, OnInit } from '@angular/core'; 2 | 3 | import { CellAlignConfig, BorderConfig, CellPaddingType, CellPositionType } from '../model/tabledata'; 4 | 5 | @Directive({ 6 | selector: '[appCellConfig]', 7 | }) 8 | export class BanzaiTableCellConfigDirective implements OnInit { 9 | 10 | // align config 11 | @Input() cellAlignConfig?: CellAlignConfig; 12 | @Input() isHeader?: boolean; 13 | 14 | // border config 15 | @Input() borderConfig?: BorderConfig; 16 | 17 | // padding config 18 | @Input() paddingType?: CellPaddingType; 19 | 20 | // position config 21 | @Input() customPosition?: CellPositionType; 22 | 23 | @HostBinding('class') hostClass; 24 | 25 | constructor( 26 | private el: ElementRef, 27 | ) { } 28 | 29 | ngOnInit(): void { 30 | 31 | let classes = this.getAlignClass().concat(' '); 32 | classes = classes.concat(this.getBorderClass()).concat(' '); 33 | classes = classes.concat(this.getPaddingClass()).concat(' '); 34 | classes = classes.concat(this.getPositionClass()); 35 | 36 | this.hostClass = `${classes} ${this.el.nativeElement.className}`; 37 | } 38 | 39 | private getAlignClass(): string { 40 | if (this.cellAlignConfig) { 41 | switch (this.cellAlignConfig) { 42 | case CellAlignConfig.Center: 43 | return this.isHeader ? 'center' : 'text-center'; 44 | } 45 | } 46 | 47 | return ''; 48 | 49 | } 50 | 51 | private getBorderClass(): string { 52 | return this.borderConfig ? `table-cell-${this.borderConfig}-border` : ''; 53 | } 54 | 55 | private getPaddingClass(): string { 56 | return this.paddingType ? `table-cell-padding-${this.paddingType}` : ''; 57 | } 58 | 59 | private getPositionClass(): string { 60 | return this.paddingType ? `table-cell-position-${this.customPosition}` : ''; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-table/directives/banzai-table-cell-text-overflow.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, HostBinding, Input, OnInit } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[appCellTextOverflow]', 5 | }) 6 | export class BanzaiTableCellTextOverflowDirective implements OnInit { 7 | 8 | @Input() enabledOverflow?: boolean; 9 | 10 | @HostBinding('class') hostClass; 11 | 12 | constructor( 13 | private el: ElementRef, 14 | ) { } 15 | 16 | ngOnInit(): void { 17 | this.hostClass = `${this.getOverflowClass()} ${this.el.nativeElement.className}`; 18 | } 19 | 20 | private getOverflowClass(): string { 21 | if (this.enabledOverflow) { 22 | return 'cell-text-overflow'; 23 | } 24 | return ''; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-table/directives/banzai-table-cell-width.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, HostBinding, Input, OnInit } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[appCellWidth]', 5 | }) 6 | export class BanzaiTableCellWidthDirective implements OnInit { 7 | 8 | @Input() customWidth?: string; 9 | 10 | @HostBinding('style.width') width; 11 | 12 | constructor() { } 13 | 14 | ngOnInit(): void { 15 | if (this.customWidth) { 16 | this.width = this.customWidth; 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-table/directives/banzai-table-row-style.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, HostBinding, Input, OnInit } from '@angular/core'; 2 | 3 | import { TableRowDesignConfig, TableRowMarkDesign } from '../model/tabledata'; 4 | 5 | @Directive({ 6 | selector: '[appRowStyle]', 7 | }) 8 | export class BanzaiTableRowStyleDirective implements OnInit { 9 | 10 | @Input() rowDesignConfig: TableRowDesignConfig; 11 | 12 | @Input() set selected(value: boolean) { 13 | this._selected = value; 14 | this.updateClass(); 15 | } 16 | 17 | @HostBinding('class') hostClass; 18 | 19 | private _selected: boolean; 20 | 21 | constructor( 22 | private el: ElementRef, 23 | ) { } 24 | 25 | ngOnInit(): void { 26 | this.updateClass(); 27 | } 28 | 29 | private updateClass() { 30 | let currentClasses = this.el.nativeElement.className.toString(); 31 | const markClass = this.getRowMarkClass(); 32 | 33 | if (this.selected) { 34 | currentClasses = currentClasses.concat(' ').concat(markClass); 35 | } else { 36 | currentClasses = currentClasses.replace(markClass, ''); 37 | } 38 | 39 | this.hostClass = `table-element-row ${currentClasses}`; 40 | } 41 | 42 | get selected(): boolean { 43 | return this._selected; 44 | } 45 | 46 | private getRowMarkClass(): string { 47 | if (this.rowDesignConfig) { 48 | 49 | switch (this.rowDesignConfig.mark) { 50 | case TableRowMarkDesign.LightRed: 51 | return 'banzai-table-row-mark-light-red'; 52 | case TableRowMarkDesign.Smoke: 53 | return 'banzai-table-row-mark-smoke'; 54 | default: 55 | return ''; 56 | } 57 | 58 | } 59 | 60 | return ''; 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-table/directives/banzai-table-style.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, HostBinding, Input, OnInit } from '@angular/core'; 2 | 3 | import { TableDesignConfig, TableDesignType, TableRowHoverDesign } from '../model/tabledata'; 4 | 5 | @Directive({ 6 | selector: '[appTableStyle]', 7 | }) 8 | export class BanzaiTableStyleDirective implements OnInit { 9 | 10 | @Input() designConfig: TableDesignConfig; 11 | 12 | @HostBinding('class') hostClass; 13 | 14 | constructor( 15 | private el: ElementRef, 16 | ) { } 17 | 18 | ngOnInit(): void { 19 | const styleTypeClass = this.getStyleTypeClass(); 20 | const hoverClass = this.getRowHoverClass(); 21 | const classes = styleTypeClass.concat(' ').concat(hoverClass); 22 | 23 | this.hostClass = `${classes} ${this.el.nativeElement.className.toString()}`; 24 | } 25 | 26 | private getStyleTypeClass(): string { 27 | if (this.designConfig) { 28 | switch (this.designConfig.type) { 29 | case TableDesignType.Card: 30 | return 'banzai-table-card'; 31 | case TableDesignType.Border: 32 | return 'banzai-table-border-rows'; 33 | case TableDesignType.Base: 34 | return 'banzai-table-base-rows'; 35 | case TableDesignType.BaseWithCheckbox: 36 | return 'banzai-table-base-rows-with-checkbox'; 37 | case TableDesignType.CardWithTemplateFirst: 38 | return 'banzai-table-card-rows-with-template-first'; 39 | } 40 | } 41 | 42 | return ''; 43 | } 44 | 45 | private getRowHoverClass() { 46 | if (this.designConfig) { 47 | 48 | switch (this.designConfig.rowDesign.hover) { 49 | case TableRowHoverDesign.White: 50 | return 'banzai-table-row-white-hover'; 51 | } 52 | 53 | } 54 | 55 | return ''; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-table/pipe/to-fixed-number.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'toFixedNumber' 5 | }) 6 | export class ToFixedNumberPipe implements PipeTransform { 7 | 8 | transform(value: string, args?: any): any { 9 | 10 | if (args) { 11 | return Number(value).toFixed(args); 12 | } 13 | return Number(value).toFixed(2); 14 | 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /web/src/app/modules/banzai-components/components/banzai-table/pipe/truncate-at-middle.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'truncateAtMiddle' 5 | }) 6 | export class TruncateAtMiddlePipe implements PipeTransform { 7 | 8 | transform(value: any, maxLengthForTruncate: string | number): any { 9 | if (value.length > maxLengthForTruncate) { 10 | return value.slice(0, Number(maxLengthForTruncate) / 2) 11 | .concat('...') 12 | .concat(value.slice(value.length - Number(maxLengthForTruncate) / 2)); 13 | } else { 14 | return value; 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /web/src/app/modules/core/components/not-found/not-found.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 |
8 |
9 | 10 |
11 | 404 13 |
14 |
15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /web/src/app/modules/core/components/not-found/not-found.component.scss: -------------------------------------------------------------------------------- 1 | .not-found-wrapper { 2 | margin-top: 120px; 3 | width: 100%; 4 | 5 | .not-found-content-wrapper { 6 | margin: auto; 7 | display: flex; 8 | flex-direction: row; 9 | justify-content: center; 10 | align-items: center; 11 | 12 | .img-wrapper { 13 | flex: 1; 14 | display: flex; 15 | justify-content: flex-end; 16 | 17 | img { 18 | margin-left: auto; 19 | margin-right: auto; 20 | } 21 | } 22 | 23 | .text { 24 | margin-right: auto; 25 | max-width: 296px; 26 | font-size: 24px; 27 | font-weight: 300; 28 | font-style: normal; 29 | font-stretch: normal; 30 | line-height: 1.33; 31 | letter-spacing: normal; 32 | text-align: left; 33 | color: #7f7f7f; 34 | } 35 | 36 | } 37 | 38 | } 39 | 40 | .not-found-content { 41 | display: flex; 42 | flex: 1; 43 | flex-direction: column; 44 | margin-left: 60px; 45 | } 46 | -------------------------------------------------------------------------------- /web/src/app/modules/core/components/not-found/not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-not-found', 5 | templateUrl: './not-found.component.html', 6 | styleUrls: ['./not-found.component.scss'] 7 | }) 8 | export class NotFoundComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /web/src/app/modules/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NotFoundComponent } from './components/not-found/not-found.component'; 4 | import { NavBarModule } from '../nav-bar/nav-bar.module'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | CommonModule, 9 | NavBarModule, 10 | ], 11 | declarations: [ 12 | NotFoundComponent, 13 | ], 14 | exports: [ 15 | NotFoundComponent, 16 | ], 17 | }) 18 | export class CoreModule { 19 | } 20 | -------------------------------------------------------------------------------- /web/src/app/modules/home/components/product-category-icon/model/ProductCategory.ts: -------------------------------------------------------------------------------- 1 | export enum ProductCategory { 2 | GENERAL = 'General purpose', 3 | COMPUTE = 'Compute optimized', 4 | MEMORY = 'Memory optimized', 5 | GPU = 'GPU instance', 6 | STORAGE = 'Storage optimized', 7 | } 8 | -------------------------------------------------------------------------------- /web/src/app/modules/home/components/product-category-icon/pipe/category-icon.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { ProductCategory } from '../model/ProductCategory'; 3 | 4 | // Usage in template: 5 | @Pipe({ 6 | name: 'categoryIcon', 7 | }) 8 | export class CategoryIconPipe implements PipeTransform { 9 | 10 | transform(category: string): string { 11 | let categoryIcon = ''; 12 | switch (category) { 13 | case ProductCategory.COMPUTE: 14 | categoryIcon = 'compute'; 15 | break; 16 | case ProductCategory.GPU: 17 | categoryIcon = 'gpu'; 18 | break; 19 | case ProductCategory.MEMORY: 20 | categoryIcon = 'memory'; 21 | break; 22 | case ProductCategory.STORAGE: 23 | categoryIcon = 'storage'; 24 | break; 25 | default: 26 | categoryIcon = 'general'; 27 | break; 28 | } 29 | 30 | return `assets/images/ic_instance_category_${categoryIcon}.svg`; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/src/app/modules/home/components/product-category-icon/product-category-icon.component.html: -------------------------------------------------------------------------------- 1 |
4 | 5 |
6 | -------------------------------------------------------------------------------- /web/src/app/modules/home/components/product-category-icon/product-category-icon.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../../theme/tooltip"; 2 | 3 | .icon-wrapper { 4 | width: 80px; 5 | height: 68px; 6 | background-color: rgba(1, 30, 93, 0.73); 7 | border-top-left-radius: 4px; 8 | border-bottom-left-radius: 4px; 9 | display: flex; 10 | align-items: center; 11 | 12 | img { 13 | margin: auto; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /web/src/app/modules/home/components/product-category-icon/product-category-icon.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-product-category-icon', 5 | templateUrl: './product-category-icon.component.html', 6 | styleUrls: ['./product-category-icon.component.scss'], 7 | }) 8 | export class ProductCategoryIconComponent implements OnInit { 9 | 10 | private _category: string; 11 | 12 | @Input() set category(value: string) { 13 | this._category = value; 14 | } 15 | 16 | get category(): string { 17 | return this._category; 18 | } 19 | 20 | constructor() { } 21 | 22 | ngOnInit() { 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /web/src/app/modules/home/components/product-list/product-list.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../../theme/tooltip"; 2 | 3 | .page-wrapper { 4 | margin: 35px 86px; 5 | 6 | .fields { 7 | display: flex; 8 | flex-direction: row; 9 | align-items: center; 10 | 11 | .region-select { 12 | margin-left: 40px; 13 | } 14 | } 15 | 16 | .boxes { 17 | display: flex; 18 | flex-direction: row; 19 | align-items: center; 20 | 21 | .border-box { 22 | min-height: 66px; 23 | display: flex; 24 | flex-direction: column; 25 | margin-top: 35px; 26 | margin-bottom: 35px; 27 | border-radius: 4px; 28 | border: solid 1px #ececec; 29 | padding: 25px; 30 | 31 | .header { 32 | margin-bottom: 16px; 33 | display: flex; 34 | flex-direction: row; 35 | align-items: center; 36 | 37 | .title { 38 | font-size: 16px; 39 | font-weight: bold; 40 | font-style: normal; 41 | font-stretch: normal; 42 | line-height: 1.19; 43 | letter-spacing: 0.4px; 44 | text-align: left; 45 | color: #7f7f7f; 46 | } 47 | 48 | .copy { 49 | margin-left: auto; 50 | } 51 | 52 | } 53 | 54 | .message { 55 | font-size: 16px; 56 | font-weight: normal; 57 | font-style: normal; 58 | font-stretch: normal; 59 | letter-spacing: 0.4px; 60 | text-align: left; 61 | color: #7f7f7f; 62 | } 63 | } 64 | 65 | .no-title { 66 | .message { 67 | margin-top: auto; 68 | } 69 | } 70 | 71 | .warn { 72 | background-color: rgba(174, 174, 174, 0.24); 73 | width: 100%; 74 | min-height: unset; 75 | flex-direction: row; 76 | margin-top: 0; 77 | 78 | img { 79 | margin-right: 16px; 80 | } 81 | 82 | } 83 | 84 | .curl { 85 | width: 70%; 86 | } 87 | 88 | .scraping-time { 89 | width: 20%; 90 | margin-left: auto; 91 | } 92 | } 93 | 94 | } 95 | 96 | .loader-wrapper { 97 | display: flex; 98 | flex-direction: row; 99 | align-items: center; 100 | 101 | mat-spinner { 102 | margin: auto; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /web/src/app/modules/home/containers/home/home.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /web/src/app/modules/home/containers/home/home.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banzaicloud/cloudinfo/22150862eee0b6b9a21230a9074fc7c38afe68a9/web/src/app/modules/home/containers/home/home.component.scss -------------------------------------------------------------------------------- /web/src/app/modules/home/containers/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.scss'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /web/src/app/modules/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HomeComponent } from './containers/home/home.component'; 4 | import { routing } from './home.routing'; 5 | import { ProductListComponent } from './components/product-list/product-list.component'; 6 | import { NavBarModule } from '../nav-bar/nav-bar.module'; 7 | import { BanzaiComponentsModule } from '../banzai-components/banzai-components.module'; 8 | import { ProductCategoryIconComponent } from './components/product-category-icon/product-category-icon.component'; 9 | import { CategoryIconPipe } from './components/product-category-icon/pipe/category-icon.pipe'; 10 | import { MatTooltipModule } from '@angular/material/tooltip'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | CommonModule, 15 | NavBarModule, 16 | BanzaiComponentsModule, 17 | MatTooltipModule, 18 | routing, 19 | ], 20 | declarations: [ 21 | HomeComponent, 22 | ProductListComponent, 23 | ProductCategoryIconComponent, 24 | CategoryIconPipe, 25 | ], 26 | providers: [ 27 | CategoryIconPipe, 28 | ], 29 | }) 30 | export class HomeModule { 31 | } 32 | -------------------------------------------------------------------------------- /web/src/app/modules/home/home.routing.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | import { ModuleWithProviders } from '@angular/core'; 3 | 4 | import { HomeComponent } from './containers/home/home.component'; 5 | import { ProductListComponent } from './components/product-list/product-list.component'; 6 | 7 | export const routes: Routes = [ 8 | { 9 | path: '', component: HomeComponent, children: [ 10 | { path: '', component: ProductListComponent }, 11 | ], 12 | }, 13 | ]; 14 | 15 | export const routing: ModuleWithProviders = RouterModule.forChild(routes); 16 | -------------------------------------------------------------------------------- /web/src/app/modules/nav-bar/components/navigation-bar/navigation-bar.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 9 | 10 | 32 |
33 | 34 | 38 | 39 |
40 | 41 | 42 |
43 | 44 |
45 | -------------------------------------------------------------------------------- /web/src/app/modules/nav-bar/components/navigation-bar/navigation-bar.component.scss: -------------------------------------------------------------------------------- 1 | .header-container { 2 | 3 | .background { 4 | background-image: url("../../../../../assets/images/bg.png"); 5 | background-position: center; 6 | background-repeat: no-repeat; 7 | background-size: cover; 8 | position: absolute; 9 | width: 100%; 10 | top: 0; 11 | left: 0; 12 | z-index: -1; 13 | height: 242px; 14 | } 15 | 16 | .header-wrapper { 17 | display: flex; 18 | flex-direction: row; 19 | align-items: center; 20 | padding: 32px 86px 0; 21 | height: 32px; 22 | 23 | .logo { 24 | margin-top: auto; 25 | margin-bottom: auto; 26 | } 27 | 28 | .social-buttons { 29 | flex-direction: row; 30 | margin-left: auto; 31 | display: flex; 32 | 33 | .twitter { 34 | margin-left: 20px; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /web/src/app/modules/nav-bar/components/navigation-bar/navigation-bar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-navigation-bar', 5 | templateUrl: './navigation-bar.component.html', 6 | styleUrls: ['./navigation-bar.component.scss'], 7 | }) 8 | export class NavigationBarComponent implements OnInit { 9 | 10 | @Input() searchField: boolean; 11 | @Input() title: string; 12 | 13 | constructor( 14 | private elementRef: ElementRef, 15 | ) { } 16 | 17 | ngOnInit() { 18 | this.addScript('https://buttons.github.io/buttons.js'); 19 | this.addScript('https://platform.twitter.com/widgets.js'); 20 | } 21 | 22 | private addScript(url: string) { 23 | const s = document.createElement('script'); 24 | s.type = 'text/javascript'; 25 | s.src = url; 26 | this.elementRef.nativeElement.appendChild(s); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /web/src/app/modules/nav-bar/components/page-title-section/page-title-section.component.html: -------------------------------------------------------------------------------- 1 |
2 |
{{ title }}
3 | 4 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /web/src/app/modules/nav-bar/components/page-title-section/page-title-section.component.scss: -------------------------------------------------------------------------------- 1 | .title-wrapper { 2 | height: 180px; 3 | padding-left: 86px; 4 | padding-right: 86px; 5 | display: flex; 6 | flex-direction: row; 7 | align-items: center; 8 | 9 | .title { 10 | font-size: 48px; 11 | font-weight: bold; 12 | font-style: normal; 13 | font-stretch: normal; 14 | line-height: 1.17; 15 | letter-spacing: normal; 16 | text-align: left; 17 | color: #ececec; 18 | margin-top: auto; 19 | margin-bottom: auto; 20 | } 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /web/src/app/modules/nav-bar/components/page-title-section/page-title-section.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-page-title-section', 5 | templateUrl: './page-title-section.component.html', 6 | styleUrls: ['./page-title-section.component.scss'], 7 | }) 8 | export class PageTitleSectionComponent { 9 | 10 | @Input() title: string; 11 | @Input() searchField: boolean; 12 | 13 | constructor() { } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /web/src/app/modules/nav-bar/nav-bar.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NavigationBarComponent } from './components/navigation-bar/navigation-bar.component'; 4 | import { PageTitleSectionComponent } from './components/page-title-section/page-title-section.component'; 5 | import { SearchModule } from '../search/search.module'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule, 10 | SearchModule, 11 | ], 12 | declarations: [ 13 | NavigationBarComponent, 14 | PageTitleSectionComponent, 15 | ], 16 | exports: [ 17 | NavigationBarComponent, 18 | ], 19 | }) 20 | export class NavBarModule { 21 | } 22 | -------------------------------------------------------------------------------- /web/src/app/modules/search/components/search/search.component.html: -------------------------------------------------------------------------------- 1 |
2 | 10 |
11 | search 12 |
13 |
14 | -------------------------------------------------------------------------------- /web/src/app/modules/search/components/search/search.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../../theme/theme-variables"; 2 | 3 | .wrap { 4 | display: inline-block; 5 | position: relative; 6 | float: right; 7 | padding: 0; 8 | } 9 | 10 | input[type="text"] { 11 | height: $search-filter-height; 12 | font-size: $search-font-size; 13 | display: inline-block; 14 | font-weight: 300; 15 | color: $search-text-color; 16 | border: none; 17 | outline: none; 18 | padding: 3px $search-filter-height 3px 3px; 19 | width: 300px; 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | background: none; 24 | z-index: 3; 25 | transition: width .4s cubic-bezier(0.000, 0.795, 0.000, 1.000); 26 | cursor: pointer; 27 | border-bottom: 1px solid $search-line-color; 28 | } 29 | 30 | input[type="text"]:focus:hover { 31 | border-bottom: 1px solid $search-line-color; 32 | } 33 | 34 | input[type="text"]:focus { 35 | width: 300px; 36 | z-index: 1; 37 | border-bottom: 1px solid $search-line-color; 38 | cursor: text; 39 | } 40 | 41 | .not-empty-field { 42 | width: 300px !important; 43 | border-bottom: 1px solid $search-line-color !important; 44 | cursor: text !important; 45 | } 46 | 47 | .search-icon { 48 | width: $search-filter-height; 49 | height: $search-filter-height; 50 | display: flex; 51 | flex-direction: row; 52 | align-items: center; 53 | 54 | .mat-icon { 55 | margin: auto; 56 | color: $search-icon-button-color; 57 | } 58 | 59 | .mat-icon:hover { 60 | color: $search-icon-button-color-hover; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /web/src/app/modules/search/components/search/search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | import { ActivatedRoute, Router } from '@angular/router'; 4 | import { debounceTime, distinctUntilChanged, pluck, takeUntil } from 'rxjs/operators'; 5 | 6 | @Component({ 7 | selector: 'app-search', 8 | templateUrl: './search.component.html', 9 | styleUrls: ['./search.component.scss'], 10 | }) 11 | export class SearchComponent implements OnInit, OnDestroy { 12 | 13 | @Output() filterChanged: EventEmitter = new EventEmitter(); 14 | 15 | @ViewChild('filterInput', { static: true }) filterInput: ElementRef; 16 | 17 | private $unsubscribe = new Subject(); 18 | private filter$ = new Subject(); 19 | public filter: string; 20 | 21 | constructor( 22 | private activatedRouter: ActivatedRoute, 23 | private router: Router, 24 | ) {} 25 | 26 | ngOnInit() { 27 | this.listenOnFilterChange(); 28 | this.listenOnQueryParams(); 29 | } 30 | 31 | private listenOnQueryParams() { 32 | this.activatedRouter.queryParams 33 | .pipe( 34 | pluck('filter'), 35 | distinctUntilChanged(), 36 | takeUntil(this.$unsubscribe)) 37 | .subscribe((filter: string = '') => { 38 | this.filter = filter; 39 | this.applyFilter(filter); 40 | 41 | if (this.filter) { 42 | this.filterInput.nativeElement.focus(); 43 | } 44 | }); 45 | } 46 | 47 | public applyFilter(filter: string = this.filter) { 48 | this.filter$.next(filter); 49 | } 50 | 51 | private listenOnFilterChange() { 52 | this.filter$ 53 | .pipe( 54 | distinctUntilChanged(), 55 | debounceTime(50), 56 | takeUntil(this.$unsubscribe)) 57 | .subscribe((filter: string) => { 58 | this.router.navigate(['.'], { 59 | relativeTo: this.activatedRouter, 60 | queryParams: { filter: filter || null }, 61 | queryParamsHandling: 'merge', 62 | }); 63 | this.filterChanged.emit(filter); 64 | }); 65 | } 66 | 67 | ngOnDestroy() { 68 | this.$unsubscribe.next(); 69 | this.$unsubscribe.complete(); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /web/src/app/modules/search/search.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { SearchComponent } from './components/search/search.component'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { MatIconModule } from '@angular/material/icon'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule, 10 | MatIconModule, 11 | FormsModule, 12 | ], 13 | declarations: [ 14 | SearchComponent, 15 | ], 16 | exports: [ 17 | SearchComponent, 18 | ], 19 | }) 20 | export class SearchModule { 21 | } 22 | -------------------------------------------------------------------------------- /web/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banzaicloud/cloudinfo/22150862eee0b6b9a21230a9074fc7c38afe68a9/web/src/assets/.gitkeep -------------------------------------------------------------------------------- /web/src/assets/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banzaicloud/cloudinfo/22150862eee0b6b9a21230a9074fc7c38afe68a9/web/src/assets/images/bg.png -------------------------------------------------------------------------------- /web/src/assets/images/ic_copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /web/src/assets/images/ic_instance_category_compute.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /web/src/assets/images/ic_instance_category_general.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /web/src/assets/images/ic_instance_category_gpu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /web/src/assets/images/ic_instance_category_memory.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /web/src/assets/images/ic_instance_category_storage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /web/src/assets/images/ic_warn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /web/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | trackingID: 'UA-109213883-1', 4 | baseProductUrl: 'api/v1/', 5 | }; 6 | -------------------------------------------------------------------------------- /web/src/environments/environment.proxy.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | trackingID: '', 4 | baseProductUrl: '/cloudinfo/api/v1/', 5 | }; 6 | 7 | /* 8 | * In development mode, to ignore zone related error stack frames such as 9 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 10 | * import the following file, but please comment it out in production mode 11 | * because it will have performance impact when throw error 12 | */ 13 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 14 | -------------------------------------------------------------------------------- /web/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | trackingID: '', 8 | baseProductUrl: 'api/v1/', 9 | }; 10 | 11 | /* 12 | * In development mode, to ignore zone related error stack frames such as 13 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 14 | * import the following file, but please comment it out in production mode 15 | * because it will have performance impact when throw error 16 | */ 17 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /web/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banzaicloud/cloudinfo/22150862eee0b6b9a21230a9074fc7c38afe68a9/web/src/favicon.ico -------------------------------------------------------------------------------- /web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Cloud Products 7 | 8 | 9 | 11 | 12 | 15 | 16 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /web/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage/web'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /web/src/main.ts: -------------------------------------------------------------------------------- 1 | import {enableProdMode} from '@angular/core'; 2 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 3 | 4 | import {AppModule} from './app/app.module'; 5 | import {environment} from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | const trackingID = environment.trackingID; 10 | document.write(``) 19 | } 20 | 21 | platformBrowserDynamic().bootstrapModule(AppModule) 22 | .catch(err => console.log(err)); 23 | -------------------------------------------------------------------------------- /web/src/test.ts: -------------------------------------------------------------------------------- 1 | import 'zone.js/testing'; 2 | import { getTestBed } from '@angular/core/testing'; 3 | import { 4 | BrowserDynamicTestingModule, 5 | platformBrowserDynamicTesting 6 | } from '@angular/platform-browser-dynamic/testing'; 7 | 8 | declare const require: any; 9 | 10 | // First, initialize the Angular testing environment. 11 | getTestBed().initTestEnvironment( 12 | BrowserDynamicTestingModule, 13 | platformBrowserDynamicTesting() 14 | ); 15 | // Then we find all the tests. 16 | const context = require.context('./', true, /\.spec\.ts$/); 17 | // And load the modules. 18 | context.keys().map(context); 19 | -------------------------------------------------------------------------------- /web/src/theme/animations.scss: -------------------------------------------------------------------------------- 1 | .wrapper-with-animations { 2 | -webkit-animation: fadeIn 1s; 3 | animation: fadeIn 1s; 4 | } 5 | 6 | @-webkit-keyframes fadeIn { 7 | from { 8 | opacity: 0; 9 | } 10 | to { 11 | opacity: 1; 12 | } 13 | } 14 | 15 | @keyframes fadeIn { 16 | from { 17 | opacity: 0; 18 | } 19 | to { 20 | opacity: 1; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web/src/theme/styles.scss: -------------------------------------------------------------------------------- 1 | @import "~@angular/material/prebuilt-themes/indigo-pink.css"; 2 | @import "theme-variables"; 3 | 4 | body { 5 | background-color: $background-color !important; 6 | } 7 | 8 | html * { 9 | font-family: 'Lato', sans-serif; 10 | outline: none !important; 11 | margin: 0; 12 | } 13 | 14 | .banzai-input-label { 15 | height: 12px; 16 | font-size: 12px; 17 | font-weight: 400; 18 | font-style: normal; 19 | font-stretch: normal; 20 | line-height: 1.33; 21 | letter-spacing: 1px; 22 | color: #535353; 23 | text-align: left; 24 | margin-bottom: 6px; 25 | text-transform: uppercase; 26 | } 27 | 28 | .banzai-input-value { 29 | width: 260px; 30 | min-height: 48px; 31 | padding: 12px; 32 | border-radius: 2px; 33 | background-color: #ECECEC; 34 | font-size: 16px; 35 | font-weight: normal; 36 | font-style: normal; 37 | font-stretch: normal; 38 | line-height: 1.5; 39 | letter-spacing: 0.2px; 40 | color: #7F7F7F; 41 | border-style: none; 42 | border-bottom: solid 1px #ECECEC; 43 | } 44 | 45 | .banzai-input { 46 | position: relative; 47 | 48 | mat-select, input, .generic-select-trigger { 49 | position: relative; 50 | @extend .banzai-input-value; 51 | 52 | &:hover { 53 | border-bottom: solid 1px #AEAEAE; 54 | } 55 | 56 | &:active, &:focus { 57 | border-bottom: solid 1px #5883BE; 58 | } 59 | 60 | &:disabled, &:read-only { 61 | border-bottom: none; 62 | } 63 | } 64 | 65 | mat-select { 66 | padding: 0 !important; 67 | 68 | .mat-select-trigger { 69 | padding: 12px; 70 | } 71 | 72 | .mat-select-value { 73 | color: #7F7F7F; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /web/src/theme/theme-variables.scss: -------------------------------------------------------------------------------- 1 | $background-color: #F7F6F6; 2 | $white: rgba(255, 255, 255, 0.87); 3 | 4 | $table-cell-border-color: #E0E0E0; 5 | $table-cell-border: 1px solid $table-cell-border-color; 6 | $table-cell-padding-to-border: 24px; 7 | 8 | $table-cell-height: 68px; 9 | $table-action-cell-width: 68px; 10 | $table-action-cell-width-small: 56px; 11 | 12 | $custom-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.15); 13 | 14 | $table-row-radius: 4px; 15 | $table-header-text-color: #B8B8B8; 16 | 17 | $search-filter-height: 46px; 18 | $search-line-color: #ECECEC; 19 | $search-font-size: 16px; 20 | $search-text-color: #ECECEC; 21 | $search-icon-size: 17.5px; 22 | $search-icon-button-color: #ECECEC; 23 | $search-icon-button-color-hover: #C6C6C6; 24 | -------------------------------------------------------------------------------- /web/src/theme/tooltip.scss: -------------------------------------------------------------------------------- 1 | .tooltip-class { 2 | font-size: 0.8em; 3 | white-space: pre-line; 4 | } 5 | -------------------------------------------------------------------------------- /web/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "main.ts", 9 | "polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /web/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /web/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "downlevelIteration": true, 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "module": "es2020", 10 | "moduleResolution": "node", 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web/web.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 Banzai Cloud 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package web 16 | 17 | import "embed" 18 | 19 | // nolint: gochecknoglobals 20 | //go:embed dist/web/* 21 | //go:embed dist/web/**/* 22 | var files embed.FS 23 | 24 | // Files returns a filesystem with static files. 25 | func Files() embed.FS { 26 | return files 27 | } 28 | --------------------------------------------------------------------------------