├── .dockerignore
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── feature_request.md
│ └── 🐛-bug-report.md
└── workflows
│ └── go.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── dbevents
├── .gitignore
├── Dockerfile
├── build.sbt
├── project
│ ├── build.properties
│ └── plugins.sbt
├── scripts
│ └── docker-push.sh
└── src
│ ├── conn-string-parser
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ ├── parser.go
│ ├── parser_mysql.go
│ ├── parser_postgres.go
│ ├── parser_sqlserver.go
│ └── types.go
│ └── main
│ ├── resources
│ ├── application.conf
│ └── logback.xml
│ └── scala
│ └── com
│ └── spaceuptech
│ └── dbevents
│ ├── EventsApp.scala
│ ├── EventsSupervisor.scala
│ ├── Global.scala
│ ├── Types.scala
│ ├── database
│ ├── Database.scala
│ ├── Debezium.scala
│ ├── KubeDatabaseHistory.scala
│ ├── KubeOffsetBackingStore.scala
│ ├── Mongo.scala
│ ├── MongoStore.scala
│ ├── Utils.scala
│ └── package.scala
│ └── spacecloud
│ ├── EventsSink.scala
│ ├── ProjectManager.scala
│ ├── ProjectsSupervisor.scala
│ ├── Routes.scala
│ └── spacecloud.scala
├── examples
├── basic-todo-app
│ ├── config.yaml
│ └── index.html
├── realtime-monitoring-app
│ ├── config.yaml
│ ├── demo.css
│ ├── demo.js
│ ├── device1.py
│ ├── device2.py
│ ├── index.html
│ ├── queries.sql
│ └── service.py
└── realtime-todo-app
│ ├── config.yaml
│ └── index.html
├── gateway
├── .dockerignore
├── Dockerfile
├── config
│ ├── config.go
│ ├── generate.go
│ ├── integration.go
│ ├── load.go
│ ├── resource.go
│ ├── routing.go
│ ├── routing_test.go
│ └── store.go
├── go.mod
├── go.sum
├── integration_test.sh
├── main.go
├── managers
│ ├── admin
│ │ ├── admin.go
│ │ ├── auth.go
│ │ ├── auth_test.go
│ │ ├── integration.go
│ │ ├── login.go
│ │ ├── login_test.go
│ │ ├── operations.go
│ │ ├── operations_test.go
│ │ ├── service.go
│ │ ├── types.go
│ │ └── types_test.go
│ ├── integration
│ │ ├── auth.go
│ │ ├── config.go
│ │ ├── hook.go
│ │ ├── http.go
│ │ ├── integration.go
│ │ ├── operations.go
│ │ └── types.go
│ ├── managers.go
│ └── syncman
│ │ ├── helpers.go
│ │ ├── helpers_test.go
│ │ ├── http.go
│ │ ├── operations.go
│ │ ├── operations_test.go
│ │ ├── store.go
│ │ ├── store_kube.go
│ │ ├── store_local.go
│ │ ├── syncman.go
│ │ ├── syncman_auth.go
│ │ ├── syncman_auth_test.go
│ │ ├── syncman_cache.go
│ │ ├── syncman_crud.go
│ │ ├── syncman_crud_test.go
│ │ ├── syncman_eventing.go
│ │ ├── syncman_eventing_test.go
│ │ ├── syncman_file.go
│ │ ├── syncman_file_test.go
│ │ ├── syncman_integration.go
│ │ ├── syncman_letsencrypt.go
│ │ ├── syncman_project.go
│ │ ├── syncman_project_test.go
│ │ ├── syncman_routing.go
│ │ ├── syncman_routing_test.go
│ │ ├── syncman_runner.go
│ │ ├── syncman_runner_test.go
│ │ ├── syncman_services.go
│ │ ├── syncman_services_test.go
│ │ ├── types.go
│ │ └── types_test.go
├── model
│ ├── cache.go
│ ├── crud.go
│ ├── deploy.go
│ ├── eventing.go
│ ├── file.go
│ ├── functions.go
│ ├── graphql.go
│ ├── hooks.go
│ ├── metric.go
│ ├── pubsub.go
│ ├── quotas.go
│ ├── realtime.go
│ ├── request.go
│ ├── schema_type.go
│ ├── types.go
│ └── validate.go
├── modules
│ ├── auth
│ │ ├── auth.go
│ │ ├── auth_test.go
│ │ ├── errors.go
│ │ ├── handle.go
│ │ ├── handle_crud.go
│ │ ├── handle_crud_test.go
│ │ ├── handle_eventing.go
│ │ ├── handle_eventing_test.go
│ │ ├── handle_file.go
│ │ ├── handle_file_test.go
│ │ ├── handle_functions.go
│ │ ├── handle_functions_test.go
│ │ ├── helpers.go
│ │ ├── helpers
│ │ │ ├── helpers.go
│ │ │ ├── helpers_test.go
│ │ │ ├── operations.go
│ │ │ └── operations_test.go
│ │ ├── integration.go
│ │ ├── match.go
│ │ ├── match_test.go
│ │ ├── match_types.go
│ │ ├── match_types_test.go
│ │ ├── model.go
│ │ ├── operations.go
│ │ ├── setters.go
│ │ ├── sort.go
│ │ └── types.go
│ ├── crud
│ │ ├── batcher.go
│ │ ├── bolt
│ │ │ ├── aggregate.go
│ │ │ ├── batch.go
│ │ │ ├── bolt.go
│ │ │ ├── collections.go
│ │ │ ├── collections_test.go
│ │ │ ├── create.go
│ │ │ ├── create_test.go
│ │ │ ├── delete.go
│ │ │ ├── delete_test.go
│ │ │ ├── describe.go
│ │ │ ├── raw.go
│ │ │ ├── read.go
│ │ │ ├── read_test.go
│ │ │ ├── update.go
│ │ │ ├── update_test.go
│ │ │ └── variables_test.go
│ │ ├── crud.go
│ │ ├── dataloader.go
│ │ ├── helpers.go
│ │ ├── internal.go
│ │ ├── mgo
│ │ │ ├── aggregate.go
│ │ │ ├── batch.go
│ │ │ ├── collections.go
│ │ │ ├── create.go
│ │ │ ├── delete.go
│ │ │ ├── describe.go
│ │ │ ├── helpers.go
│ │ │ ├── helpers_test.go
│ │ │ ├── integration_collections_test.go
│ │ │ ├── integration_create_test.go
│ │ │ ├── integration_delete_test.go
│ │ │ ├── integration_main_test.go
│ │ │ ├── integration_read_test.go
│ │ │ ├── integration_update_test.go
│ │ │ ├── mongo.go
│ │ │ ├── raw.go
│ │ │ ├── read.go
│ │ │ └── update.go
│ │ ├── operations.go
│ │ ├── setters.go
│ │ ├── sql
│ │ │ ├── aggregate.go
│ │ │ ├── batch.go
│ │ │ ├── collections.go
│ │ │ ├── create.go
│ │ │ ├── create_test.go
│ │ │ ├── delete.go
│ │ │ ├── delete_test.go
│ │ │ ├── describe.go
│ │ │ ├── helpers.go
│ │ │ ├── helpers_test.go
│ │ │ ├── integration_collections_test.go
│ │ │ ├── integration_create_test.go
│ │ │ ├── integration_delete_test.go
│ │ │ ├── integration_describe_test.go
│ │ │ ├── integration_main_test.go
│ │ │ ├── integration_raw_test.go
│ │ │ ├── integration_read_test.go
│ │ │ ├── integration_update_test.go
│ │ │ ├── raw.go
│ │ │ ├── read.go
│ │ │ ├── read_aggregate_test.go
│ │ │ ├── read_test.go
│ │ │ ├── sql.go
│ │ │ ├── update.go
│ │ │ └── update_test.go
│ │ └── types.go
│ ├── eventing
│ │ ├── broadcast.go
│ │ ├── broadcast_test.go
│ │ ├── crud.go
│ │ ├── eventing.go
│ │ ├── eventing_test.go
│ │ ├── file.go
│ │ ├── file_test.go
│ │ ├── handle_intents.go
│ │ ├── handle_intents_test.go
│ │ ├── handle_staged.go
│ │ ├── handle_staged_test.go
│ │ ├── helpers.go
│ │ ├── helpers_test.go
│ │ ├── http.go
│ │ ├── http_test.go
│ │ ├── operations.go
│ │ ├── operations_test.go
│ │ ├── routine.go
│ │ └── types.go
│ ├── filestore
│ │ ├── amazons3
│ │ │ ├── amazonS3.go
│ │ │ ├── create.go
│ │ │ ├── delete.go
│ │ │ ├── raw.go
│ │ │ ├── raw_test.go
│ │ │ └── read.go
│ │ ├── gcpstorage
│ │ │ ├── create.go
│ │ │ ├── delete.go
│ │ │ ├── gcpstorage.go
│ │ │ ├── raw.go
│ │ │ ├── raw_test.go
│ │ │ └── read.go
│ │ ├── local
│ │ │ ├── create.go
│ │ │ ├── delete.go
│ │ │ ├── integration_create_test.go
│ │ │ ├── integration_delete_test.go
│ │ │ ├── local.go
│ │ │ ├── raw.go
│ │ │ └── read.go
│ │ ├── operations.go
│ │ └── store.go
│ ├── functions
│ │ ├── functions.go
│ │ ├── helpers.go
│ │ ├── helpers_test.go
│ │ ├── operations.go
│ │ └── types.go
│ ├── getters.go
│ ├── global
│ │ ├── caching
│ │ │ ├── caching.go
│ │ │ ├── crud.go
│ │ │ ├── helpers.go
│ │ │ ├── helpers_test.go
│ │ │ ├── operations.go
│ │ │ ├── redis.go
│ │ │ └── types.go
│ │ ├── global.go
│ │ ├── letsencrypt
│ │ │ ├── config.go
│ │ │ ├── domains.go
│ │ │ ├── kube_store.go
│ │ │ ├── letsencrypt.go
│ │ │ ├── operations.go
│ │ │ └── sc_store.go
│ │ ├── metrics
│ │ │ ├── helpers.go
│ │ │ ├── helpers_test.go
│ │ │ ├── metrics.go
│ │ │ ├── metrics_test.go
│ │ │ ├── operations.go
│ │ │ ├── operations_test.go
│ │ │ ├── projects.go
│ │ │ ├── projects_test.go
│ │ │ └── sink.go
│ │ └── routing
│ │ │ ├── helpers.go
│ │ │ ├── http.go
│ │ │ ├── http_test.go
│ │ │ ├── modify.go
│ │ │ ├── modify_test.go
│ │ │ ├── operations.go
│ │ │ ├── operations_test.go
│ │ │ ├── routes.go
│ │ │ ├── routes_test.go
│ │ │ ├── routing.go
│ │ │ └── routing_test.go
│ ├── module.go
│ ├── modules.go
│ ├── operations.go
│ ├── realtime
│ │ ├── clients.go
│ │ ├── helpers.go
│ │ ├── operations.go
│ │ ├── realtime.go
│ │ ├── routine.go
│ │ └── types.go
│ ├── schema
│ │ ├── creation.go
│ │ ├── creation_test.go
│ │ ├── eventing.go
│ │ ├── eventing_test.go
│ │ ├── helpers.go
│ │ ├── helpers
│ │ │ ├── helpers.go
│ │ │ ├── operations.go
│ │ │ └── operations_test.go
│ │ ├── inspection.go
│ │ ├── inspection_test.go
│ │ ├── operations.go
│ │ ├── operations_test.go
│ │ ├── schema.go
│ │ ├── template.go
│ │ ├── template_test.go
│ │ ├── types.go
│ │ └── variables.go
│ ├── types.go
│ └── userman
│ │ ├── operations.go
│ │ └── user.go
├── scripts
│ └── docker-push.sh
├── server
│ ├── handlers
│ │ ├── clusters.go
│ │ ├── config_admin.go
│ │ ├── config_auth.go
│ │ ├── config_batch.go
│ │ ├── config_cache.go
│ │ ├── config_crud.go
│ │ ├── config_eventing.go
│ │ ├── config_file.go
│ │ ├── config_integration.go
│ │ ├── config_letsencrypt.go
│ │ ├── config_project.go
│ │ ├── config_routing.go
│ │ ├── config_services.go
│ │ ├── crud.go
│ │ ├── eventing.go
│ │ ├── filestore.go
│ │ ├── functions.go
│ │ ├── graphql.go
│ │ ├── health_check.go
│ │ ├── middleware_helpers.go
│ │ ├── mission_control.go
│ │ ├── realtime.go
│ │ ├── schema.go
│ │ ├── userman.go
│ │ ├── websocket.go
│ │ └── websocket_test.go
│ ├── http.go
│ ├── middleware.go
│ ├── routes.go
│ └── server.go
└── utils
│ ├── apply_config.go
│ ├── atomic.go
│ ├── client
│ ├── client.go
│ └── websocket.go
│ ├── constants.go
│ ├── errors.go
│ ├── eventing.go
│ ├── file.go
│ ├── graphql.go
│ ├── graphql
│ ├── create.go
│ ├── delete.go
│ ├── func.go
│ ├── graphql.go
│ ├── graphql_test.go
│ ├── helpers.go
│ ├── mutation.go
│ ├── read.go
│ ├── types.go
│ ├── unit_database_test.go
│ ├── unit_function_test.go
│ ├── unit_mock_test.go
│ ├── unit_prepare_query_test.go
│ └── update.go
│ ├── graphql_test.go
│ ├── http.go
│ ├── jwt
│ ├── helpers.go
│ ├── helpers_test.go
│ ├── jwt.go
│ └── operations.go
│ ├── leader
│ ├── leader.go
│ └── operations.go
│ ├── mask.go
│ ├── mast_test.go
│ ├── pubsub
│ ├── helpers.go
│ ├── operations.go
│ └── pubsub.go
│ ├── store.go
│ ├── store_test.go
│ ├── string.go
│ ├── string_test.go
│ ├── time.go
│ ├── tmpl
│ ├── go.go
│ └── go_test.go
│ ├── utils.go
│ ├── utils_test.go
│ ├── validate.go
│ └── validate_test.go
├── install-manifests
├── cli
│ ├── install.ps1
│ └── install.sh
├── docker
│ ├── mongo
│ │ └── docker-compose.yaml
│ ├── mysql
│ │ └── docker-compose.yaml
│ ├── postgres
│ │ ├── docker-compose.yaml
│ │ └── postgresql.conf
│ └── sql-server
│ │ └── docker-compose.yaml
├── helm
│ ├── index.yaml
│ ├── mongo
│ │ ├── .helmignore
│ │ ├── Chart.yaml
│ │ ├── templates
│ │ │ └── mongo.yaml
│ │ └── values.yaml
│ ├── mysql
│ │ ├── .helmignore
│ │ ├── Chart.yaml
│ │ ├── templates
│ │ │ └── mysql.yaml
│ │ └── values.yaml
│ ├── postgres
│ │ ├── .helmignore
│ │ ├── Chart.yaml
│ │ ├── templates
│ │ │ └── postgres.yaml
│ │ └── values.yaml
│ ├── space-cloud
│ │ ├── .helmignore
│ │ ├── Chart.yaml
│ │ ├── templates
│ │ │ ├── 01-core.yaml
│ │ │ ├── 02-redis.yaml
│ │ │ ├── 03-prometheus.yaml
│ │ │ ├── 04-runner.yaml
│ │ │ ├── 05-gateway.yaml
│ │ │ ├── 06-dbevents.yaml
│ │ │ └── keda-2.0.0.yaml
│ │ └── values.yaml
│ └── sqlserver
│ │ ├── .helmignore
│ │ ├── Chart.yaml
│ │ ├── templates
│ │ └── sqlserver.yaml
│ │ └── values.yaml
├── kubernetes
│ ├── 01-core.yaml
│ ├── 02-redis.yaml
│ ├── 03-prometheus.yaml
│ ├── 04-runner.yaml
│ ├── 05-gateway.yaml
│ ├── 06-dbevents.yaml
│ └── keda-2.0.0.yaml
└── quick-start
│ └── docker-compose
│ ├── mongo
│ └── docker-compose.yaml
│ ├── mysql
│ └── docker-compose.yaml
│ └── postgres
│ └── docker-compose.yaml
├── postman
└── Space Up.postman_collection.json
├── protocol
└── config.schema.json
├── runner
├── Dockerfile
├── actions.go
├── go.mod
├── go.sum
├── main.go
├── metrics
│ └── metrics.go
├── model
│ ├── artifact.go
│ ├── config.go
│ ├── driver.go
│ ├── environment.go
│ ├── eventing.go
│ ├── key.go
│ ├── kubeSecret.go
│ ├── metrics.go
│ ├── proxy.go
│ ├── pubsub.go
│ ├── roles.go
│ ├── routing.go
│ ├── service.go
│ └── version.go
├── modules
│ ├── pubsub
│ │ ├── helpers.go
│ │ ├── operations.go
│ │ ├── pubsub.go
│ │ └── store.go
│ ├── routing
│ │ ├── actions.go
│ │ └── generate.go
│ ├── scaler
│ │ ├── externalscaler
│ │ │ ├── externalscaler.pb.go
│ │ │ └── externalscaler.proto
│ │ ├── helpers.go
│ │ ├── keda.go
│ │ ├── operations.go
│ │ ├── prometheus.go
│ │ ├── routine.go
│ │ └── scaler.go
│ └── secrets
│ │ ├── actions.go
│ │ └── generate.go
├── scripts
│ └── docker-push.sh
├── server
│ ├── config.go
│ ├── handle.go
│ ├── handle_env.go
│ ├── handle_roles.go
│ ├── handle_secrets.go
│ ├── helpers.go
│ ├── middlware.go
│ ├── routes.go
│ └── server.go
└── utils
│ ├── auth
│ ├── admin.go
│ ├── auth.go
│ └── keys.go
│ ├── debounce.go
│ ├── domains.go
│ ├── driver
│ ├── driver.go
│ ├── istio
│ │ ├── apply.go
│ │ ├── apply_helpers.go
│ │ ├── config.go
│ │ ├── delete.go
│ │ ├── delete_helpers.go
│ │ ├── get.go
│ │ ├── get_helpers.go
│ │ ├── get_test.go
│ │ ├── helpers.go
│ │ ├── helpers_test.go
│ │ ├── istio.go
│ │ ├── logs.go
│ │ ├── names.go
│ │ ├── names_test.go
│ │ ├── projects.go
│ │ ├── scale.go
│ │ ├── secrets.go
│ │ └── secrets_test.go
│ └── operations.go
│ ├── file.go
│ └── http.go
└── space-cli
├── cmd
├── cmd.go
├── model
│ ├── cli.go
│ ├── constants.go
│ └── service.go
├── modules
│ ├── accounts
│ │ ├── commands.go
│ │ ├── delete.go
│ │ ├── delete_test.go
│ │ ├── set.go
│ │ ├── set_test.go
│ │ ├── view.go
│ │ └── view_test.go
│ ├── addons
│ │ ├── commands.go
│ │ └── database.go
│ ├── all.go
│ ├── auth
│ │ ├── commands.go
│ │ ├── delete.go
│ │ ├── delete_test.go
│ │ ├── generate.go
│ │ ├── generate_test.go
│ │ ├── getters.go
│ │ ├── getters_test.go
│ │ └── helpers.go
│ ├── database
│ │ ├── commands.go
│ │ ├── delete.go
│ │ ├── delete_test.go
│ │ ├── generate.go
│ │ ├── generate_test.go
│ │ ├── getters.go
│ │ ├── getters_test.go
│ │ └── helpers.go
│ ├── delete.go
│ ├── deploy
│ │ ├── commands.go
│ │ ├── deploy.go
│ │ └── prepare.go
│ ├── eventing
│ │ ├── commands.go
│ │ ├── delete.go
│ │ ├── delete_test.go
│ │ ├── generate.go
│ │ ├── generate_test.go
│ │ ├── getters.go
│ │ ├── getters_test.go
│ │ └── helpers.go
│ ├── filestore
│ │ ├── commands.go
│ │ ├── delete.go
│ │ ├── delete_test.go
│ │ ├── generate.go
│ │ ├── generate_test.go
│ │ ├── getters.go
│ │ ├── getters_test.go
│ │ └── helpers.go
│ ├── generator.go
│ ├── getter.go
│ ├── ingress
│ │ ├── commands.go
│ │ ├── delete.go
│ │ ├── delete_test.go
│ │ ├── generate.go
│ │ ├── generate_test.go
│ │ ├── getters.go
│ │ ├── getters_test.go
│ │ └── helpers.go
│ ├── letsencrypt
│ │ ├── commands.go
│ │ ├── delete.go
│ │ ├── delete_test.go
│ │ ├── generate.go
│ │ ├── generate_test.go
│ │ ├── getters.go
│ │ └── getters_test.go
│ ├── login
│ │ └── commands.go
│ ├── logs
│ │ ├── commands.go
│ │ └── getters.go
│ ├── operations
│ │ ├── apply.go
│ │ ├── commands.go
│ │ ├── destroy.go
│ │ ├── inspect.go
│ │ ├── list.go
│ │ ├── setup.go
│ │ ├── start.go
│ │ ├── stop.go
│ │ └── update.go
│ ├── project
│ │ ├── commands.go
│ │ ├── delete.go
│ │ ├── delete_test.go
│ │ ├── generate.go
│ │ ├── generate_test.go
│ │ ├── getters.go
│ │ ├── getters_test.go
│ │ └── helpers.go
│ ├── remote-services
│ │ ├── commands.go
│ │ ├── delete.go
│ │ ├── delete_test.go
│ │ ├── generate.go
│ │ ├── generate_test.go
│ │ ├── getters.go
│ │ ├── getters_test.go
│ │ └── helpers.go
│ └── services
│ │ ├── commands.go
│ │ ├── delete.go
│ │ ├── delete_test.go
│ │ ├── generate.go
│ │ ├── generate_test.go
│ │ ├── getters.go
│ │ ├── getters_test.go
│ │ └── helpers.go
└── utils
│ ├── creds.go
│ ├── creds_test.go
│ ├── docker.go
│ ├── domains.go
│ ├── domains_test.go
│ ├── file.go
│ ├── file
│ ├── file.go
│ └── fileTest.go
│ ├── file_test.go
│ ├── filter
│ └── filter.go
│ ├── helm.go
│ ├── http.go
│ ├── input
│ └── input.go
│ ├── log.go
│ ├── log_test.go
│ ├── login.go
│ ├── login_test.go
│ ├── operations.go
│ ├── paths.go
│ ├── paths_test.go
│ ├── printer.go
│ ├── printer_test.go
│ ├── projects.go
│ ├── projects_test.go
│ ├── transport
│ └── transport.go
│ ├── types.go
│ ├── versioning.go
│ └── versioning_test.go
├── go.mod
├── go.sum
├── main.go
├── plugins.go
├── scripts
└── push.sh
├── utils.go
└── versioning.go
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 | *.zip
8 |
9 | # Test binary, build with `go test -c`
10 | *.test
11 |
12 | # Output of the go coverage tool, specifically when used with LiteIDE
13 | *.out
14 |
15 | **/node_modules/**
16 | mission-control/public/**
17 | mission-control/src/**
18 | mission-control/node_modules/**
19 |
20 | .sass-cache
21 |
22 | # Ignore the binaries
23 | space-cloud
24 | gateway/gateway
25 | runner/runner
26 | metric-proxy/metric-proxy
27 |
28 | raft-store/**
29 | publish.sh
30 |
31 | test_config.yaml
32 | config.yaml
33 |
34 | # Helper scripts
35 | # kube/**
36 |
37 | # docs and learn
38 | docs/**
39 | learn/**
40 |
41 | #IDEs
42 | .idea
43 | .vscode
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | contact_links:
2 | - name: Ask a question or get support
3 | url: https://github.com/spaceuptech/space-cloud/discussions/new
4 | about: Ask a question or request support for using Space Cloud
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F680 Feature request"
3 | about: Suggest an idea for improving SpaceCloud
4 | title: "[Feature] "
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### The problem faced currently?
11 |
15 |
16 | ### How can we solve it?
17 |
21 |
22 |
23 | >If you want this feature to be implemented, give it a thumbs up reaction, so that we can determine which features are important to you.
24 | >👍
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/🐛-bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Bug report"
3 | about: Create a report to help us improve
4 | title: "[Bug] "
5 | labels: "\U0001F41B bug"
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Describe the bug
11 |
14 |
15 | ### Expected behavior
16 |
19 |
20 |
21 | ### Steps to reproduce
22 |
25 |
26 | ### Your environment
27 | - Space Cloud version:
28 | - OS:
29 | - Kubernetes or Docker:
30 | - Browser (if applicable):
31 |
32 | >If this bug restricts your use of space-cloud, give it a thumbs up reaction, so that we can determine which bugs need to be fixed immediately.
33 | >👍
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vscode
3 |
4 | test
5 |
6 | # Binaries for programs and plugins
7 | *.exe
8 | *.exe~
9 | *.dll
10 | *.so
11 | *.zip
12 | *.dylib
13 | *.db
14 | # Test binary, build with `go test -c`
15 | *.test
16 |
17 | # sh file for bash completion
18 | *.sh
19 | !install-manifests/cli/install.sh
20 |
21 | # Output of the go coverage tool, specifically when used with LiteIDE
22 | *.out
23 |
24 | build/
25 | node_modules
26 | css
27 | .sass-cache
28 |
29 | # Ignore the binaries
30 | space-cloud
31 | gateway/gateway
32 | runner/runner
33 | metric-proxy/metric-proxy
34 | space-cli/space-cli
35 |
36 | # Ignore the vendor folders
37 | metric-proxy/vendor
38 | runner/vendor
39 | gateway/vendor
40 | space-cli/vendor
41 |
42 | space-cli/cmd/cmd
43 |
44 | raft-store
45 | publish.sh
46 | runner/runner.db
47 |
48 | test_config.yaml
49 | config.yaml
50 |
51 | # Helper scripts
52 | kube
53 | scripts
54 | runner/scripts
55 | gateway/scripts
56 | metric-proxy/scripts
57 | docker-build.sh
58 |
59 | scripts/*
60 | #IDEs
61 |
62 | yarn.lock
63 |
64 | #boltdatabase
65 | gateway/testbolt.db
66 |
67 |
68 | npm-debug.log
69 |
70 | # ignore space-cloud helm chart
71 | !install-manifests/helm/space-cloud
--------------------------------------------------------------------------------
/dbevents/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 |
3 | .settings
4 | .project
5 | .classpath
6 |
7 | .idea
8 | *.iml
9 |
10 | .metals
11 | .bloop
12 |
13 |
--------------------------------------------------------------------------------
/dbevents/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:8 as stage0
2 | LABEL snp-multi-stage="intermediate"
3 | LABEL snp-multi-stage-id="58f58868-292b-482f-8850-79b72faa9d52"
4 | WORKDIR /opt/docker
5 | COPY target/docker/stage/1/opt /1/opt
6 | COPY target/docker/stage/2/opt /2/opt
7 | USER root
8 | RUN ["chmod", "-R", "u=rX,g=rX", "/1/opt/docker"]
9 | RUN ["chmod", "-R", "u=rX,g=rX", "/2/opt/docker"]
10 | RUN ["chmod", "u+x,g+x", "/1/opt/docker/bin/db-events-soruce"]
11 |
12 | FROM golang:1.15.3-alpine3.12 as stage1
13 | WORKDIR /build
14 | COPY src/conn-string-parser .
15 | #RUN apk --no-cache add build-base
16 | RUN GOOS=linux CGO_ENABLED=0 go build -a -ldflags '-s -w -extldflags "-static"' -o app .
17 |
18 | FROM openjdk:8 as mainstage
19 | USER root
20 | RUN id -u demiourgos728 1>/dev/null 2>&1 || (( getent group 0 1>/dev/null 2>&1 || ( type groupadd 1>/dev/null 2>&1 && groupadd -g 0 root || addgroup -g 0 -S root )) && ( type useradd 1>/dev/null 2>&1 && useradd --system --create-home --uid 1001 --gid 0 demiourgos728 || adduser -S -u 1001 -G root demiourgos728 ))
21 | WORKDIR /opt/docker
22 | COPY --from=stage0 --chown=demiourgos728:root /1/opt/docker /opt/docker
23 | COPY --from=stage0 --chown=demiourgos728:root /2/opt/docker /opt/docker
24 | COPY --from=stage1 --chown=demiourgos728:root /build/app /usr/local/bin/conn-string-parser
25 | COPY --chown=demiourgos728:root src/main/resources/application.conf /config/application.conf
26 | RUN chmod -R 0777 /opt/docker
27 | USER 1001:0
28 | ENTRYPOINT ["/opt/docker/bin/db-events-soruce"]
29 | CMD []
30 |
--------------------------------------------------------------------------------
/dbevents/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.3.13
2 |
--------------------------------------------------------------------------------
/dbevents/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.7.6")
2 | addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")
3 |
--------------------------------------------------------------------------------
/dbevents/scripts/docker-push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | sbt docker:stage
5 | docker build --no-cache -t spacecloudio/dbevents:0.2.0 .
6 | docker push spacecloudio/dbevents:0.2.0
7 |
--------------------------------------------------------------------------------
/dbevents/src/conn-string-parser/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/spaceuptech/space-cloud/dbevents/conn-string-parser
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/go-sql-driver/mysql v1.5.0
7 | github.com/urfave/cli/v2 v2.2.0
8 | )
9 |
--------------------------------------------------------------------------------
/dbevents/src/conn-string-parser/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
3 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
4 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
5 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
6 | github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
7 | github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
10 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
11 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
12 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
13 | github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
14 | github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
15 | github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
17 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
18 |
--------------------------------------------------------------------------------
/dbevents/src/conn-string-parser/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "os"
8 |
9 | "github.com/urfave/cli/v2"
10 | )
11 |
12 | func main() {
13 | app := &cli.App{
14 | Name: "conn-string-parser",
15 | Version: "0.1.0",
16 | Commands: []*cli.Command{
17 | {
18 | Name: "parse",
19 | Usage: "Parse the db connection string",
20 | Flags: []cli.Flag{
21 | &cli.StringFlag{
22 | Name: "db-type",
23 | Usage: "The `db` the connection string is for",
24 | Value: "none",
25 | },
26 | },
27 | Action: func(c *cli.Context) error {
28 | dbType := c.String("db-type")
29 | if dbType == "none" {
30 | return errors.New("db-type is a required flag")
31 | }
32 | if c.Args().Len() == 0 {
33 | return errors.New("connection string not provided")
34 | }
35 | return parseConnectionString(dbType, c.Args().First())
36 | },
37 | },
38 | },
39 | }
40 |
41 | if err := app.Run(os.Args); err != nil {
42 | jsonString, _ := json.Marshal(map[string]string{"error": err.Error()})
43 | fmt.Println(string(jsonString))
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/dbevents/src/conn-string-parser/parser.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func parseConnectionString(dbType, conn string) error {
8 | switch dbType {
9 | case "mysql":
10 | return parseMySQLConn(conn)
11 | case "postgres":
12 | return parsePostgresConnString(conn)
13 | case "sqlserver":
14 | return parseSQLSeverConnString(conn)
15 | default:
16 | return fmt.Errorf("invalid dbtype (%s) provided", dbType)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/dbevents/src/conn-string-parser/parser_mysql.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/go-sql-driver/mysql"
9 | )
10 |
11 | func parseMySQLConn(conn string) error {
12 | c, err := mysql.ParseDSN(conn)
13 | if err != nil {
14 | return err
15 | }
16 |
17 | arr := strings.Split(c.Addr, ":")
18 | jsonString, _ := json.Marshal(DBConfig{
19 | Host: arr[0],
20 | Port: arr[1],
21 | User: c.User,
22 | Pass: c.Passwd,
23 | SSLMode: getMySQLSSLMode(c),
24 | })
25 |
26 | // Print it out
27 | fmt.Println(string(jsonString))
28 | return nil
29 | }
30 |
31 | func getMySQLSSLMode(c *mysql.Config) string {
32 | switch c.TLSConfig {
33 | case "true":
34 | return "verify-ca"
35 | case "false":
36 | return "disabled"
37 | case "skip-verify":
38 | return "required"
39 | case "preferred":
40 | return "preferred"
41 | }
42 |
43 | return "disabled"
44 | }
45 |
--------------------------------------------------------------------------------
/dbevents/src/conn-string-parser/parser_postgres.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "net"
8 | "net/url"
9 | )
10 |
11 | func parsePostgresConnString(conn string) error {
12 | u, err := url.Parse(conn)
13 | if err != nil {
14 | return err
15 | }
16 |
17 | host, port, err := net.SplitHostPort(u.Host)
18 | if err != nil {
19 | return err
20 | }
21 |
22 | // Throw error if path is empty
23 | if u.Path == "" {
24 | return errors.New("no database provided in conn string")
25 | }
26 |
27 | pass, _ := u.User.Password()
28 | jsonString, _ := json.Marshal(DBConfig{
29 | Host: host,
30 | Port: port,
31 | User: u.User.Username(),
32 | Pass: pass,
33 | DB: u.Path[1:],
34 | SSLMode: getPostgresSSLMode(u.Query()),
35 | })
36 |
37 | // Print it out
38 | fmt.Println(string(jsonString))
39 | return nil
40 | }
41 |
42 | func getPostgresSSLMode(params url.Values) string {
43 | sslMode := params.Get("sslmode")
44 | if sslMode == "" {
45 | return "disable"
46 | }
47 |
48 | return sslMode
49 | }
50 |
--------------------------------------------------------------------------------
/dbevents/src/conn-string-parser/parser_sqlserver.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net"
7 | "net/url"
8 | )
9 |
10 | func parseSQLSeverConnString(conn string) error {
11 | u, err := url.Parse(conn)
12 | if err != nil {
13 | return err
14 | }
15 |
16 | host, port, err := net.SplitHostPort(u.Host)
17 | if err != nil {
18 | return err
19 | }
20 |
21 | pass, _ := u.User.Password()
22 | jsonString, _ := json.Marshal(DBConfig{
23 | Host: host,
24 | Port: port,
25 | User: u.User.Username(),
26 | Pass: pass,
27 | DB: u.Query().Get("database"),
28 | })
29 |
30 | // Print it out
31 | fmt.Println(string(jsonString))
32 | return nil
33 | }
34 |
--------------------------------------------------------------------------------
/dbevents/src/conn-string-parser/types.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type DBConfig struct {
4 | Host string `json:"host"`
5 | Port string `json:"port"`
6 | User string `json:"user"`
7 | Pass string `json:"password"`
8 | DB string `json:"db"`
9 | SSLMode string `json:"sslMode"`
10 | }
11 |
--------------------------------------------------------------------------------
/dbevents/src/main/resources/application.conf:
--------------------------------------------------------------------------------
1 | akka {
2 | log-config-on-start = on
3 | http {
4 | host-connection-pool {
5 | max-connections = 100
6 | max-open-requests = 512
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/dbevents/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | [%date{ISO8601}] [%level] [%logger] [%thread] [%X{akkaSource}] - %msg%n
7 |
8 |
9 |
10 |
11 | 1024
12 | true
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/dbevents/src/main/scala/com/spaceuptech/dbevents/EventsApp.scala:
--------------------------------------------------------------------------------
1 | package com.spaceuptech.dbevents
2 |
3 | import java.io.File
4 |
5 | import akka.actor.typed.ActorSystem
6 | import com.typesafe.config.{Config, ConfigFactory}
7 |
8 | object EventsApp extends App {
9 | // Load env variables
10 | Global.gatewayUrl = scala.util.Properties.envOrElse("GATEWAY_URL", "gateway.space-cloud.svc.cluster.local:4122")
11 | Global.secret = scala.util.Properties.envOrElse("SC_ADMIN_SECRET", "some-secret")
12 | Global.storageType = scala.util.Properties.envOrElse("STORAGE_TYPE", "local")
13 |
14 | val conf = ConfigFactory.load(ConfigFactory.parseFile(new File("/config/application.conf")))
15 | // Create the main actor system
16 | val a = ActorSystem[Nothing](EventsSupervisor(), "db-events", conf)
17 | }
18 |
--------------------------------------------------------------------------------
/dbevents/src/main/scala/com/spaceuptech/dbevents/EventsSupervisor.scala:
--------------------------------------------------------------------------------
1 | package com.spaceuptech.dbevents
2 |
3 | import akka.actor.typed.{Behavior, PostStop, Signal}
4 | import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
5 | import com.spaceuptech.dbevents.spacecloud.ProjectsSupervisor
6 |
7 | // We are creating an object to simply creation of the actor
8 | object EventsSupervisor {
9 | def apply(): Behavior[Nothing] = Behaviors.setup[Nothing](context => new EventsSupervisor(context))
10 | }
11 |
12 | class EventsSupervisor(context: ActorContext[Nothing]) extends AbstractBehavior[Nothing](context) {
13 | println("DB events source app started")
14 |
15 | // Start the projects supervisor
16 | private val projects = context.spawn(ProjectsSupervisor(), "projects")
17 | projects ! ProjectsSupervisor.FetchProjects()
18 |
19 | // No need to handle any messages
20 | override def onMessage(msg: Nothing): Behavior[Nothing] = Behaviors.unhandled
21 |
22 | override def onSignal: PartialFunction[Signal, Behavior[Nothing]] = {
23 | case PostStop =>
24 | projects ! ProjectsSupervisor.Stop()
25 | println("DB events source app stopped")
26 | this
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/dbevents/src/main/scala/com/spaceuptech/dbevents/Global.scala:
--------------------------------------------------------------------------------
1 | package com.spaceuptech.dbevents
2 |
3 | import com.auth0.jwt.JWT
4 | import com.auth0.jwt.algorithms.Algorithm
5 |
6 | object Global {
7 | var secret: String = ""
8 | var gatewayUrl: String = ""
9 | var storageType: String = "local"
10 |
11 | def createAdminToken(): String = {
12 | val alg = Algorithm.HMAC256(secret)
13 | JWT.create()
14 | .withClaim("role", "admin")
15 | .withClaim("id", "debezium")
16 | .sign(alg)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/dbevents/src/main/scala/com/spaceuptech/dbevents/Types.scala:
--------------------------------------------------------------------------------
1 | package com.spaceuptech.dbevents
2 |
3 | case class DatabaseSource(project: String, dbAlias: String, dbType: String, config: Map[String, String])
4 | case class DatabaseSources()
5 |
--------------------------------------------------------------------------------
/dbevents/src/main/scala/com/spaceuptech/dbevents/database/Database.scala:
--------------------------------------------------------------------------------
1 | package com.spaceuptech.dbevents.database
2 |
3 | import akka.actor.typed.{ActorRef, Behavior}
4 | import akka.actor.typed.scaladsl.Behaviors
5 | import com.spaceuptech.dbevents.spacecloud.{DatabaseConfig, EventsSink}
6 |
7 | object Database {
8 | def createActor(projectId: String, dbType: String, actor: ActorRef[EventsSink.Command]): Behavior[Command] = {
9 | dbType match {
10 | case "postgres" | "mysql" | "sqlserver" => Behaviors.withTimers[Command](timers => Behaviors.setup[Command](context => new Debezium(context, timers, projectId, actor)))
11 | case "mongo" => Behaviors.withTimers[Command](timers => Behaviors.setup[Command](context => new Mongo(context, timers, projectId, actor)))
12 | case _ => throw new Exception(s"Invalid db type ($dbType) provided")
13 | }
14 | }
15 |
16 | sealed trait Command
17 | case class ChangeRecord(payload: ChangeRecordPayload, project: String, dbAlias: String, dbType: String) extends Command
18 | case class CheckEngineStatus() extends Command
19 | case class UpdateEngineConfig(config: DatabaseConfig) extends Command
20 | case class ProcessEngineConfig(conn: String, config: DatabaseConfig) extends Command
21 | case class Stop() extends Command
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/dbevents/src/main/scala/com/spaceuptech/dbevents/database/package.scala:
--------------------------------------------------------------------------------
1 | package com.spaceuptech.dbevents
2 |
3 |
4 | import akka.actor.ClassicActorSystemProvider
5 | import com.spaceuptech.dbevents.spacecloud.{Secret, SecretResponse, fetchSpaceCloudResource}
6 | import io.debezium.engine.{ChangeEvent, DebeziumEngine}
7 |
8 | import scala.concurrent.{ExecutionContext, Future}
9 |
10 | package object database {
11 |
12 | case class ChangeRecordPayload(op: String, before: Option[Map[String, Any]], after: Option[Map[String, Any]], source: ChangeRecordPayloadSource)
13 | case class ChangeRecordPayloadSource(name: String, ts_ms: Long, table: String)
14 |
15 | case class DebeziumStatus(error: String, future: java.util.concurrent.Future[_], engine: DebeziumEngine[ChangeEvent[String, String]])
16 | case class MongoStatus(future: java.util.concurrent.Future[_], store: MongoStore)
17 |
18 | def getConnString(projectId: String, conn: String)(implicit system: ClassicActorSystemProvider, executor: ExecutionContext): Future[String] = {
19 | if (!conn.startsWith("secrets")) {
20 | return Future { conn }
21 | }
22 |
23 | val secret = conn.split('.')(1)
24 | fetchSpaceCloudResource[SecretResponse](s"http://${Global.gatewayUrl}/v1/runner/$projectId/secrets?id=$secret").flatMap {
25 | secretResponse =>
26 | if (secretResponse.error.isDefined) return Future.failed(new Exception(s"Error received while fetching secret - ${secretResponse.error.get}"))
27 | secretResponse.result(0).data.get("CONN") match {
28 | case Some(conn) => Future{conn}
29 | case _ => Future.failed(new Exception("Secret does not have a valid resonse"))
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/dbevents/src/main/scala/com/spaceuptech/dbevents/spacecloud/Routes.scala:
--------------------------------------------------------------------------------
1 | package com.spaceuptech.dbevents.spacecloud
2 |
3 | import akka.actor.typed.{ActorRef, ActorSystem}
4 | import akka.http.scaladsl.model._
5 | import akka.http.scaladsl.server.Directives._
6 | import akka.http.scaladsl.server.Route
7 | import akka.util.Timeout
8 |
9 | import scala.concurrent.duration.DurationInt
10 |
11 | class Routes(projects: ActorRef[ProjectsSupervisor.Command])(implicit system: ActorSystem[_]) {
12 | implicit val timeout: Timeout = 3.seconds
13 |
14 | lazy val routes: Route =
15 | path("fetch-projects") {
16 | post {
17 | projects ! ProjectsSupervisor.FetchProjects()
18 | complete(HttpEntity(ContentTypes.`application/json`, "{}"))
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/basic-todo-app/config.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | projects:
3 | - id: basic-todo-app
4 | secret: some-secret
5 | modules:
6 | crud:
7 | mongo:
8 | enabled: true
9 | conn: mongodb://localhost:27017
10 | collections:
11 | todos:
12 | rules:
13 | read:
14 | rule: allow
15 | create:
16 | rule: allow
17 | update:
18 | rule: allow
19 | delete:
20 | rule: allow
21 | isRealtimeEnabled: false
22 | users:
23 | rules:
24 | read:
25 | rule: allow
26 | create:
27 | rule: allow
28 | update:
29 | rule: allow
30 | delete:
31 | rule: allow
32 | isRealtimeEnabled: false
33 | auth:
34 | email:
35 | enabled: true
36 |
--------------------------------------------------------------------------------
/examples/realtime-monitoring-app/demo.css:
--------------------------------------------------------------------------------
1 | .hide {
2 | display: none;
3 | }
--------------------------------------------------------------------------------
/examples/realtime-monitoring-app/device1.py:
--------------------------------------------------------------------------------
1 | import time
2 | import random
3 | import jwt
4 | from space_api import API
5 |
6 | api = API('demo', 'localhost:4124')
7 | SECRET = 'my_secret'
8 | api.set_token(jwt.encode({"password": "super_secret_password"}, SECRET, algorithm='HS256').decode('utf-8'))
9 | db = api.my_sql()
10 |
11 | for i in range(10):
12 | response = db.insert('demo').doc({"id": i, "device": 1, "value": random.randint(1, 10)}).apply()
13 | if response.status == 200:
14 | print("Sent")
15 | else:
16 | print(response.error)
17 | time.sleep(2)
18 |
--------------------------------------------------------------------------------
/examples/realtime-monitoring-app/device2.py:
--------------------------------------------------------------------------------
1 | import time
2 | import random
3 | import jwt
4 | from space_api import API
5 |
6 | api = API('demo', 'localhost:4124')
7 | SECRET = 'my_secret'
8 | api.set_token(jwt.encode({"password": "super_secret_password"}, SECRET, algorithm='HS256').decode('utf-8'))
9 | db = api.my_sql()
10 |
11 | for i in range(10):
12 | response = db.insert('demo').doc({"id": i+100, "device": 2, "value": random.randint(11, 20)}).apply()
13 | if response.status == 200:
14 | print("Sent")
15 | else:
16 | print(response.error)
17 | time.sleep(2)
18 |
--------------------------------------------------------------------------------
/examples/realtime-monitoring-app/queries.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE demo (
2 | id INTEGER NOT NULL PRIMARY KEY,
3 | device INTEGER,
4 | value INTEGER
5 | );
6 |
7 | CREATE TABLE demo_users (
8 | id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
9 | username VARCHAR(255),
10 | password VARCHAR(255)
11 | );
12 |
--------------------------------------------------------------------------------
/examples/realtime-monitoring-app/service.py:
--------------------------------------------------------------------------------
1 | import jwt
2 | from space_api import API, COND
3 |
4 | api = API('demo', 'localhost:4124')
5 | SECRET = 'my_secret'
6 | api.set_token(jwt.encode({"password": "super_secret_admin_password"}, SECRET, algorithm='HS256').decode('utf-8'))
7 | db = api.my_sql()
8 |
9 | service = api.service('login_service')
10 |
11 |
12 | def login(params, auth, cb):
13 | response = db.get_one('demo_users').where(COND("username", "==", params["username"])).apply()
14 | if response.status == 200:
15 | res = response.result
16 | if res["username"] == params["username"] and res["password"] == params["password"]:
17 | cb('response',
18 | {'ack': True,
19 | 'token': jwt.encode({"username": res["username"], "password": res["password"]}, SECRET,
20 | algorithm='HS256').decode('utf-8')})
21 | else:
22 | cb('response', {'ack': False})
23 | else:
24 | print(response.error)
25 | cb('response', {'ack': False})
26 |
27 |
28 | def register(params, auth, cb):
29 | response = db.insert('demo_users').doc(
30 | {"username": params["username"], "password": params["password"]}).apply()
31 | if response.status != 200:
32 | print(response.error)
33 | cb('response', {'ack': response.status == 200})
34 |
35 |
36 | service.register_func('login_func', login)
37 | service.register_func('register_func', register)
38 |
39 | service.start()
40 | api.close()
41 |
--------------------------------------------------------------------------------
/gateway/.dockerignore:
--------------------------------------------------------------------------------
1 | *.zip
2 | gateway
3 |
--------------------------------------------------------------------------------
/gateway/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.15.3-alpine3.12
2 | WORKDIR /build
3 |
4 | # Take the current space cloud version as a argument
5 | ARG SC_VERSION=0.21.5
6 |
7 | # Copy all the source files
8 | COPY . .
9 | # Install the required packages
10 | RUN apk --no-cache add ca-certificates wget unzip
11 |
12 | # Build SC
13 | RUN GOOS=linux CGO_ENABLED=0 go build -a -ldflags '-s -w -extldflags "-static"' -o app .
14 |
15 | # Download mission control
16 | RUN echo $SC_VERSION && wget https://storage.googleapis.com/space-cloud/mission-control/mission-control-v$SC_VERSION.zip && unzip mission-control-v$SC_VERSION.zip
17 |
18 | FROM alpine:3.12
19 | ARG SC_VERSION=0.21.5
20 |
21 | RUN apk --no-cache add ca-certificates
22 |
23 | WORKDIR /app
24 | COPY --from=0 /build/build /root/.space-cloud/mission-control-v$SC_VERSION/build
25 | COPY --from=0 /build/app .
26 |
27 | CMD ["./app", "run"]
28 |
--------------------------------------------------------------------------------
/gateway/config/generate.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | // GenerateEmptyConfig creates an empty config file
4 | func GenerateEmptyConfig() *Config {
5 | return &Config{
6 | Projects: make(Projects),
7 | SSL: &SSL{Enabled: false},
8 | ClusterConfig: new(ClusterConfig),
9 | Integrations: make(Integrations),
10 | IntegrationHooks: make(IntegrationHooks),
11 | CacheConfig: new(CacheConfig),
12 | }
13 | }
14 |
15 | // GenerateEmptyProject creates a empty project
16 | func GenerateEmptyProject(project *ProjectConfig) *Project {
17 | return &Project{
18 | ProjectConfig: project,
19 | DatabaseConfigs: make(map[string]*DatabaseConfig),
20 | DatabaseSchemas: make(map[string]*DatabaseSchema),
21 | DatabaseRules: make(map[string]*DatabaseRule),
22 | DatabasePreparedQueries: make(map[string]*DatbasePreparedQuery),
23 | EventingConfig: new(EventingConfig),
24 | EventingSchemas: make(map[string]*EventingSchema),
25 | EventingRules: make(map[string]*Rule),
26 | EventingTriggers: make(map[string]*EventingTrigger),
27 | FileStoreConfig: new(FileStoreConfig),
28 | FileStoreRules: FileStoreRules{},
29 | Auths: make(Auths),
30 | LetsEncrypt: new(LetsEncrypt),
31 | IngressRoutes: make(IngressRoutes),
32 | IngressGlobal: new(GlobalRoutesConfig),
33 | RemoteService: make(Services),
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/gateway/config/load.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | "os"
7 | "strings"
8 |
9 | "github.com/ghodss/yaml"
10 | )
11 |
12 | func loadEnvironmentVariable(c *Config) {
13 | for _, p := range c.Projects {
14 | for i, secret := range p.ProjectConfig.Secrets {
15 | if strings.HasPrefix(secret.Secret, "$") {
16 | tempString := strings.TrimPrefix(secret.Secret, "$")
17 | tempEnvVar, present := os.LookupEnv(tempString)
18 |
19 | if present {
20 | p.ProjectConfig.Secrets[i].Secret = tempEnvVar
21 | }
22 | }
23 | }
24 | for _, value := range p.DatabaseConfigs {
25 | if strings.HasPrefix(value.Conn, "$") {
26 | tempStringC := strings.TrimPrefix(value.Conn, "$")
27 | tempEnvVarC, presentC := os.LookupEnv(tempStringC)
28 |
29 | if presentC {
30 | value.Conn = tempEnvVarC
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
37 | // LoadConfigFromFile loads the config from the provided file path
38 | func LoadConfigFromFile(path string) (*Config, error) {
39 | // Load the file in memory
40 | dat, err := ioutil.ReadFile(path)
41 | if err != nil {
42 | return nil, err
43 | }
44 |
45 | // Marshal the configuration
46 | conf := new(Config)
47 | if strings.HasSuffix(path, "json") {
48 | err = json.Unmarshal(dat, conf)
49 | } else {
50 | err = yaml.Unmarshal(dat, conf)
51 | }
52 | if err != nil {
53 | return nil, err
54 | }
55 |
56 | loadEnvironmentVariable(conf)
57 | return conf, nil
58 | }
59 |
--------------------------------------------------------------------------------
/gateway/config/store.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "io/ioutil"
8 | "strings"
9 |
10 | "github.com/ghodss/yaml"
11 | "github.com/spaceuptech/helpers"
12 | )
13 |
14 | // StoreConfigToFile stores the config file to disk
15 | func StoreConfigToFile(conf *Config, path string) error {
16 | var data []byte
17 | var err error
18 |
19 | if strings.HasSuffix(path, ".yaml") {
20 | data, err = yaml.Marshal(conf)
21 | } else if strings.HasSuffix(path, ".json") {
22 | data, err = json.Marshal(conf)
23 | } else {
24 | return helpers.Logger.LogError(helpers.GetRequestID(context.TODO()), fmt.Sprintf("Invalid config file type (%s) provided", path), nil, nil)
25 | }
26 |
27 | // Check if error occured while marshaling
28 | if err != nil {
29 | return err
30 | }
31 |
32 | return ioutil.WriteFile(path, data, 0644)
33 | }
34 |
--------------------------------------------------------------------------------
/gateway/managers/admin/integration.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | // GetIntegrationToken returns the admin token required by an intergation
4 | func (m *Manager) GetIntegrationToken(id string) (string, error) {
5 | m.lock.RLock()
6 | defer m.lock.RUnlock()
7 |
8 | return m.createToken(map[string]interface{}{"id": id, "role": "integration"})
9 | }
10 |
--------------------------------------------------------------------------------
/gateway/managers/admin/login.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/helpers"
8 | "github.com/spaceuptech/space-cloud/gateway/model"
9 | )
10 |
11 | // Login handles the admin login operation
12 | func (m *Manager) Login(ctx context.Context, user, pass string) (int, string, error) {
13 | m.lock.RLock()
14 | defer m.lock.RUnlock()
15 |
16 | // Prepare the request params object
17 | params := model.RequestParams{
18 | Payload: map[string]string{"user": user, "key": pass},
19 | Method: http.MethodPost,
20 | Resource: "admin-login",
21 | Op: "access",
22 | }
23 |
24 | // Invoke integration hooks
25 | hookResponse := m.integrationMan.InvokeHook(ctx, params)
26 | if hookResponse.CheckResponse() {
27 | // Check if an error occurred
28 | if err := hookResponse.Error(); err != nil {
29 | return hookResponse.Status(), "", err
30 | }
31 |
32 | // Check the status code first. Send error for non 200 status code
33 | res := hookResponse.Result().(map[string]interface{})
34 |
35 | // Return the token
36 | return http.StatusOK, res["token"].(string), nil
37 | }
38 |
39 | if m.user.User == user && m.user.Pass == pass {
40 | token, err := m.createToken(map[string]interface{}{"id": user, "role": "admin"})
41 | if err != nil {
42 | return http.StatusInternalServerError, "", err
43 | }
44 | return http.StatusOK, token, nil
45 | }
46 |
47 | return http.StatusUnauthorized, "", helpers.Logger.LogError(helpers.GetRequestID(ctx), "Invalid username or password provided", nil, map[string]interface{}{"user": user})
48 | }
49 |
--------------------------------------------------------------------------------
/gateway/managers/admin/service.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/helpers"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/model"
9 | )
10 |
11 | // SetServices sets services in admin
12 | func (m *Manager) SetServices(eventType string, services model.ScServices) {
13 | m.lock.Lock()
14 | defer m.lock.Unlock()
15 | helpers.Logger.LogDebug(helpers.GetRequestID(context.TODO()), "Setting services in admin", map[string]interface{}{"eventType": eventType, "services": services})
16 |
17 | m.services = services
18 | }
19 |
--------------------------------------------------------------------------------
/gateway/managers/admin/types.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/config"
7 | "github.com/spaceuptech/space-cloud/gateway/model"
8 | )
9 |
10 | // IntegrationInterface s used to describe the features of integration manager we need.
11 | type IntegrationInterface interface {
12 | HandleConfigAuth(ctx context.Context, resource, op string, claims map[string]interface{}, attr map[string]string) config.IntegrationAuthResponse
13 | InvokeHook(ctx context.Context, params model.RequestParams) config.IntegrationAuthResponse
14 | }
15 |
--------------------------------------------------------------------------------
/gateway/managers/admin/types_test.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/stretchr/testify/mock"
8 |
9 | "github.com/spaceuptech/space-cloud/gateway/config"
10 | "github.com/spaceuptech/space-cloud/gateway/model"
11 | )
12 |
13 | type mockIntegrationManager struct {
14 | mock.Mock
15 | }
16 |
17 | func (m *mockIntegrationManager) HandleConfigAuth(_ context.Context, resource, op string, claims map[string]interface{}, attr map[string]string) config.IntegrationAuthResponse {
18 | return m.Called(resource, op, claims, attr).Get(0).(config.IntegrationAuthResponse)
19 | }
20 |
21 | func (m *mockIntegrationManager) InvokeHook(ctx context.Context, params model.RequestParams) config.IntegrationAuthResponse {
22 | return m.Called(params).Get(0).(config.IntegrationAuthResponse)
23 | }
24 |
25 | type mockIntegrationResponse struct {
26 | checkResponse bool
27 | err string
28 | result interface{}
29 | status int
30 | }
31 |
32 | func (m mockIntegrationResponse) CheckResponse() bool {
33 | return m.checkResponse
34 | }
35 |
36 | func (m mockIntegrationResponse) Result() interface{} {
37 | return m.result
38 | }
39 |
40 | func (m mockIntegrationResponse) Status() int {
41 | return m.status
42 | }
43 |
44 | func (m mockIntegrationResponse) Error() error {
45 | if m.err == "" {
46 | return nil
47 | }
48 | return errors.New(m.err)
49 | }
50 |
--------------------------------------------------------------------------------
/gateway/managers/integration/config.go:
--------------------------------------------------------------------------------
1 | package integration
2 |
3 | import (
4 | "github.com/spaceuptech/space-cloud/gateway/config"
5 | )
6 |
7 | // SetConfig sets the config of the integration manager
8 | func (m *Manager) SetConfig(integrations config.Integrations, integrationHooks config.IntegrationHooks) error {
9 | if err := m.SetIntegrations(integrations); err != nil {
10 | return err
11 | }
12 |
13 | m.SetIntegrationHooks(integrationHooks)
14 | return nil
15 | }
16 |
17 | // SetIntegrations sets integtaion config
18 | func (m *Manager) SetIntegrations(integrations config.Integrations) error {
19 | m.lock.Lock()
20 | defer m.lock.Unlock()
21 |
22 | // Check if integration are valid
23 | // if err := m.adminMan.ValidateIntegrationSyncOperation(integrations); err != nil {
24 | // m.integrationConfig = map[string]*config.IntegrationConfig{}
25 | // return err
26 | // }
27 |
28 | m.integrationConfig = integrations
29 | return nil
30 | }
31 |
32 | // SetIntegrationHooks set integration hooks
33 | func (m *Manager) SetIntegrationHooks(integrationHooks config.IntegrationHooks) {
34 | m.lock.Lock()
35 | defer m.lock.Unlock()
36 | m.integrationHookConfig = integrationHooks
37 | }
38 |
--------------------------------------------------------------------------------
/gateway/managers/integration/http.go:
--------------------------------------------------------------------------------
1 | package integration
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "net/http"
8 |
9 | "github.com/spaceuptech/space-cloud/gateway/model"
10 | "github.com/spaceuptech/space-cloud/gateway/utils"
11 | )
12 |
13 | func invokeHook(ctx context.Context, url, scToken string, params model.RequestParams, ptr interface{}) (int, error) {
14 | data, err := json.Marshal(params)
15 | if err != nil {
16 | return 0, err
17 | }
18 |
19 | req := &utils.HTTPRequest{URL: url, Params: bytes.NewBuffer(data), Method: http.MethodPost, SCToken: scToken}
20 | return utils.MakeHTTPRequest(ctx, req, ptr)
21 | }
22 |
--------------------------------------------------------------------------------
/gateway/managers/integration/integration.go:
--------------------------------------------------------------------------------
1 | package integration
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/config"
7 | )
8 |
9 | // Manager is responsible for handling all integration related tasks
10 | type Manager struct {
11 | lock sync.RWMutex
12 |
13 | adminMan adminManager
14 |
15 | integrationConfig config.Integrations
16 | integrationHookConfig config.IntegrationHooks
17 | }
18 |
19 | // New creates a new instance of the integration module
20 | func New(adminMan adminManager) *Manager {
21 | return &Manager{adminMan: adminMan, integrationConfig: make(config.Integrations), integrationHookConfig: make(config.IntegrationHooks)}
22 | }
23 |
--------------------------------------------------------------------------------
/gateway/managers/integration/operations.go:
--------------------------------------------------------------------------------
1 | package integration
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/config"
7 | "github.com/spaceuptech/space-cloud/gateway/model"
8 | )
9 |
10 | // HandleConfigAuth handles the authentication of the config requests
11 | func (m *Manager) HandleConfigAuth(ctx context.Context, resource, op string, claims map[string]interface{}, attr map[string]string) config.IntegrationAuthResponse {
12 | m.lock.RLock()
13 | defer m.lock.RUnlock()
14 |
15 | res := authResponse{checkResponse: false, err: nil}
16 |
17 | // Return if the request is not made by an integration
18 | if !isIntegrationRequest(claims) {
19 | return res
20 | }
21 |
22 | // Set the value of the result
23 | res.checkResponse = true
24 | res.err = m.checkPermissions(ctx, "config", resource, op, claims, attr)
25 | return res
26 | }
27 |
28 | // InvokeHook invokes all the hooks registered for the given request
29 | func (m *Manager) InvokeHook(ctx context.Context, params model.RequestParams) config.IntegrationAuthResponse {
30 | m.lock.RLock()
31 | defer m.lock.RUnlock()
32 |
33 | // Don't invoke hook if request is internal
34 | if role, p := params.Claims["role"]; p && role == "sc-internal" {
35 | return authResponse{}
36 | }
37 |
38 | return m.invokeHooks(ctx, params)
39 | }
40 |
--------------------------------------------------------------------------------
/gateway/managers/integration/types.go:
--------------------------------------------------------------------------------
1 | package integration
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | type adminManager interface {
8 | GetInternalAccessToken() (string, error)
9 | }
10 |
11 | type authResponse struct {
12 | checkResponse bool
13 | err error
14 | result interface{}
15 | status int
16 |
17 | integration, hook string
18 | }
19 |
20 | // CheckResponse indicates whether the integration is hijacking the authentication of the request or not.
21 | // Its a humble way of saying that I'm the boss for this request
22 | func (r authResponse) CheckResponse() bool {
23 | return r.checkResponse
24 | }
25 |
26 | // Error returns error generated by the module if CheckResponse() returns true.
27 | func (r authResponse) Error() error {
28 | return r.err
29 | }
30 |
31 | // Status returns the status code of the hook
32 | func (r authResponse) Status() int {
33 | if r.status == 0 {
34 | return http.StatusServiceUnavailable
35 | }
36 |
37 | return r.status
38 | }
39 |
40 | // Result returns the value received from the integration
41 | func (r authResponse) Result() interface{} {
42 | return r.result
43 | }
44 |
--------------------------------------------------------------------------------
/gateway/managers/managers.go:
--------------------------------------------------------------------------------
1 | package managers
2 |
3 | import (
4 | "github.com/spaceuptech/space-cloud/gateway/config"
5 | "github.com/spaceuptech/space-cloud/gateway/managers/admin"
6 | "github.com/spaceuptech/space-cloud/gateway/managers/integration"
7 | "github.com/spaceuptech/space-cloud/gateway/managers/syncman"
8 | )
9 |
10 | // Managers holds all the managers
11 | type Managers struct {
12 | adminMan *admin.Manager
13 | syncMan *syncman.Manager
14 | integrationMan *integration.Manager
15 | }
16 |
17 | // New creates a new managers instance
18 | func New(nodeID, clusterID, storeType, runnerAddr string, isDev bool, adminUserInfo *config.AdminUser, ssl *config.SSL) (*Managers, error) {
19 | // Create the fundamental modules
20 | adminMan := admin.New(nodeID, clusterID, isDev, adminUserInfo)
21 | i := integration.New(adminMan)
22 | syncMan, err := syncman.New(nodeID, clusterID, storeType, runnerAddr, adminMan, i, ssl)
23 | if err != nil {
24 | return nil, err
25 | }
26 | adminMan.SetSyncMan(syncMan)
27 | adminMan.SetIntegrationMan(i)
28 |
29 | return &Managers{adminMan: adminMan, syncMan: syncMan, integrationMan: i}, nil
30 | }
31 |
32 | // Admin returns the admin manager
33 | func (m *Managers) Admin() *admin.Manager {
34 | return m.adminMan
35 | }
36 |
37 | // Sync returns the sync manager
38 | func (m *Managers) Sync() *syncman.Manager {
39 | return m.syncMan
40 | }
41 |
42 | // Integration returns the integration manager
43 | func (m *Managers) Integration() *integration.Manager {
44 | return m.integrationMan
45 | }
46 |
--------------------------------------------------------------------------------
/gateway/managers/syncman/store.go:
--------------------------------------------------------------------------------
1 | package syncman
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/config"
7 | "github.com/spaceuptech/space-cloud/gateway/model"
8 | )
9 |
10 | // Store abstracts the implementation of letsencrypt storage operations
11 | type Store interface {
12 | WatchServices(cb func(eventType string, serviceID string, projects model.ScServices)) error
13 | WatchResources(cb func(eventType, resourceId string, resourceType config.Resource, resource interface{})) error
14 |
15 | Register()
16 |
17 | SetResource(ctx context.Context, resourceID string, resource interface{}) error
18 | DeleteResource(ctx context.Context, resourceID string) error
19 |
20 | // This function should only be used by delete project endpoint
21 | DeleteProject(ctx context.Context, projectID string) error
22 |
23 | GetGlobalConfig() (*config.Config, error)
24 | }
25 |
--------------------------------------------------------------------------------
/gateway/model/cache.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/config"
7 | )
8 |
9 | // CachePurgeRequest describes the payload for cache purge request
10 | type CachePurgeRequest struct {
11 | Resource config.Resource `json:"resource,omitempty"`
12 | DbAlias string `json:"dbAlias,omitempty"`
13 | ServiceID string `json:"serviceId,omitempty"`
14 | ID string `json:"id,omitempty"`
15 | }
16 |
17 | // CacheIngressRoute corresponds to a value of ingress route key
18 | type CacheIngressRoute struct {
19 | Body []byte `json:"body"`
20 | Headers http.Header `json:"headers"`
21 | }
22 |
23 | // CacheDatabaseResult is used to store cached database result
24 | type CacheDatabaseResult struct {
25 | Result interface{} `json:"result"`
26 | MetricCount int64 `json:"metricCount"`
27 | }
28 |
--------------------------------------------------------------------------------
/gateway/model/functions.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "github.com/spaceuptech/space-cloud/gateway/config"
4 |
5 | // FunctionsRequest is the api call request
6 | type FunctionsRequest struct {
7 | Params interface{} `json:"params"`
8 | Timeout int `json:"timeout"`
9 | Cache *config.ReadCacheOptions `json:"cache"`
10 | }
11 |
--------------------------------------------------------------------------------
/gateway/model/graphql.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "encoding/json"
4 |
5 | // GraphQLRequest is the payload received in a graphql request
6 | type GraphQLRequest struct {
7 | Query string `json:"query"`
8 | OperationName string `json:"operationName"`
9 | Variables map[string]interface{} `json:"variables"`
10 | }
11 |
12 | // ReadRequestKey is the key type for the dataloader
13 | type ReadRequestKey struct {
14 | DBAlias string
15 | Col string
16 | DBType string
17 | HasOptions bool
18 | Req ReadRequest
19 | ReqParams RequestParams
20 | }
21 |
22 | // String returns a guaranteed unique string that can be used to identify an object
23 | func (key ReadRequestKey) String() string {
24 | data, _ := json.Marshal(key)
25 | return string(data)
26 | }
27 |
28 | // Raw returns the raw, underlaying value of the key
29 | func (key ReadRequestKey) Raw() interface{} {
30 | return key
31 | }
32 |
33 | // GraphQLCallback is used as a callback for graphql requests
34 | type GraphQLCallback func(op interface{}, err error)
35 |
--------------------------------------------------------------------------------
/gateway/model/metric.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // OperationType is the type of operation being performed on the database
4 | type OperationType string
5 |
6 | const (
7 | // Create is the type used for insert operations
8 | Create OperationType = "create"
9 |
10 | // Read is the type used for query operation
11 | Read OperationType = "read"
12 |
13 | // List is the type used for file store list operation
14 | List OperationType = "list"
15 |
16 | // Update is the type used ofr update operations
17 | Update OperationType = "update"
18 |
19 | // Delete is the type used for delete operations
20 | Delete OperationType = "delete"
21 |
22 | // Batch is the type used for batch operations
23 | Batch OperationType = "batch"
24 |
25 | // Aggregation is the type used for aggregations
26 | Aggregation OperationType = "aggr"
27 | )
28 |
--------------------------------------------------------------------------------
/gateway/model/pubsub.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/mitchellh/mapstructure"
7 | )
8 |
9 | // PubSubMessage describes the format of pubsub send message
10 | type PubSubMessage struct {
11 | ReplyTo string `json:"replyTo"`
12 | Payload interface{} `json:"payload"`
13 | }
14 |
15 | // Unmarshal parses the payload into the object provided
16 | func (m *PubSubMessage) Unmarshal(ptr interface{}) error {
17 | if m.Payload == nil {
18 | return errors.New("no payload has been provided")
19 | }
20 |
21 | return mapstructure.Decode(m.Payload, ptr)
22 | }
23 |
--------------------------------------------------------------------------------
/gateway/model/request.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | // RequestParams describes the params passed down in every request
8 | type RequestParams struct {
9 | RequestID string `json:"requestId"`
10 | Resource string `json:"resource"`
11 | Op string `json:"op"`
12 | Attributes map[string]string `json:"attributes"`
13 | Headers http.Header `json:"headers"`
14 | Claims map[string]interface{} `json:"claims"`
15 | Method string `json:"method"`
16 | Path string `json:"path"`
17 | Payload interface{} `json:"payload"`
18 | }
19 |
20 | // SpecObject describes the basic structure of config specifications
21 | type SpecObject struct {
22 | API string `json:"api" yaml:"api"`
23 | Type string `json:"type" yaml:"type"`
24 | Meta map[string]string `json:"meta" yaml:"meta"`
25 | Spec interface{} `json:"spec" yaml:"spec,omitempty"`
26 | }
27 |
28 | // BatchSpecApplyRequest body of batch config apply endpoint
29 | type BatchSpecApplyRequest struct {
30 | Specs []*SpecObject `json:"specs" yaml:"specs"`
31 | }
32 |
33 | // LicenseUpgradeRequest is the body of license upgrade request
34 | type LicenseUpgradeRequest struct {
35 | LicenseKey string `json:"licenseKey" mapstructure:"licenseKey"`
36 | LicenseValue string `json:"licenseValue" mapstructure:"licenseValue"`
37 | ClusterName string `json:"clusterName" mapstructure:"clusterName"`
38 | }
39 |
--------------------------------------------------------------------------------
/gateway/model/validate.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // RegisterRequest is the struct which carries the space cloud register payload
4 | type RegisterRequest struct {
5 | ID string `json:"id"` // This is the space cloud id
6 | Key string `json:"key"`
7 | UserID string `json:"userId"`
8 | Mode int `json:"mode"`
9 | }
10 |
11 | // RegisterResponse is the response to the register request
12 | type RegisterResponse struct {
13 | Ack bool `json:"ack"`
14 | Error string `json:"error"`
15 | }
16 |
--------------------------------------------------------------------------------
/gateway/modules/auth/errors.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 |
8 | "github.com/spaceuptech/helpers"
9 |
10 | "github.com/spaceuptech/space-cloud/gateway/config"
11 | )
12 |
13 | // ErrRuleNotFound is thrown when an error is not present in the auth object
14 | var ErrRuleNotFound = errors.New("auth: No rule has been provided")
15 |
16 | // ErrIncorrectRuleFieldType is thrown when the field type of a rule is of incorrect type
17 | var ErrIncorrectRuleFieldType = errors.New("auth: Incorrect rule field type")
18 |
19 | // ErrIncorrectMatch is thrown when the field type of a rule is of incorrect type
20 | var ErrIncorrectMatch = errors.New("auth: The two fields do not match")
21 |
22 | // FormatError check whether error is provided in config.Rule
23 | func formatError(ctx context.Context, rule *config.Rule, err error) error {
24 | if err == nil {
25 | return nil
26 | }
27 |
28 | name := rule.Name
29 | if name == "" {
30 | name = "with no name"
31 | }
32 |
33 | _ = helpers.Logger.LogError(helpers.GetRequestID(ctx), fmt.Sprintf("Rule (%s) of type (%s) failed", name, rule.Rule), err, map[string]interface{}{})
34 |
35 | if rule.Error == "" {
36 | return err
37 | }
38 | return errors.New(rule.Error)
39 | }
40 |
--------------------------------------------------------------------------------
/gateway/modules/auth/handle.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/config"
7 | "github.com/spaceuptech/space-cloud/gateway/model"
8 | )
9 |
10 | // AuthorizeRequest authorizes a request using the rule provided
11 | func (m *Module) AuthorizeRequest(ctx context.Context, rule *config.Rule, project, token string, args map[string]interface{}) (map[string]interface{}, error) {
12 | m.RLock()
13 | defer m.RUnlock()
14 |
15 | // Return if rule is allow
16 | if rule.Rule == "allow" {
17 | return map[string]interface{}{}, nil
18 | }
19 |
20 | // Parse token
21 | auth, err := m.jwt.ParseToken(ctx, token)
22 | if err != nil {
23 | return nil, err
24 | }
25 |
26 | args["auth"] = auth
27 | args["token"] = token
28 | if _, err := m.matchRule(ctx, project, rule, map[string]interface{}{"args": args}, auth, model.ReturnWhereStub{}); err != nil {
29 | return nil, err
30 | }
31 |
32 | return auth, err
33 | }
34 |
--------------------------------------------------------------------------------
/gateway/modules/auth/helpers/helpers.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import (
4 | "crypto/aes"
5 | "crypto/cipher"
6 | )
7 |
8 | // DecryptAESCFB decrypts aes cfb string
9 | func DecryptAESCFB(dst, src, key, iv []byte) error {
10 | aesBlockDecrypter, err := aes.NewCipher([]byte(key))
11 | if err != nil {
12 | return err
13 | }
14 | aesDecrypter := cipher.NewCFBDecrypter(aesBlockDecrypter, iv)
15 | aesDecrypter.XORKeyStream(dst, src)
16 | return nil
17 | }
18 |
--------------------------------------------------------------------------------
/gateway/modules/auth/helpers/helpers_test.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import (
4 | "crypto/aes"
5 | "encoding/base64"
6 | "reflect"
7 | "testing"
8 | )
9 |
10 | func base64DecodeString(key string) []byte {
11 | decodedKey, _ := base64.StdEncoding.DecodeString(key)
12 | return decodedKey
13 | }
14 |
15 | func Test_decryptAESCFB(t *testing.T) {
16 |
17 | type args struct {
18 | dst []byte
19 | src []byte
20 | key []byte
21 | iv []byte
22 | }
23 | tests := []struct {
24 | name string
25 | args args
26 | wantErr bool
27 | }{
28 | {
29 | name: "invalid key",
30 | args: args{dst: make([]byte, len("username1")), src: []byte("username1"), key: []byte("invalidKey"), iv: []byte("invalidKey123456")[:aes.BlockSize]},
31 | wantErr: true,
32 | },
33 | {
34 | name: "decryption takes place",
35 | args: args{dst: make([]byte, len("username1")), src: []byte{5, 120, 168, 68, 222, 6, 202, 246, 108}, key: base64DecodeString("Olw6AhA/GzSxfhwKLxO7JJsUL6VUwwGEFTgxzoZPy9g="), iv: base64DecodeString("Olw6AhA/GzSxfhwKLxO7JJsUL6VUwwGEFTgxzoZPy9g=")[:aes.BlockSize]},
36 | },
37 | }
38 | for _, tt := range tests {
39 | t.Run(tt.name, func(t *testing.T) {
40 | if err := DecryptAESCFB(tt.args.dst, tt.args.src, tt.args.key, tt.args.iv); (err != nil) != tt.wantErr {
41 | t.Errorf("decryptAESCFB() error = %v, wantErr %v", err, tt.wantErr)
42 | }
43 | if !tt.wantErr && reflect.DeepEqual(tt.args.dst, tt.args.src) {
44 | t.Errorf("decryptAESCFB() decryption did not take place")
45 | }
46 | })
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/gateway/modules/auth/integration.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/utils"
7 | )
8 |
9 | // GetIntegrationToken returns a token for the integration module
10 | func (m *Module) GetIntegrationToken(ctx context.Context, id string) (string, error) {
11 | return m.CreateToken(ctx, map[string]interface{}{"id": id, "role": "integration"})
12 | }
13 |
14 | // GetMissionControlToken returns a token to be used by mission control
15 | func (m *Module) GetMissionControlToken(ctx context.Context, claims map[string]interface{}) (string, error) {
16 | return m.CreateToken(context.Background(), map[string]interface{}{"id": utils.InternalUserID, "claims": claims})
17 | }
18 |
--------------------------------------------------------------------------------
/gateway/modules/auth/model.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import "errors"
4 |
5 | // TokenClaims holds the JWT token claims
6 | type TokenClaims map[string]interface{}
7 |
8 | // GetRole returns the role present in the token claims
9 | func (c TokenClaims) GetRole() (string, error) {
10 | roleTemp, p := c["role"]
11 | if !p {
12 | return "", errors.New("role is not present in the token claims")
13 | }
14 |
15 | role, ok := roleTemp.(string)
16 | if !ok {
17 | return "", errors.New("role is not of the correct type")
18 | }
19 |
20 | return role, nil
21 | }
22 |
--------------------------------------------------------------------------------
/gateway/modules/auth/operations.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/utils"
7 | )
8 |
9 | // Encrypt encrypts a value if the aes key present in the config. The result is base64 encoded
10 | // before being returned.
11 | func (m *Module) Encrypt(value string) (string, error) {
12 | m.RLock()
13 | defer m.RUnlock()
14 |
15 | return utils.Encrypt(m.aesKey, value)
16 | }
17 |
18 | // ParseToken simply parses and returns the claims of a provided token
19 | func (m *Module) ParseToken(ctx context.Context, token string) (map[string]interface{}, error) {
20 | return m.jwt.ParseToken(ctx, token)
21 | }
22 |
23 | // GetAESKey gets aes key
24 | func (m *Module) GetAESKey() []byte {
25 | m.RLock()
26 | defer m.RUnlock()
27 | return m.aesKey
28 | }
29 |
--------------------------------------------------------------------------------
/gateway/modules/auth/sort.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "sort"
5 | "strings"
6 |
7 | "github.com/spaceuptech/space-cloud/gateway/config"
8 | )
9 |
10 | func sortFileRule(rules []*config.FileRule) {
11 |
12 | sort.Slice(rules, func(i, j int) bool {
13 | return rules[i].Prefix < rules[j].Prefix
14 | })
15 | var splitKey int
16 | for key, val := range rules {
17 | if strings.Contains(val.Prefix, "{") {
18 | splitKey = key
19 | break
20 | }
21 | }
22 | ar1 := rules[:splitKey]
23 | ar2 := rules[splitKey:]
24 | rules = append(bubbleSortFileRule(ar1), bubbleSortFileRule(ar2)...)
25 | }
26 |
27 | func bubbleSortFileRule(arr []*config.FileRule) []*config.FileRule {
28 | var lenArr []int
29 | for _, value := range arr {
30 | lenArr = append(lenArr, strings.Count(value.Prefix, "/"))
31 | }
32 |
33 | for i := 0; i < len(lenArr)-1; i++ {
34 | for j := 0; j < len(lenArr)-i-1; j++ {
35 | if lenArr[j] < lenArr[j+1] {
36 | temp := arr[j]
37 | arr[j] = arr[j+1]
38 | arr[j+1] = temp
39 | num := lenArr[j]
40 | lenArr[j] = lenArr[j+1]
41 | lenArr[j+1] = num
42 | }
43 | }
44 | }
45 | return arr
46 | }
47 |
--------------------------------------------------------------------------------
/gateway/modules/auth/types.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/config"
7 | "github.com/spaceuptech/space-cloud/gateway/model"
8 | )
9 |
10 | type adminMan interface {
11 | GetSecret() string
12 | }
13 | type integrationManagerInterface interface {
14 | InvokeHook(ctx context.Context, params model.RequestParams) config.IntegrationAuthResponse
15 | }
16 |
--------------------------------------------------------------------------------
/gateway/modules/crud/bolt/aggregate.go:
--------------------------------------------------------------------------------
1 | package bolt
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/helpers"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/model"
9 | )
10 |
11 | // Aggregate performs a bolt db pipeline aggregation
12 | func (b *Bolt) Aggregate(ctx context.Context, col string, req *model.AggregateRequest) (interface{}, error) {
13 | return nil, helpers.Logger.LogError(helpers.GetRequestID(ctx), "aggregate operation not supported for selected database", nil, nil)
14 | }
15 |
--------------------------------------------------------------------------------
/gateway/modules/crud/bolt/batch.go:
--------------------------------------------------------------------------------
1 | package bolt
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/helpers"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/model"
9 | )
10 |
11 | // Batch performs the provided operations in a single Batch
12 | func (b *Bolt) Batch(ctx context.Context, req *model.BatchRequest) ([]int64, error) {
13 | return nil, helpers.Logger.LogError(helpers.GetRequestID(ctx), "Batch operation not supported for selected database", nil, nil)
14 | }
15 |
--------------------------------------------------------------------------------
/gateway/modules/crud/bolt/create_test.go:
--------------------------------------------------------------------------------
1 | package bolt
2 |
3 | import (
4 | "context"
5 | "os"
6 | "testing"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/utils"
9 | )
10 |
11 | func TestBolt_Create(t *testing.T) {
12 |
13 | b, err := Init(true, "create.db", "bucketName")
14 | if err != nil {
15 | t.Fatal("error initializing database")
16 | }
17 |
18 | for _, tt := range generateCreateTestCases() {
19 | t.Run(tt.name, func(t *testing.T) {
20 |
21 | got, err := b.Create(context.Background(), tt.args.col, tt.args.req)
22 | if (err != nil) != tt.wantErr {
23 | t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr)
24 | return
25 | }
26 | if got != tt.want {
27 | t.Errorf("Create() got = %v, want %v", got, tt.want)
28 | }
29 | })
30 | }
31 | utils.CloseTheCloser(b)
32 | if err := os.Remove("create.db"); err != nil {
33 | t.Error("error removing database file:", err)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/gateway/modules/crud/bolt/describe.go:
--------------------------------------------------------------------------------
1 | package bolt
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/helpers"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/model"
9 | )
10 |
11 | // DescribeTable return a structure of sql table
12 | func (b *Bolt) DescribeTable(ctx context.Context, col string) ([]model.InspectorFieldType, []model.IndexType, error) {
13 | return nil, nil, helpers.Logger.LogError(helpers.GetRequestID(ctx), "Describe table operation not supported for selected database", nil, nil)
14 | }
15 |
--------------------------------------------------------------------------------
/gateway/modules/crud/bolt/raw.go:
--------------------------------------------------------------------------------
1 | package bolt
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/spaceuptech/helpers"
8 |
9 | "github.com/spaceuptech/space-cloud/gateway/model"
10 | )
11 |
12 | // RawQuery query document(s) from the database
13 | func (b *Bolt) RawQuery(ctx context.Context, query string, isDebug bool, args []interface{}) (int64, interface{}, *model.SQLMetaData, error) {
14 | return 0, "", nil, errors.New("error raw query cannot be performed over embedded database")
15 | }
16 |
17 | // CreateDatabaseIfNotExist creates a project if none exist
18 | func (b *Bolt) CreateDatabaseIfNotExist(ctx context.Context, project string) error {
19 | return helpers.Logger.LogError(helpers.GetRequestID(ctx), "Unable to create database operation cannot be performed over selected database", nil, nil)
20 | }
21 |
22 | // RawBatch performs a batch operation for schema creation
23 | // NOTE: not to be exposed externally
24 | func (b *Bolt) RawBatch(ctx context.Context, batchedQueries []string) error {
25 | return helpers.Logger.LogError(helpers.GetRequestID(ctx), "Unable to create raw batch operation cannot be performed over selected database", nil, nil)
26 | }
27 |
28 | // GetConnectionState : function to check connection state
29 | func (b *Bolt) GetConnectionState(ctx context.Context) bool {
30 | if !b.enabled || b.client == nil {
31 | return false
32 | }
33 |
34 | // Ping to check if connection is established
35 | err := b.client.Info()
36 | if err != nil {
37 | _ = b.client.Close()
38 | return false
39 | }
40 |
41 | return true
42 | }
43 |
--------------------------------------------------------------------------------
/gateway/modules/crud/mgo/aggregate.go:
--------------------------------------------------------------------------------
1 | package mgo
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/spaceuptech/space-cloud/gateway/model"
8 | "github.com/spaceuptech/space-cloud/gateway/utils"
9 | )
10 |
11 | // Aggregate performs a mongo db pipeline aggregation
12 | func (m *Mongo) Aggregate(ctx context.Context, col string, req *model.AggregateRequest) (interface{}, error) {
13 | collection := m.getClient().Database(m.dbName).Collection(col)
14 |
15 | switch req.Operation {
16 | case utils.One:
17 | var result map[string]interface{}
18 |
19 | cur, err := collection.Aggregate(ctx, req.Pipeline)
20 | if err != nil {
21 | return nil, err
22 | }
23 | defer func() { _ = cur.Close(ctx) }()
24 |
25 | if !cur.Next(ctx) {
26 | return nil, errors.New("No result found")
27 | }
28 |
29 | err = cur.Decode(&result)
30 | if err != nil {
31 | return nil, err
32 | }
33 |
34 | return result, nil
35 |
36 | case utils.All:
37 | results := []interface{}{}
38 |
39 | cur, err := collection.Aggregate(ctx, req.Pipeline)
40 | defer func() { _ = cur.Close(ctx) }()
41 | if err != nil {
42 | return nil, err
43 | }
44 |
45 | for cur.Next(ctx) {
46 | var doc map[string]interface{}
47 | err := cur.Decode(&doc)
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | results = append(results, doc)
53 | }
54 |
55 | if err := cur.Err(); err != nil {
56 | return nil, err
57 | }
58 |
59 | return results, nil
60 |
61 | default:
62 | return nil, utils.ErrInvalidParams
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/gateway/modules/crud/mgo/collections.go:
--------------------------------------------------------------------------------
1 | package mgo
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/spaceuptech/helpers"
8 |
9 | "github.com/spaceuptech/space-cloud/gateway/utils"
10 | )
11 |
12 | // GetCollections returns collection / tables name of specified database
13 | func (m *Mongo) GetCollections(ctx context.Context) ([]utils.DatabaseCollections, error) {
14 |
15 | collections, err := m.getClient().Database(m.dbName).ListCollectionNames(ctx, map[string]interface{}{})
16 | if err != nil {
17 | return nil, helpers.Logger.LogError(helpers.GetRequestID(ctx), fmt.Sprintf("Unable to query database to get tables in database (%s)", m.dbName), err, nil)
18 | }
19 |
20 | dbCols := make([]utils.DatabaseCollections, len(collections))
21 | for i, col := range collections {
22 | dbCols[i] = utils.DatabaseCollections{TableName: col}
23 | }
24 |
25 | return dbCols, nil
26 | }
27 |
--------------------------------------------------------------------------------
/gateway/modules/crud/mgo/create.go:
--------------------------------------------------------------------------------
1 | package mgo
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/model"
7 | "github.com/spaceuptech/space-cloud/gateway/utils"
8 | )
9 |
10 | // Create inserts a document (or multiple when op is "all") into the database
11 | func (m *Mongo) Create(ctx context.Context, col string, req *model.CreateRequest) (int64, error) {
12 | // Create a collection object
13 | collection := m.getClient().Database(m.dbName).Collection(col)
14 |
15 | switch req.Operation {
16 | case utils.One:
17 | // Insert single document
18 | _, err := collection.InsertOne(ctx, req.Document)
19 | if err != nil {
20 | return 0, err
21 | }
22 |
23 | return 1, nil
24 |
25 | case utils.All:
26 | // Insert multiple documents
27 | objs, ok := req.Document.([]interface{})
28 | if !ok {
29 | return 0, utils.ErrInvalidParams
30 | }
31 |
32 | res, err := collection.InsertMany(ctx, objs)
33 | if err != nil {
34 | return 0, err
35 | }
36 |
37 | return int64(len(res.InsertedIDs)), nil
38 |
39 | default:
40 | return 0, utils.ErrInvalidParams
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/gateway/modules/crud/mgo/delete.go:
--------------------------------------------------------------------------------
1 | package mgo
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "go.mongodb.org/mongo-driver/mongo/options"
8 |
9 | "github.com/spaceuptech/space-cloud/gateway/model"
10 | "github.com/spaceuptech/space-cloud/gateway/utils"
11 | )
12 |
13 | // Delete removes the document(s) from the database which match the condition
14 | func (m *Mongo) Delete(ctx context.Context, col string, req *model.DeleteRequest) (int64, error) {
15 | collection := m.getClient().Database(m.dbName).Collection(col)
16 | req.Find = sanitizeWhereClause(ctx, col, req.Find)
17 |
18 | switch req.Operation {
19 | case utils.One:
20 | _, err := collection.DeleteOne(ctx, req.Find)
21 | if err != nil {
22 | return 0, err
23 | }
24 |
25 | return 1, nil
26 |
27 | case utils.All:
28 | res, err := collection.DeleteMany(ctx, req.Find)
29 | if err != nil {
30 | return 0, err
31 | }
32 |
33 | return res.DeletedCount, nil
34 |
35 | default:
36 | return 0, errors.New("Invalid operation")
37 | }
38 | }
39 |
40 | // DeleteCollection removes a collection from database`
41 | func (m *Mongo) DeleteCollection(ctx context.Context, col string) error {
42 | return m.getClient().Database(m.dbName).Collection(col, &options.CollectionOptions{}).Drop(ctx)
43 | }
44 |
--------------------------------------------------------------------------------
/gateway/modules/crud/mgo/describe.go:
--------------------------------------------------------------------------------
1 | package mgo
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/spaceuptech/space-cloud/gateway/model"
8 | )
9 |
10 | // DescribeTable return a structure of sql table
11 | func (m *Mongo) DescribeTable(ctc context.Context, col string) ([]model.InspectorFieldType, []model.IndexType, error) {
12 | return nil, nil, errors.New("schema operation cannot be performed")
13 | }
14 |
--------------------------------------------------------------------------------
/gateway/modules/crud/mgo/helpers.go:
--------------------------------------------------------------------------------
1 | package mgo
2 |
3 | import (
4 | "context"
5 | "strings"
6 | )
7 |
8 | func sanitizeWhereClause(ctx context.Context, col string, find map[string]interface{}) map[string]interface{} {
9 | for key, value := range find {
10 | arr := strings.Split(key, ".")
11 | if len(arr) > 1 && arr[0] == col {
12 | delete(find, key)
13 | find[strings.Join(arr[1:], ".")] = value
14 | }
15 | switch key {
16 | case "$or":
17 | objArr, ok := value.([]interface{})
18 | if ok {
19 | for _, obj := range objArr {
20 | t, ok := obj.(map[string]interface{})
21 | if ok {
22 | sanitizeWhereClause(ctx, col, t)
23 | }
24 | }
25 | }
26 | default:
27 | obj, ok := value.(map[string]interface{})
28 | if ok {
29 | sanitizeWhereClause(ctx, col, obj)
30 | }
31 | }
32 | }
33 | return find
34 | }
35 |
--------------------------------------------------------------------------------
/gateway/modules/crud/mgo/raw.go:
--------------------------------------------------------------------------------
1 | package mgo
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 |
8 | "github.com/spaceuptech/helpers"
9 |
10 | "github.com/spaceuptech/space-cloud/gateway/model"
11 | )
12 |
13 | // RawBatch performs a batch operation for schema creation
14 | // NOTE: not to be exposed externally
15 | func (m *Mongo) RawBatch(ctx context.Context, queries []string) error {
16 | return errors.New("raw batch operation cannot be performed on mongo")
17 | }
18 |
19 | // RawQuery query document(s) from the database
20 | func (m *Mongo) RawQuery(ctx context.Context, query string, isDebug bool, args []interface{}) (int64, interface{}, *model.SQLMetaData, error) {
21 | return 0, "", nil, errors.New("error raw query operation cannot be performed on mongo")
22 | }
23 |
24 | // GetConnectionState : function to check connection state
25 | func (m *Mongo) GetConnectionState(ctx context.Context) bool {
26 | if !m.enabled || m.getClient() == nil {
27 | return false
28 | }
29 |
30 | // Ping to check if connection is established
31 | err := m.getClient().Ping(ctx, nil)
32 | if err != nil {
33 | _ = m.getClient().Disconnect(context.Background())
34 | _ = helpers.Logger.LogError(helpers.GetRequestID(ctx), fmt.Sprintf("Unable to ping mongo database - %s", m.dbName), err, nil)
35 | return false
36 | }
37 |
38 | return true
39 | }
40 |
41 | // CreateDatabaseIfNotExist creates a database if not exist which has same name of project
42 | func (m *Mongo) CreateDatabaseIfNotExist(ctx context.Context, project string) error {
43 | return errors.New("create project exists cannot be performed over mongo")
44 | }
45 |
--------------------------------------------------------------------------------
/gateway/modules/crud/mgo/update.go:
--------------------------------------------------------------------------------
1 | package mgo
2 |
3 | import (
4 | "context"
5 |
6 | "go.mongodb.org/mongo-driver/mongo/options"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/model"
9 | "github.com/spaceuptech/space-cloud/gateway/utils"
10 | )
11 |
12 | // Update updates the document(s) which match the condition provided.
13 | func (m *Mongo) Update(ctx context.Context, col string, req *model.UpdateRequest) (int64, error) {
14 | collection := m.getClient().Database(m.dbName).Collection(col)
15 | req.Find = sanitizeWhereClause(ctx, col, req.Find)
16 |
17 | switch req.Operation {
18 | case utils.One:
19 | _, err := collection.UpdateOne(ctx, req.Find, req.Update)
20 | if err != nil {
21 | return 0, err
22 | }
23 |
24 | return 1, nil
25 |
26 | case utils.All:
27 | res, err := collection.UpdateMany(ctx, req.Find, req.Update)
28 | if err != nil {
29 | return 0, err
30 | }
31 |
32 | return res.MatchedCount, nil
33 |
34 | case utils.Upsert:
35 | doUpsert := true
36 | res, err := collection.UpdateOne(ctx, req.Find, req.Update, &options.UpdateOptions{Upsert: &doUpsert})
37 | if err != nil {
38 | return 0, err
39 | }
40 |
41 | return res.MatchedCount + res.UpsertedCount, nil
42 |
43 | default:
44 | return 0, utils.ErrInvalidParams
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/gateway/modules/crud/sql/aggregate.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/spaceuptech/space-cloud/gateway/model"
8 | )
9 |
10 | // Aggregate performs a mongo db pipeline aggregation
11 | func (s *SQL) Aggregate(ctx context.Context, col string, req *model.AggregateRequest) (interface{}, error) {
12 | return nil, errors.New("aggregation is not supported for sql databases")
13 | }
14 |
--------------------------------------------------------------------------------
/gateway/modules/crud/sql/collections.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/doug-martin/goqu/v8"
9 | "github.com/spaceuptech/helpers"
10 |
11 | "github.com/spaceuptech/space-cloud/gateway/utils"
12 | )
13 |
14 | // GetCollections returns collection / tables name of specified database
15 | func (s *SQL) GetCollections(ctx context.Context) ([]utils.DatabaseCollections, error) {
16 | dialect := goqu.Dialect(s.dbType)
17 | query := dialect.From("information_schema.tables").Prepared(true).Select("table_name").Where(goqu.Ex{"table_schema": s.name})
18 |
19 | sqlString, args, err := query.ToSQL()
20 | if err != nil {
21 | return nil, err
22 | }
23 | if s.dbType == "sqlserver" {
24 | new := strings.Replace(sqlString, "?", "@p1", -1)
25 | sqlString = new
26 | }
27 |
28 | sqlString = strings.Replace(sqlString, "\"", "", -1)
29 | rows, err := s.getClient().QueryxContext(ctx, sqlString, args...)
30 | if err != nil {
31 | return nil, helpers.Logger.LogError(helpers.GetRequestID(ctx), fmt.Sprintf("Unable to query database to get tables in database (%s)", s.name), err, nil)
32 | }
33 | defer func() { _ = rows.Close() }()
34 |
35 | result := make([]utils.DatabaseCollections, 0)
36 | for rows.Next() {
37 | var tableName string
38 |
39 | if err := rows.Scan(&tableName); err != nil {
40 | return nil, helpers.Logger.LogError(helpers.GetRequestID(ctx), "Unable to process database result", err, nil)
41 | }
42 |
43 | result = append(result, utils.DatabaseCollections{TableName: tableName})
44 | }
45 |
46 | return result, nil
47 | }
48 |
--------------------------------------------------------------------------------
/gateway/modules/crud/types.go:
--------------------------------------------------------------------------------
1 | package crud
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/config"
7 | "github.com/spaceuptech/space-cloud/gateway/model"
8 | "github.com/spaceuptech/space-cloud/gateway/modules/global/caching"
9 | )
10 |
11 | type integrationManagerInterface interface {
12 | InvokeHook(ctx context.Context, params model.RequestParams) config.IntegrationAuthResponse
13 | }
14 |
15 | type cachingInterface interface {
16 | SetDatabaseKey(ctx context.Context, projectID, dbAlias, col string, result *model.CacheDatabaseResult, dbCacheOptions *caching.CacheResult, cache *config.ReadCacheOptions, cacheJoinInfo map[string]map[string]string) error
17 | GetDatabaseKey(ctx context.Context, projectID, dbAlias, tableName string, req *model.ReadRequest) (*caching.CacheResult, error)
18 | }
19 |
--------------------------------------------------------------------------------
/gateway/modules/eventing/broadcast.go:
--------------------------------------------------------------------------------
1 | package eventing
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/spaceuptech/helpers"
9 |
10 | "github.com/spaceuptech/space-cloud/gateway/model"
11 | )
12 |
13 | // ProcessTransmittedEvents processes the event received
14 | func (m *Module) ProcessTransmittedEvents(eventDocs []*model.EventDocument) {
15 |
16 | // Get the assigned token range
17 | start, end := m.syncMan.GetAssignedTokens()
18 |
19 | // Get current timestamp
20 | currentTimestamp := time.Now()
21 |
22 | for _, eventDoc := range eventDocs {
23 | if eventDoc.Token >= start && eventDoc.Token <= end {
24 | timestamp, err := time.Parse(time.RFC3339Nano, eventDoc.Timestamp)
25 | if err != nil {
26 | _ = helpers.Logger.LogError(helpers.GetRequestID(context.TODO()), fmt.Sprintf("Could not parse (%s) in event doc (%s) as time", eventDoc.Timestamp, eventDoc.ID), err, nil)
27 | continue
28 | }
29 |
30 | if currentTimestamp.After(timestamp) || currentTimestamp.Equal(timestamp) {
31 | go m.processStagedEvent(eventDoc)
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/gateway/modules/eventing/eventing_test.go:
--------------------------------------------------------------------------------
1 | package eventing
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/config"
7 | )
8 |
9 | func TestModule_SetConfig(t *testing.T) {
10 | type args struct {
11 | eventing *config.EventingConfig
12 | }
13 | tests := []struct {
14 | name string
15 | m *Module
16 | args args
17 | wantErr bool
18 | }{
19 | {
20 | name: "eventing is not enabled",
21 | m: &Module{config: &config.Eventing{Enabled: true}},
22 | args: args{eventing: &config.EventingConfig{Enabled: false, DBAlias: "mysql"}},
23 | },
24 | {
25 | name: "DBAlias not mentioned",
26 | m: &Module{config: &config.Eventing{Enabled: true}},
27 | args: args{eventing: &config.EventingConfig{Enabled: true, DBAlias: ""}},
28 | wantErr: true,
29 | },
30 | }
31 | for _, tt := range tests {
32 | t.Run(tt.name, func(t *testing.T) {
33 | if err := tt.m.SetConfig("projectID", tt.args.eventing); (err != nil) != tt.wantErr {
34 | t.Errorf("Module.SetConfig() error = %v, wantErr %v", err, tt.wantErr)
35 | }
36 | })
37 | }
38 | }
39 |
40 | // TODO: New function && write test case for len(schemaType["dummyDBName"][eventType]) != 0
41 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/amazons3/amazonS3.go:
--------------------------------------------------------------------------------
1 | package amazons3
2 |
3 | import (
4 | "github.com/aws/aws-sdk-go/aws"
5 | "github.com/aws/aws-sdk-go/aws/session"
6 |
7 | "github.com/spaceuptech/space-cloud/gateway/utils"
8 | )
9 |
10 | // AmazonS3 holds the S3 driver session
11 | type AmazonS3 struct {
12 | client *session.Session
13 | bucket string
14 | }
15 |
16 | // Init initializes an amazon s3 driver
17 | func Init(region, endpoint, bucket string, disableSSL, forcePathStyle *bool) (*AmazonS3, error) {
18 | awsConf := &aws.Config{
19 | Region: aws.String(region),
20 | DisableSSL: disableSSL,
21 | S3ForcePathStyle: forcePathStyle,
22 | }
23 | if len(endpoint) > 0 {
24 | awsConf.Endpoint = aws.String(endpoint)
25 | }
26 | session, err := session.NewSession(awsConf)
27 | return &AmazonS3{client: session, bucket: bucket}, err
28 | }
29 |
30 | // GetStoreType returns the file store type
31 | func (a *AmazonS3) GetStoreType() utils.FileStoreType {
32 | return utils.AmazonS3
33 | }
34 |
35 | // Close gracefully close the s3 filestore module
36 | func (a *AmazonS3) Close() error {
37 | return nil
38 | }
39 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/amazons3/create.go:
--------------------------------------------------------------------------------
1 | package amazons3
2 |
3 | import (
4 | "context"
5 | "io"
6 |
7 | "github.com/aws/aws-sdk-go/aws"
8 | "github.com/aws/aws-sdk-go/service/s3"
9 | "github.com/aws/aws-sdk-go/service/s3/s3manager"
10 |
11 | "github.com/spaceuptech/space-cloud/gateway/model"
12 | "github.com/spaceuptech/space-cloud/gateway/utils"
13 | )
14 |
15 | // CreateFile creates a file in S3
16 | func (a *AmazonS3) CreateFile(ctx context.Context, req *model.CreateFileRequest, file io.Reader) error {
17 | uploader := s3manager.NewUploader(a.client)
18 | _, err := uploader.Upload(&s3manager.UploadInput{
19 | Bucket: aws.String(a.bucket),
20 | Key: aws.String(utils.JoinLeading(req.Path, req.Name, "/")),
21 | Body: file,
22 | })
23 | return err
24 | }
25 |
26 | // CreateDir creates a directory in S3
27 | func (a *AmazonS3) CreateDir(ctx context.Context, req *model.CreateFileRequest) error {
28 | // back slash at the end is important, if not then file will be created of that name
29 | svc := s3.New(a.client)
30 | request := &s3.PutObjectInput{
31 | Bucket: aws.String(a.bucket),
32 | Key: aws.String(utils.JoinLeadingTrailing(req.Path, req.Name, "/")),
33 | }
34 | _, err := svc.PutObject(request)
35 | return err
36 | }
37 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/amazons3/delete.go:
--------------------------------------------------------------------------------
1 | package amazons3
2 |
3 | import (
4 | "context"
5 | "strings"
6 |
7 | "github.com/aws/aws-sdk-go/aws"
8 | "github.com/aws/aws-sdk-go/service/s3"
9 | "github.com/aws/aws-sdk-go/service/s3/s3manager"
10 | )
11 |
12 | // DeleteFile deletes a file from S3
13 | func (a *AmazonS3) DeleteFile(ctx context.Context, path string) error {
14 | svc := s3.New(a.client)
15 | _, err := svc.DeleteObject(&s3.DeleteObjectInput{Bucket: aws.String(a.bucket), Key: aws.String(path)})
16 | if err != nil {
17 | return err
18 | }
19 |
20 | return svc.WaitUntilObjectNotExists(&s3.HeadObjectInput{
21 | Bucket: aws.String(a.bucket),
22 | Key: aws.String(path),
23 | })
24 | }
25 |
26 | // DeleteDir deletes a directory in S3
27 | func (a *AmazonS3) DeleteDir(ctx context.Context, path string) error {
28 | // TODO: Consider AWS operation limit
29 | svc := s3.New(a.client)
30 | path = strings.TrimPrefix(path, "/")
31 | // Setup BatchDeleteIterator to iterate through a list of objects.
32 | iter := s3manager.NewDeleteListIterator(svc, &s3.ListObjectsInput{
33 | Bucket: aws.String(a.bucket),
34 | Prefix: aws.String(path),
35 | })
36 |
37 | // Traverse iterator deleting each object
38 | return s3manager.NewBatchDeleteWithClient(svc).Delete(aws.BackgroundContext(), iter)
39 | }
40 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/amazons3/raw.go:
--------------------------------------------------------------------------------
1 | package amazons3
2 |
3 | import (
4 | "context"
5 | "strings"
6 |
7 | "github.com/aws/aws-sdk-go/aws"
8 | "github.com/aws/aws-sdk-go/aws/awserr"
9 | "github.com/aws/aws-sdk-go/service/s3"
10 | "github.com/spaceuptech/helpers"
11 | )
12 |
13 | // DoesExists checks if path exists
14 | func (a *AmazonS3) DoesExists(ctx context.Context, path string) error {
15 | path = strings.TrimPrefix(path, "/")
16 |
17 | svc := s3.New(a.client)
18 | input := &s3.GetObjectInput{
19 | Bucket: aws.String(a.bucket),
20 | Key: aws.String(path),
21 | }
22 |
23 | _, err := svc.GetObject(input)
24 | if err != nil {
25 | return helpers.Logger.LogError(helpers.GetRequestID(ctx), "Unable to get specified object from Amazon s3", err, nil)
26 | }
27 | return nil
28 | }
29 |
30 | // GetState checks if sc is able to query s3
31 | func (a *AmazonS3) GetState(ctx context.Context) error {
32 | err := a.DoesExists(ctx, "/")
33 | if err != nil {
34 | if v, ok := err.(awserr.Error); ok {
35 | if v.Code() != s3.ErrCodeNoSuchKey {
36 | return helpers.Logger.LogError(helpers.GetRequestID(ctx), "Unable to connect to Amazon s3", err, nil)
37 | }
38 | }
39 | }
40 | return nil
41 | }
42 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/gcpstorage/create.go:
--------------------------------------------------------------------------------
1 | package gcpstorage
2 |
3 | import (
4 | "context"
5 | "io"
6 | "strings"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/model"
9 | "github.com/spaceuptech/space-cloud/gateway/utils"
10 | )
11 |
12 | // CreateFile creates a file in GCPStorage
13 | func (g *GCPStorage) CreateFile(ctx context.Context, req *model.CreateFileRequest, file io.Reader) error {
14 | req.Path = strings.TrimPrefix(req.Path, "/")
15 | path := req.Path + "/" + req.Name
16 | if len(req.Path) == 0 {
17 | path = req.Name
18 | }
19 | wc := g.client.Bucket(g.bucket).Object(path).NewWriter(ctx)
20 | if _, err := io.Copy(wc, file); err != nil {
21 | return err
22 | }
23 | return wc.Close()
24 | }
25 |
26 | // CreateDir creates a directory in GCPStorage
27 | func (g *GCPStorage) CreateDir(ctx context.Context, req *model.CreateFileRequest) error {
28 | req.Path = strings.TrimPrefix(req.Path, "/")
29 | wc := g.client.Bucket(g.bucket).Object(utils.JoinTrailing(req.Path, req.Name, "/")).NewWriter(ctx)
30 | _, err := wc.Write([]byte(""))
31 | if err != nil {
32 | return err
33 | }
34 | return wc.Close()
35 | }
36 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/gcpstorage/delete.go:
--------------------------------------------------------------------------------
1 | package gcpstorage
2 |
3 | import (
4 | "context"
5 | "strings"
6 |
7 | "cloud.google.com/go/storage"
8 | "google.golang.org/api/iterator"
9 | )
10 |
11 | // DeleteFile deletes a file from GCPStorage
12 | func (g *GCPStorage) DeleteFile(ctx context.Context, path string) error {
13 | // trim / at the start and at the end
14 | path = strings.TrimPrefix(strings.TrimSuffix(path, "/"), "/")
15 | return g.client.Bucket(g.bucket).Object(path).Delete(ctx)
16 | }
17 |
18 | // DeleteDir deletes a directory in GCPStorage
19 | func (g *GCPStorage) DeleteDir(ctx context.Context, path string) error {
20 | path = strings.TrimPrefix(path, "/")
21 | if !strings.HasSuffix(path, "/") {
22 | path += "/"
23 | }
24 | bucket := g.client.Bucket(g.bucket)
25 | it := bucket.Objects(ctx, &storage.Query{
26 | Prefix: path,
27 | })
28 | for {
29 | attrs, err := it.Next()
30 | if err == iterator.Done {
31 | break
32 | }
33 | if err != nil {
34 | return err
35 | }
36 | err = bucket.Object(attrs.Name).Delete(ctx)
37 | if err != nil {
38 | return err
39 | }
40 | }
41 | return nil
42 | }
43 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/gcpstorage/gcpstorage.go:
--------------------------------------------------------------------------------
1 | package gcpstorage
2 |
3 | import (
4 | "context"
5 |
6 | "cloud.google.com/go/storage"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/utils"
9 | )
10 |
11 | // GCPStorage holds the GCPStorage client
12 | type GCPStorage struct {
13 | client *storage.Client
14 | bucket string
15 | }
16 |
17 | // Init initializes a GCPStorage client
18 | func Init(bucket string) (*GCPStorage, error) {
19 | ctx := context.TODO()
20 | client, err := storage.NewClient(ctx)
21 | if err != nil {
22 | return nil, err
23 | }
24 | return &GCPStorage{client, bucket}, nil
25 | }
26 |
27 | // GetStoreType returns the file store type
28 | func (g *GCPStorage) GetStoreType() utils.FileStoreType {
29 | return utils.GCPStorage
30 | }
31 |
32 | // Close gracefully close the GCPStorage module
33 | func (g *GCPStorage) Close() error {
34 | return g.client.Close()
35 | }
36 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/gcpstorage/raw.go:
--------------------------------------------------------------------------------
1 | package gcpstorage
2 |
3 | import (
4 | "context"
5 | "strings"
6 |
7 | "cloud.google.com/go/storage"
8 | )
9 |
10 | // DoesExists checks if the path exists
11 | func (g *GCPStorage) DoesExists(ctx context.Context, path string) error {
12 | path = strings.TrimPrefix(path, "/")
13 | if _, err := g.client.Bucket(g.bucket).Object(path).Attrs(ctx); err != nil {
14 | return err
15 | }
16 | return nil
17 | }
18 |
19 | // GetState checks if sc is able to query gcp storage
20 | func (g *GCPStorage) GetState(ctx context.Context) error {
21 | if _, err := g.client.Bucket(g.bucket).Object("/").Attrs(ctx); err != nil && err != storage.ErrObjectNotExist {
22 | return err
23 | }
24 | return nil
25 | }
26 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/gcpstorage/raw_test.go:
--------------------------------------------------------------------------------
1 | package gcpstorage
2 |
3 | // func TestGCPStorage_DoesExists(t *testing.T) {
4 | // type fields struct {
5 | // client *storage.Client
6 | // bucket string
7 | // }
8 | // type args struct {
9 | // path string
10 | // }
11 | // tests := []struct {
12 | // name string
13 | // fields fields
14 | // args args
15 | // want bool
16 | // }{
17 | // {
18 | // name: "test",
19 | // fields: fields{
20 | // bucket: "gcpgolang",
21 | // },
22 | // args: args{path: "name/sirname/"},
23 | // want: true,
24 | // },
25 | // }
26 | // for _, tt := range tests {
27 | // t.Run(tt.name, func(t *testing.T) {
28 | // g, err := Init(tt.fields.bucket)
29 | // if err != nil {
30 | // t.Fatal(err)
31 | // }
32 | // if err := g.DoesExists(tt.args.path); err != nil {
33 | // t.Errorf("DoesExists() = %v, want %v " , tt.want, err)
34 | // }
35 | // })
36 | // }
37 | // }
38 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/local/create.go:
--------------------------------------------------------------------------------
1 | package local
2 |
3 | import (
4 | "bufio"
5 | "context"
6 | "errors"
7 | "io"
8 | "os"
9 | "strings"
10 |
11 | "github.com/spaceuptech/space-cloud/gateway/model"
12 | "github.com/spaceuptech/space-cloud/gateway/utils"
13 | )
14 |
15 | // CreateFile creates a file in the path provided
16 | func (l *Local) CreateFile(ctx context.Context, req *model.CreateFileRequest, file io.Reader) error {
17 | ps := string(os.PathSeparator)
18 | path := strings.TrimRight(l.rootPath, ps) + ps + strings.TrimLeft(req.Path, ps)
19 |
20 | // Create the dir recursively if it does not exists or overwrite if a file of same name already exists.
21 | if !isPathDir(path) {
22 | if !req.MakeAll {
23 | return errors.New("Local: Provided path is not a directory")
24 | }
25 |
26 | err := os.MkdirAll(path, os.ModePerm)
27 | if err != nil {
28 | return err
29 | }
30 | }
31 |
32 | f, err := os.Create(path + string(os.PathSeparator) + req.Name)
33 | defer utils.CloseTheCloser(f)
34 | if err != nil {
35 | return err
36 | }
37 |
38 | w := bufio.NewWriter(f)
39 | defer func() { _ = w.Flush() }()
40 |
41 | _, err = io.Copy(w, file)
42 | return err
43 | }
44 |
45 | // CreateDir creates a directory in the path provided
46 | func (l *Local) CreateDir(ctx context.Context, req *model.CreateFileRequest) error {
47 | ps := string(os.PathSeparator)
48 | path := strings.TrimRight(l.rootPath, ps) + ps + strings.TrimLeft(req.Path, ps)
49 | if !isPathDir(path) && !req.MakeAll {
50 | return errors.New("Local: Provided path is not a directory")
51 | }
52 |
53 | return os.MkdirAll(path+string(os.PathSeparator)+req.Name, os.ModePerm)
54 | }
55 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/local/delete.go:
--------------------------------------------------------------------------------
1 | package local
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "strings"
8 | )
9 |
10 | // DeleteDir deletes a directory if it exists
11 | func (l *Local) DeleteDir(ctx context.Context, path string) error {
12 | ps := string(os.PathSeparator)
13 | path = strings.TrimRight(l.rootPath, ps) + ps + strings.TrimLeft(path, ps)
14 | return os.RemoveAll(path)
15 | }
16 |
17 | // DeleteFile deletes a file if it exists
18 | func (l *Local) DeleteFile(ctx context.Context, path string) error {
19 | ps := string(os.PathSeparator)
20 | path = strings.TrimRight(l.rootPath, ps) + ps + strings.TrimLeft(path, ps)
21 | if isPathDir(path) {
22 | return fmt.Errorf("cannot delete the folder")
23 | }
24 | return os.Remove(path)
25 | }
26 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/local/local.go:
--------------------------------------------------------------------------------
1 | package local
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/utils"
7 | )
8 |
9 | // Local is the file store driver for the local filesystem
10 | type Local struct {
11 | rootPath string
12 | }
13 |
14 | // Init initialises the local filestore driver
15 | func Init(path string) (*Local, error) {
16 | return &Local{path}, os.MkdirAll(path, os.ModePerm)
17 | }
18 |
19 | // GetStoreType returns the file store type
20 | func (l *Local) GetStoreType() utils.FileStoreType {
21 | return utils.Local
22 | }
23 |
24 | // Close gracefully closed the local filestore module
25 | func (l *Local) Close() error {
26 | return nil
27 | }
28 |
29 | func isPathDir(path string) bool {
30 | stat, err := os.Stat(path)
31 | return err == nil && stat.IsDir()
32 | }
33 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/local/raw.go:
--------------------------------------------------------------------------------
1 | package local
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "os"
7 | "strings"
8 |
9 | "github.com/spaceuptech/helpers"
10 | )
11 |
12 | // DoesExists checks if the path exists
13 | func (l *Local) DoesExists(ctx context.Context, path string) error {
14 | // check if file / folder exists
15 | ps := string(os.PathSeparator)
16 | path = strings.TrimRight(l.rootPath, ps) + ps + strings.TrimLeft(path, ps)
17 | if _, err := os.Stat(path); err != nil {
18 | // path does not exist
19 | return errors.New("provided file / dir path not found")
20 | }
21 |
22 | return nil
23 | }
24 |
25 | // GetState check if root path is valid
26 | func (l *Local) GetState(ctx context.Context) error {
27 | if _, err := os.Stat(l.rootPath); os.IsNotExist(err) {
28 | return helpers.Logger.LogError(helpers.GetRequestID(ctx), "Invalid root path provided for file store", err, nil)
29 | }
30 | return nil
31 | }
32 |
--------------------------------------------------------------------------------
/gateway/modules/filestore/local/read.go:
--------------------------------------------------------------------------------
1 | package local
2 |
3 | import (
4 | "bufio"
5 | "context"
6 | "io/ioutil"
7 | "os"
8 | "strings"
9 |
10 | "github.com/spaceuptech/space-cloud/gateway/model"
11 | )
12 |
13 | // ListDir lists the directory
14 | func (l *Local) ListDir(ctx context.Context, req *model.ListFilesRequest) ([]*model.ListFilesResponse, error) {
15 | ps := string(os.PathSeparator)
16 | path := strings.TrimRight(l.rootPath, ps) + ps + strings.TrimLeft(req.Path, ps)
17 | files, err := ioutil.ReadDir(path)
18 | if err != nil {
19 | return nil, err
20 | }
21 |
22 | result := []*model.ListFilesResponse{}
23 | for _, f := range files {
24 | t := &model.ListFilesResponse{Name: f.Name(), Type: "file"}
25 | if f.IsDir() {
26 | t.Type = "dir"
27 | }
28 |
29 | if req.Type == "all" || req.Type == t.Type {
30 | result = append(result, t)
31 | }
32 | }
33 |
34 | return result, nil
35 | }
36 |
37 | // ReadFile reads a file from the path provided
38 | func (l *Local) ReadFile(ctx context.Context, path string) (*model.File, error) {
39 | ps := string(os.PathSeparator)
40 | p := strings.TrimRight(l.rootPath, ps) + ps + strings.TrimLeft(path, ps)
41 | f, err := os.Open(p)
42 | if err != nil {
43 | return nil, err
44 | }
45 |
46 | return &model.File{File: bufio.NewReader(f), Close: func() error { return f.Close() }}, nil
47 | }
48 |
--------------------------------------------------------------------------------
/gateway/modules/functions/types.go:
--------------------------------------------------------------------------------
1 | package functions
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/config"
7 | "github.com/spaceuptech/space-cloud/gateway/model"
8 | "github.com/spaceuptech/space-cloud/gateway/modules/global/caching"
9 | )
10 |
11 | type integrationManagerInterface interface {
12 | InvokeHook(ctx context.Context, params model.RequestParams) config.IntegrationAuthResponse
13 | }
14 |
15 | type cachingInterface interface {
16 | SetRemoteServiceKey(ctx context.Context, redisKey string, remoteServiceCacheOptions *caching.CacheResult, cache *config.ReadCacheOptions, result interface{}) error
17 | GetRemoteService(ctx context.Context, projectID, serviceID, endpoint string, cache *config.ReadCacheOptions, cacheOptions []interface{}) (*caching.CacheResult, error)
18 | }
19 |
--------------------------------------------------------------------------------
/gateway/modules/global/caching/types.go:
--------------------------------------------------------------------------------
1 | package caching
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/spaceuptech/space-cloud/gateway/model"
7 | )
8 |
9 | // CacheResult store cache result
10 | type CacheResult struct {
11 | lock sync.RWMutex
12 | redisKey string
13 | isCacheHit bool
14 | isCacheEnabled bool
15 | result interface{}
16 | }
17 |
18 | // GetResult gets cache result
19 | func (d *CacheResult) GetResult() interface{} {
20 | d.lock.Lock()
21 | defer d.lock.Unlock()
22 | return d.result
23 | }
24 |
25 | // GetDatabaseResult get cached database result
26 | func (d *CacheResult) GetDatabaseResult() *model.CacheDatabaseResult {
27 | d.lock.Lock()
28 | defer d.lock.Unlock()
29 | return d.result.(*model.CacheDatabaseResult)
30 | }
31 |
32 | // Key gets cache key
33 | func (d *CacheResult) Key() string {
34 | d.lock.Lock()
35 | defer d.lock.Unlock()
36 | return d.redisKey
37 | }
38 |
39 | // IsCacheHit tells if it's a cache hit or miss
40 | func (d *CacheResult) IsCacheHit() bool {
41 | d.lock.Lock()
42 | defer d.lock.Unlock()
43 | return d.isCacheHit
44 | }
45 |
46 | // IsCacheEnabled tells if the cache is enabled or not
47 | func (d *CacheResult) IsCacheEnabled() bool {
48 | d.lock.Lock()
49 | defer d.lock.Unlock()
50 | return d.isCacheEnabled
51 | }
52 |
--------------------------------------------------------------------------------
/gateway/modules/global/letsencrypt/config.go:
--------------------------------------------------------------------------------
1 | package letsencrypt
2 |
3 | import (
4 | "os"
5 | "strings"
6 | )
7 |
8 | // Config describes the configuration for let's encrypt
9 | type Config struct {
10 | Email string
11 | WhitelistedDomains []string
12 | StoreType StoreType
13 | }
14 |
15 | // StoreType describes the store used by the lets encrypt module
16 | type StoreType string
17 |
18 | const (
19 | // StoreLocal is used when the local filesystem us used as a store
20 | StoreLocal StoreType = "local"
21 |
22 | // StoreSC is used when SC is supposed to be used as a store
23 | StoreSC StoreType = "sc"
24 |
25 | // StoreKube is used when kubernetes is supposed to be used as a store
26 | StoreKube StoreType = "kube"
27 | )
28 |
29 | func loadConfig() *Config {
30 | prefix := "LETSENCRYPT_"
31 | c := &Config{WhitelistedDomains: []string{}, StoreType: StoreLocal}
32 | if email, p := os.LookupEnv(prefix + "EMAIL"); p {
33 | c.Email = email
34 | }
35 |
36 | if whitelist, p := os.LookupEnv(prefix + "WHITELIST"); p {
37 | c.WhitelistedDomains = strings.Split(whitelist, ",")
38 | }
39 |
40 | if store, p := os.LookupEnv(prefix + "STORE"); p {
41 | c.StoreType = StoreType(store)
42 | }
43 |
44 | return c
45 | }
46 |
--------------------------------------------------------------------------------
/gateway/modules/global/letsencrypt/domains.go:
--------------------------------------------------------------------------------
1 | package letsencrypt
2 |
3 | import "github.com/spaceuptech/space-cloud/gateway/utils"
4 |
5 | type domainMapping map[string][]string // key is project id and array is domain
6 |
7 | func (d domainMapping) setProjectDomains(project string, domains []string) {
8 | d[project] = domains
9 | }
10 |
11 | func (d domainMapping) deleteProject(project string) {
12 | delete(d, project)
13 | }
14 |
15 | func (d domainMapping) getUniqueDomains() []string {
16 | var domains []string
17 |
18 | // Iterate over all projects
19 | for _, v := range d {
20 | // Iterate over all domains in project
21 | for _, domain := range v {
22 | if !utils.StringExists(domains, domain) {
23 | domains = append(domains, domain)
24 | }
25 | }
26 | }
27 |
28 | return domains
29 | }
30 |
--------------------------------------------------------------------------------
/gateway/modules/global/routing/routing.go:
--------------------------------------------------------------------------------
1 | package routing
2 |
3 | import (
4 | "context"
5 | "sync"
6 | "text/template"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/config"
9 | "github.com/spaceuptech/space-cloud/gateway/model"
10 | )
11 |
12 | // Routing manages the routing functionality of space cloud
13 | type Routing struct {
14 | lock sync.RWMutex
15 |
16 | routes config.Routes
17 | globalConfig *config.GlobalRoutesConfig
18 | caching cachingInterface
19 | goTemplates map[string]*template.Template
20 | }
21 |
22 | // New creates a new instance of the routing module
23 | func New() *Routing {
24 | return &Routing{routes: make(config.Routes, 0), goTemplates: map[string]*template.Template{}, globalConfig: new(config.GlobalRoutesConfig)}
25 | }
26 |
27 | // SetCachingModule sets caching module
28 | func (r *Routing) SetCachingModule(c cachingInterface) {
29 | r.caching = c
30 | }
31 |
32 | type cachingInterface interface {
33 | SetIngressRouteKey(ctx context.Context, redisKey string, cache *config.ReadCacheOptions, result *model.CacheIngressRoute) error
34 | GetIngressRoute(ctx context.Context, routeID string, cacheOptions []interface{}) (string, bool, *model.CacheIngressRoute, error)
35 | }
36 |
--------------------------------------------------------------------------------
/gateway/modules/global/routing/routing_test.go:
--------------------------------------------------------------------------------
1 | package routing
2 |
3 | import (
4 | "reflect"
5 | "sync"
6 | "testing"
7 | "text/template"
8 |
9 | "github.com/spaceuptech/space-cloud/gateway/config"
10 | )
11 |
12 | func TestNew(t *testing.T) {
13 | tests := []struct {
14 | name string
15 | want *Routing
16 | }{
17 | // TODO: Add test cases.
18 | {
19 | name: "New Routing instance",
20 | want: &Routing{
21 | lock: sync.RWMutex{},
22 | routes: make(config.Routes, 0),
23 | goTemplates: map[string]*template.Template{},
24 | globalConfig: new(config.GlobalRoutesConfig),
25 | },
26 | },
27 | }
28 | for _, tt := range tests {
29 | t.Run(tt.name, func(t *testing.T) {
30 | if got := New(); !reflect.DeepEqual(got, tt.want) {
31 | t.Errorf("New() = %v, want %v", got, tt.want)
32 | }
33 | })
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/gateway/modules/realtime/types.go:
--------------------------------------------------------------------------------
1 | package realtime
2 |
3 | import "github.com/spaceuptech/space-cloud/gateway/model"
4 |
5 | type schemaInterface interface {
6 | GetSchema(dbAlias, col string) (model.Fields, bool)
7 | }
8 |
--------------------------------------------------------------------------------
/gateway/modules/schema/types.go:
--------------------------------------------------------------------------------
1 | package schema
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/stretchr/testify/mock"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/model"
9 | )
10 |
11 | type mockCrudSchemaInterface struct {
12 | mock.Mock
13 | }
14 |
15 | func (m *mockCrudSchemaInterface) GetDBType(dbAlias string) (string, error) {
16 | c := m.Called(dbAlias)
17 | return c.String(0), nil
18 | }
19 |
20 | func (m *mockCrudSchemaInterface) DescribeTable(ctx context.Context, dbAlias, col string) ([]model.InspectorFieldType, []model.IndexType, error) {
21 | return nil, nil, nil
22 | }
23 |
24 | func (m *mockCrudSchemaInterface) RawBatch(ctx context.Context, dbAlias string, batchedQueries []string) error {
25 | return nil
26 | }
27 |
--------------------------------------------------------------------------------
/gateway/modules/schema/variables.go:
--------------------------------------------------------------------------------
1 | package schema
2 |
3 | import "github.com/spaceuptech/space-cloud/gateway/model"
4 |
5 | type indexStore []*model.TableProperties
6 |
7 | func (a indexStore) Len() int { return len(a) }
8 | func (a indexStore) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
9 | func (a indexStore) Less(i, j int) bool { return a[i].Order < a[j].Order }
10 |
11 | type primaryKeyStore []*model.FieldType
12 |
13 | func (a primaryKeyStore) Len() int { return len(a) }
14 | func (a primaryKeyStore) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
15 | func (a primaryKeyStore) Less(i, j int) bool {
16 | return a[i].PrimaryKeyInfo.Order < a[j].PrimaryKeyInfo.Order
17 | }
18 |
--------------------------------------------------------------------------------
/gateway/modules/types.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/graphql-go/graphql/language/ast"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/model"
9 | "github.com/spaceuptech/space-cloud/gateway/utils"
10 | )
11 |
12 | // RealtimeInterface is used to mock the realtime module
13 | type RealtimeInterface interface {
14 | RemoveClient(clientID string)
15 | Subscribe(clientID string, data *model.RealtimeRequest, sendFeed model.SendFeed) ([]*model.FeedData, error)
16 | Unsubscribe(ctx context.Context, data *model.RealtimeRequest, clientID string) error
17 |
18 | HandleRealtimeEvent(ctxRoot context.Context, eventDoc *model.CloudEventPayload) error
19 | ProcessRealtimeRequests(ctx context.Context, eventDoc *model.CloudEventPayload) error
20 | }
21 |
22 | // GraphQLInterface is used to mock the graphql module
23 | type GraphQLInterface interface {
24 | GetDBAlias(ctx context.Context, field *ast.Field, token string, store utils.M) (string, error)
25 | ExecGraphQLQuery(ctx context.Context, req *model.GraphQLRequest, token string, cb model.GraphQLCallback)
26 | }
27 |
--------------------------------------------------------------------------------
/gateway/modules/userman/user.go:
--------------------------------------------------------------------------------
1 | package userman
2 |
3 | import (
4 | "encoding/base64"
5 | "sync"
6 |
7 | "github.com/spaceuptech/space-cloud/gateway/config"
8 | "github.com/spaceuptech/space-cloud/gateway/model"
9 | )
10 |
11 | // Module is responsible for user management
12 | type Module struct {
13 | sync.RWMutex
14 | methods map[string]*config.AuthStub
15 | crud model.CrudUserInterface
16 | auth model.AuthUserInterface
17 |
18 | // auth module
19 | aesKey []byte
20 | }
21 |
22 | // Init creates a new instance of the user management object
23 | func Init(crud model.CrudUserInterface, auth model.AuthUserInterface) *Module {
24 | return &Module{crud: crud, auth: auth}
25 | }
26 |
27 | // SetConfig sets the config required by the user management module
28 | func (m *Module) SetConfig(auth config.Auths) {
29 | m.Lock()
30 | defer m.Unlock()
31 |
32 | m.methods = make(map[string]*config.AuthStub, len(auth))
33 |
34 | for _, v := range auth {
35 | m.methods[v.ID] = v
36 | }
37 | }
38 |
39 | // IsActive shows if a given method is active
40 | func (m *Module) IsActive(method string) bool {
41 | m.RLock()
42 | defer m.RUnlock()
43 |
44 | s, p := m.methods[method]
45 | return p && s.Enabled
46 | }
47 |
48 | // IsEnabled shows if the user management module is enabled
49 | func (m *Module) IsEnabled() bool {
50 | m.RLock()
51 | defer m.RUnlock()
52 |
53 | return len(m.methods) > 0
54 | }
55 |
56 | // SetProjectAESKey set aes key
57 | func (m *Module) SetProjectAESKey(aesKey string) error {
58 | m.Lock()
59 | defer m.Unlock()
60 |
61 | decodedAESKey, err := base64.StdEncoding.DecodeString(aesKey)
62 | if err != nil {
63 | return err
64 | }
65 | m.aesKey = decodedAESKey
66 | return nil
67 | }
68 |
--------------------------------------------------------------------------------
/gateway/scripts/docker-push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | docker rmi -f sc-gateway
3 | docker rmi -f spacecloudio/gateway:0.21.5
4 | docker rmi -f spacecloudio/gateway:latest
5 |
6 | docker build --no-cache -t sc-gateway .
7 |
8 | docker tag sc-gateway spacecloudio/gateway:0.21.5
9 | docker tag sc-gateway spacecloudio/gateway:latest
10 |
11 | docker push spacecloudio/gateway:0.21.5
12 | docker push spacecloudio/gateway:latest
13 |
--------------------------------------------------------------------------------
/gateway/server/handlers/clusters.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/spaceuptech/helpers"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/model"
9 | )
10 |
11 | // HandleCluster returns handler cluster
12 | func HandleCluster() http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | // Get the JWT token from header
15 | _ = helpers.Response.SendResponse(r.Context(), w, 501, model.Response{Error: "not implemented in open source"})
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/gateway/server/handlers/config_batch.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "net/http"
7 | "time"
8 |
9 | "github.com/spaceuptech/helpers"
10 |
11 | "github.com/spaceuptech/space-cloud/gateway/managers/admin"
12 | "github.com/spaceuptech/space-cloud/gateway/model"
13 | "github.com/spaceuptech/space-cloud/gateway/utils"
14 | )
15 |
16 | // HandleBatchApplyConfig applies all the config at once
17 | func HandleBatchApplyConfig(adminMan *admin.Manager) http.HandlerFunc {
18 | return func(w http.ResponseWriter, r *http.Request) {
19 | token := utils.GetTokenFromHeader(r)
20 |
21 | req := new(model.BatchSpecApplyRequest)
22 | _ = json.NewDecoder(r.Body).Decode(req)
23 | defer utils.CloseTheCloser(r.Body)
24 |
25 | ctx, cancel := context.WithTimeout(r.Context(), time.Duration(utils.DefaultContextTime)*time.Second)
26 | defer cancel()
27 |
28 | if err := adminMan.CheckIfAdmin(ctx, token); err != nil {
29 | _ = helpers.Response.SendErrorResponse(ctx, w, http.StatusUnauthorized, err)
30 | return
31 | }
32 |
33 | for _, specObject := range req.Specs {
34 | if err := utils.ApplySpec(ctx, token, "http://localhost:4122", specObject); err != nil {
35 | _ = helpers.Response.SendErrorResponse(ctx, w, http.StatusBadRequest, err)
36 | return
37 | }
38 | }
39 |
40 | _ = helpers.Response.SendOkayResponse(ctx, http.StatusOK, w)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/gateway/server/handlers/health_check.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | "time"
7 |
8 | "github.com/spaceuptech/helpers"
9 |
10 | "github.com/spaceuptech/space-cloud/gateway/managers/syncman"
11 | )
12 |
13 | // HandleHealthCheck check health of gateway
14 | func HandleHealthCheck(syncMan *syncman.Manager) http.HandlerFunc {
15 | return func(w http.ResponseWriter, r *http.Request) {
16 |
17 | ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
18 | defer cancel()
19 |
20 | if err := syncMan.HealthCheck(); err != nil {
21 | _ = helpers.Response.SendErrorResponse(ctx, w, http.StatusInternalServerError, err)
22 | return
23 | }
24 |
25 | _ = helpers.Response.SendOkayResponse(ctx, http.StatusOK, w)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/gateway/server/handlers/mission_control.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "net/http"
5 | "os"
6 | "strings"
7 |
8 | "github.com/spaceuptech/space-cloud/gateway/utils"
9 | )
10 |
11 | // HandleMissionControl hosts the static resources for mission control
12 | func HandleMissionControl(staticPath string) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | url := r.URL.Path
15 |
16 | defer utils.CloseTheCloser(r.Body)
17 |
18 | path := strings.TrimPrefix(url, "/mission-control")
19 | if !strings.HasPrefix(path, "/") {
20 | path = "/" + path
21 | }
22 |
23 | path = staticPath + path
24 |
25 | // Check if path exists
26 | if fileInfo, err := os.Stat(path); !os.IsNotExist(err) {
27 | // If path exists and is of type file then serve that file
28 | if !fileInfo.IsDir() {
29 | http.ServeFile(w, r, path)
30 | return
31 | }
32 | // Else if a index file exists within that folder serve that index file
33 | path = strings.TrimSuffix(path, "/")
34 | if _, err := os.Stat(path + "/index.html"); !os.IsNotExist(err) {
35 | http.ServeFile(w, r, path+"/index.html")
36 | return
37 | }
38 | }
39 |
40 | // If path does not exists serve the root index file
41 | http.ServeFile(w, r, strings.TrimSuffix(staticPath, "/")+"/index.html")
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/gateway/server/handlers/schema.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
--------------------------------------------------------------------------------
/gateway/server/http.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "net/http"
5 | "strings"
6 |
7 | "github.com/spaceuptech/space-cloud/gateway/utils"
8 | )
9 |
10 | func (s *Server) restrictDomainMiddleware(restrictedHosts []string, h http.Handler) http.Handler {
11 | routingHandler := s.modules.Routing().HandleRoutes(s.modules)
12 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13 |
14 | // Get the url and host parameters
15 | url := r.URL.Path
16 | host := strings.Split(r.Host, ":")[0]
17 |
18 | // Check if host belongs does not belong to restricted host
19 | if !utils.StringExists(restrictedHosts, "*") && !utils.StringExists(restrictedHosts, host) {
20 | // We are here means we just got an excluded host. The config, runner and mission-control routes need to be hidden in this case.
21 | // So we'll forward the request straight to the routing handler.
22 | if strings.HasPrefix(url, "/v1/config") || strings.HasPrefix(url, "/v1/runner") || strings.HasPrefix(url, "/mission-control") {
23 | // Forward the request to the routing handler and don't forget to return
24 | routingHandler(w, r)
25 | return
26 | }
27 | // We are here means that the request is not a config, runner or mission-control path. Hence we can safely use the main handler to serve the request.
28 | // Since the main handler includes the routing handler, we need not worry about routing requests
29 | }
30 | h.ServeHTTP(w, r)
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/gateway/server/middleware.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "bytes"
5 | "io/ioutil"
6 | "net/http"
7 |
8 | "github.com/segmentio/ksuid"
9 | "github.com/spaceuptech/helpers"
10 | )
11 |
12 | func loggerMiddleWare(next http.Handler) http.Handler {
13 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
14 |
15 | requestID := r.Header.Get(helpers.HeaderRequestID)
16 | if requestID == "" {
17 | // set a new request id header of request
18 | requestID = ksuid.New().String()
19 | r.Header.Set(helpers.HeaderRequestID, requestID)
20 | }
21 |
22 | var reqBody []byte
23 | if r.Header.Get("Content-Type") == "application/json" {
24 | reqBody, _ = ioutil.ReadAll(r.Body)
25 | r.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody))
26 | }
27 |
28 | helpers.Logger.LogInfo(requestID, "Request", map[string]interface{}{"method": r.Method, "url": r.URL.Path, "queryVars": r.URL.Query(), "body": string(reqBody)})
29 | next.ServeHTTP(w, r.WithContext(helpers.CreateContext(r)))
30 |
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/gateway/utils/client/client.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/gorilla/websocket"
7 | "github.com/segmentio/ksuid"
8 |
9 | "github.com/spaceuptech/space-cloud/gateway/model"
10 | )
11 |
12 | // Client is the inteface for the websocket and grpc sockets
13 | type Client interface {
14 | Write(res *model.Message)
15 | Read(cb DataCallback)
16 | RoutineWrite(ctx context.Context)
17 | ClientID() string
18 | Close()
19 | Context() context.Context
20 | }
21 |
22 | // DataCallback is the callback invoked when data is read by the socket
23 | type DataCallback func(data *model.Message) bool
24 |
25 | // CreateWebsocketClient makes a client object to manage the socket
26 | func CreateWebsocketClient(socket *websocket.Conn) *WebsocketClient {
27 | channel := make(chan *model.Message, 5)
28 | ctx, cancel := context.WithCancel(context.Background())
29 | id := ksuid.New().String()
30 | return &WebsocketClient{id, channel, ctx, cancel, socket}
31 | }
32 |
--------------------------------------------------------------------------------
/gateway/utils/errors.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "errors"
4 |
5 | // ErrInvalidParams is thrown when the input parameters for an operation are invalid
6 | var ErrInvalidParams = errors.New("Invalid parameter provided")
7 |
8 | // ErrDatabaseDisabled is thrown when an operation is requested on a disabled database
9 | var ErrDatabaseDisabled = errors.New("Database is disabled. Please enable it")
10 |
11 | // ErrUnsupportedDatabase is thrown when an invalid db type is provided
12 | var ErrUnsupportedDatabase = errors.New("Unsupported database. Make sure your database type is correct")
13 |
14 | // ErrDatabaseConnection is thrown when SC was unable to connect to the requested database
15 | var ErrDatabaseConnection = errors.New("Could not connect to database. Make sure it is up and connection string provided to SC is correct")
16 |
--------------------------------------------------------------------------------
/gateway/utils/eventing.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | const (
4 | // TableEventingLogs is a variable for "event_logs"
5 | TableEventingLogs string = "event_logs"
6 | // TableInvocationLogs is a variable for "invocation_logs"
7 | TableInvocationLogs string = "invocation_logs"
8 | // SchemaInvocationLogs is a variable for invocaton schema
9 | SchemaInvocationLogs string = `type invocation_logs {
10 | _id: ID! @primary
11 | event_id: ID! @foreign(table: "event_logs", field: "_id")
12 | invocation_time: DateTime! @createdAt
13 | request_payload: String
14 | response_status_code: Integer
15 | response_body: String
16 | error_msg: String
17 | remark: String
18 | }`
19 | // SchemaEventLogs is a variable for event schema
20 | SchemaEventLogs string = `type event_logs {
21 | _id: ID! @primary
22 | batchid: String
23 | type: String
24 | rule_name: String
25 | token: Integer
26 | ts: DateTime
27 | event_ts: DateTime @createdAt
28 | payload: String
29 | status: String
30 | remark: String
31 | trigger_type: ID @size(value: 10)
32 | invocations: [invocation_logs]! @link(table: "invocation_logs", from: "_id", to: "event_id")
33 | }`
34 | )
35 |
--------------------------------------------------------------------------------
/gateway/utils/graphql/delete.go:
--------------------------------------------------------------------------------
1 | package graphql
2 |
3 | import (
4 | "context"
5 | "strings"
6 |
7 | "github.com/graphql-go/graphql/language/ast"
8 |
9 | "github.com/spaceuptech/space-cloud/gateway/model"
10 | "github.com/spaceuptech/space-cloud/gateway/utils"
11 | )
12 |
13 | func (graph *Module) genrateDeleteReq(ctx context.Context, field *ast.Field, token string, store map[string]interface{}) (model.RequestParams, *model.AllRequest, error) {
14 | dbAlias, err := graph.GetDBAlias(ctx, field, token, store)
15 | if err != nil {
16 | return model.RequestParams{}, nil, err
17 | }
18 | col := strings.TrimPrefix(field.Name.Value, "delete_")
19 |
20 | req, err := generateDeleteRequest(ctx, field, store)
21 | if err != nil {
22 | return model.RequestParams{}, nil, err
23 | }
24 |
25 | _, err = graph.auth.IsDeleteOpAuthorised(ctx, graph.project, dbAlias, col, token, req)
26 | if err != nil {
27 | return model.RequestParams{}, nil, err
28 | }
29 | return model.RequestParams{}, generateDeleteAllRequest(req), nil
30 |
31 | }
32 |
33 | func generateDeleteAllRequest(req *model.DeleteRequest) *model.AllRequest {
34 | return &model.AllRequest{Operation: req.Operation, Find: req.Find}
35 | }
36 |
37 | func generateDeleteRequest(ctx context.Context, field *ast.Field, store utils.M) (*model.DeleteRequest, error) {
38 | var err error
39 |
40 | // Create a delete request object
41 | deleteRequest := model.DeleteRequest{Operation: utils.All}
42 |
43 | deleteRequest.Find, err = ExtractWhereClause(field.Arguments, store)
44 | if err != nil {
45 | return nil, err
46 | }
47 |
48 | return &deleteRequest, nil
49 | }
50 |
--------------------------------------------------------------------------------
/gateway/utils/leader/operations.go:
--------------------------------------------------------------------------------
1 | package leader
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/go-redis/redis/v8"
7 | )
8 |
9 | // GetLeaderNodeID gets leader node id
10 | func (s *Module) GetLeaderNodeID(ctx context.Context) (string, error) {
11 | s.lock.Lock()
12 | defer s.lock.Unlock()
13 |
14 | value, err := s.pubsubClient.GetKey(ctx, leaderElectionRedisKey)
15 | if err != nil {
16 | return "", err
17 | }
18 |
19 | return value, nil
20 | }
21 |
22 | // IsLeader checks if the provided node id is the leader gateway or not
23 | func (s *Module) IsLeader(ctx context.Context, nodeID string) (bool, error) {
24 | s.lock.Lock()
25 | defer s.lock.Unlock()
26 |
27 | value, err := s.pubsubClient.GetKey(ctx, leaderElectionRedisKey)
28 | if err == redis.Nil {
29 | return false, nil
30 | } else if err != nil {
31 | return false, err
32 | }
33 |
34 | return nodeID == value, nil
35 | }
36 |
--------------------------------------------------------------------------------
/gateway/utils/mask.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "context"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "crypto/sha256"
8 | "encoding/base64"
9 |
10 | "github.com/spaceuptech/helpers"
11 | )
12 |
13 | // HashString hashes a string value and base64 encodes the result
14 | func HashString(stringValue string) string {
15 | h := sha256.New()
16 | _, _ = h.Write([]byte(stringValue))
17 | return base64.StdEncoding.EncodeToString(h.Sum(nil))
18 | }
19 |
20 | // Encrypt encrypts a string and base64 encodes the result
21 | func Encrypt(aesKey []byte, value string) (string, error) {
22 | encrypted := make([]byte, len(value))
23 | if err := encryptAESCFB(encrypted, []byte(value), aesKey, aesKey[:aes.BlockSize]); err != nil {
24 | return "", helpers.Logger.LogError(helpers.GetRequestID(context.TODO()), "Unable to encrypt value in match encrypt", err, nil)
25 | }
26 | return base64.StdEncoding.EncodeToString(encrypted), nil
27 | }
28 |
29 | func encryptAESCFB(dst, src, key, iv []byte) error {
30 | aesBlockEncrypter, err := aes.NewCipher([]byte(key))
31 | if err != nil {
32 | return err
33 | }
34 | aesEncrypter := cipher.NewCFBEncrypter(aesBlockEncrypter, iv)
35 | aesEncrypter.XORKeyStream(dst, src)
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/gateway/utils/mast_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "crypto/aes"
5 | "encoding/base64"
6 | "reflect"
7 | "testing"
8 | )
9 |
10 | func Test_encryptAESCFB(t *testing.T) {
11 | type args struct {
12 | dst []byte
13 | src []byte
14 | key []byte
15 | iv []byte
16 | }
17 | tests := []struct {
18 | name string
19 | args args
20 | want []byte
21 | wantErr bool
22 | }{
23 | {
24 | name: "invalid key",
25 | args: args{dst: make([]byte, len("username1")), src: []byte("username1"), key: []byte("invalidKey"), iv: []byte("invalidKey123456")[:aes.BlockSize]},
26 | wantErr: true,
27 | },
28 | {
29 | name: "encryption takes place",
30 | args: args{dst: make([]byte, len("username1")), src: []byte("username1"), key: base64DecodeString("Olw6AhA/GzSxfhwKLxO7JJsUL6VUwwGEFTgxzoZPy9g="), iv: []byte(base64DecodeString("Olw6AhA/GzSxfhwKLxO7JJsUL6VUwwGEFTgxzoZPy9g="))[:aes.BlockSize]},
31 | },
32 | }
33 | for _, tt := range tests {
34 | t.Run(tt.name, func(t *testing.T) {
35 | if err := encryptAESCFB(tt.args.dst, tt.args.src, tt.args.key, tt.args.iv); (err != nil) != tt.wantErr {
36 | t.Errorf("encryptAESCFB() error = %v, wantErr %v", err, tt.wantErr)
37 | }
38 | if !tt.wantErr && reflect.DeepEqual(tt.args.dst, tt.args.src) {
39 | t.Errorf("encryptAESCFB() encryption did not take place")
40 | }
41 | })
42 | }
43 | }
44 |
45 | func base64DecodeString(key string) []byte {
46 | decodedKey, _ := base64.StdEncoding.DecodeString(key)
47 | return decodedKey
48 | }
49 |
--------------------------------------------------------------------------------
/gateway/utils/pubsub/helpers.go:
--------------------------------------------------------------------------------
1 | package pubsub
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/go-redis/redis/v8"
7 | )
8 |
9 | type subscription struct {
10 | ch <-chan *redis.Message
11 | pubsub *redis.PubSub
12 | }
13 |
14 | func (m *Module) getTopicName(topic string) string {
15 | return fmt.Sprintf("%s-%s", m.projectID, topic)
16 | }
17 |
--------------------------------------------------------------------------------
/gateway/utils/pubsub/pubsub.go:
--------------------------------------------------------------------------------
1 | package pubsub
2 |
3 | import (
4 | "context"
5 | "sync"
6 | "time"
7 |
8 | "github.com/go-redis/redis/v8"
9 | )
10 |
11 | // Module deals with pub sub related activities
12 | type Module struct {
13 | lock sync.Mutex
14 |
15 | // Redis client
16 | client *redis.Client
17 |
18 | // Internal variables
19 | projectID string
20 | mapping map[string]*subscription
21 | }
22 |
23 | // New creates a new instance of the client
24 | func New(projectID, conn string) (*Module, error) {
25 | // Set a default connection string if not provided
26 | if conn == "" {
27 | conn = "localhost:6379"
28 | }
29 |
30 | c := redis.NewClient(&redis.Options{
31 | Addr: conn,
32 | Password: "",
33 | DB: 0,
34 | })
35 |
36 | // Create a temporary context
37 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
38 | defer cancel()
39 |
40 | if _, err := c.Ping(ctx).Result(); err != nil {
41 | return nil, err
42 | }
43 |
44 | return &Module{client: c, projectID: projectID, mapping: map[string]*subscription{}}, nil
45 | }
46 |
47 | // Close closes the redis client along with the active subscriptions on it
48 | func (m *Module) Close() {
49 | m.lock.Lock()
50 | defer m.lock.Unlock()
51 |
52 | // Close all active subscriptions first
53 | for _, sub := range m.mapping {
54 | _ = sub.pubsub.Close()
55 | }
56 | m.mapping = map[string]*subscription{}
57 |
58 | // Close the redis client
59 | _ = m.client.Close()
60 | }
61 |
--------------------------------------------------------------------------------
/gateway/utils/time.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/spaceuptech/helpers"
9 | )
10 |
11 | // CheckParse checks if the string can be parsed or not
12 | func CheckParse(s string) (time.Time, error) {
13 | var value time.Time
14 | var err error
15 | value, err = time.Parse(time.RFC3339Nano, s)
16 | if err != nil {
17 | value, err = time.Parse("2006-01-02", s)
18 | if err != nil {
19 | return time.Time{}, helpers.Logger.LogError(helpers.GetRequestID(context.TODO()), fmt.Sprintf("Invalid date format (%s) provided", s), nil, nil)
20 | }
21 | }
22 | return value, nil
23 | }
24 |
--------------------------------------------------------------------------------
/install-manifests/docker/mysql/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 | services:
3 | gateway:
4 | image: "spacecloudio/gateway:0.21.5"
5 | pull_policy: "if_not_present" # other values never, if_not_present
6 | restart: "always" # other values no, on-failure
7 | environment:
8 | - DEV=true # Turn this to false for production mode
9 | - CLUSTER_ID=prod-cluster
10 | - CONFIG=/config/config.yaml
11 | - ADMIN_USER=admin # Log in username
12 | - ADMIN_PASS=1234 # Log in password
13 | - ADMIN_SECRET=some-secret # Space cloud uses this secret for parsing jwt tokens for config APIs
14 | - LOG_LEVEL=debug # other values info, warn
15 | - LOG_FORMAT=json # other values text
16 | - DISABLE_UI=false
17 | - LETSENCRYPT_STORE=local
18 | - REDIS_CONN=redis:6379
19 | - SSL_ENABLE=false
20 | - SSL_CERT=""
21 | - SSL_KEY=""
22 | volumes:
23 | - ./sc-config:/config
24 | depends_on:
25 | - redis
26 | - mysql
27 | ports:
28 | - "4122:4122"
29 |
30 | redis:
31 | image: "redis:6.0"
32 |
33 | debezium:
34 | image: "spacecloudio/dbevents:0.2.0"
35 | environment:
36 | - "SC_ADMIN_SECRET=some-secret"
37 | - "GATEWAY_URL=gateway:4122"
38 | depends_on:
39 | - gateway
40 | - mysql
41 |
42 | mysql:
43 | image: mysql:8
44 | environment:
45 | - MYSQL_ROOT_PASSWORD=my-secret-pw
46 | volumes:
47 | - sc-mysql-data:/var/lib/mysql
48 |
49 | volumes:
50 | sc-mysql-data:
--------------------------------------------------------------------------------
/install-manifests/docker/sql-server/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 | services:
3 | gateway:
4 | image: "spacecloudio/gateway:0.21.5"
5 | pull_policy: "if_not_present" # other values never, if_not_present
6 | restart: "always" # other values no, on-failure
7 | environment:
8 | - DEV=true # Turn this to false for production mode
9 | - CLUSTER_ID=prod-cluster
10 | - CONFIG=/config/config.yaml
11 | - ADMIN_USER=admin # Log in username
12 | - ADMIN_PASS=1234 # Log in password
13 | - ADMIN_SECRET=some-secret # Space cloud uses this secret for parsing jwt tokens for config APIs
14 | - LOG_LEVEL=debug # other values info, warn
15 | - LOG_FORMAT=json # other values text
16 | - DISABLE_UI=false
17 | - LETSENCRYPT_STORE=local
18 | - REDIS_CONN=redis:6379
19 | - SSL_ENABLE=false
20 | - SSL_CERT=""
21 | - SSL_KEY=""
22 | volumes:
23 | - ./sc-config:/config
24 | depends_on:
25 | - redis
26 | - sql-server
27 | ports:
28 | - "4122:4122"
29 |
30 | redis:
31 | image: "redis:6.0"
32 |
33 | sql-server:
34 | image: "mcr.microsoft.com/mssql/server:latest"
35 | pull_policy: "if_not_present" # other values never, if_not_present
36 | restart: "always" # other values no, on-failure
37 | environment:
38 | - ACCEPT_EULA=sa # Log in username
39 | - SA_PASSWORD=yourStrong(!)Password # Log in password
40 | - MSSQL_AGENT_ENABLED=true
41 | - MSSQL_PID=Standard
--------------------------------------------------------------------------------
/install-manifests/helm/mongo/.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 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
--------------------------------------------------------------------------------
/install-manifests/helm/mongo/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v3
2 | name: mongo
3 | version: 0.1.0 # Chart version
4 | description: NoSQL document-oriented database that stores JSON-like documents with dynamic schemas, simplifying the integration of data in content-driven applications.
5 | type: application
6 | keywords:
7 | - mongodb
8 | - database
9 | - nosql
10 | - cluster
11 | - replicaset
12 | - replication
13 | home: https://spaceuptech.com/
14 | sources:
15 | - https://github.com/spaceuptech/space-cloud/tree/master/install-manifests/helm/mongo
16 |
17 |
--------------------------------------------------------------------------------
/install-manifests/helm/mongo/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for mongo
2 | # This is a YAML-formatted file.
3 | # Declare variables to be passed into your templates.
4 |
5 | credentials:
6 | password: pass
7 | username: user
8 |
9 | # name used for creating kubernetes resources
10 | name: "mongo"
11 |
12 | # Storage size of mysql
13 | size: "10Gi"
14 |
15 | image:
16 | name: "mongo"
17 | tag: "4.4"
18 | pullPolicy: IfNotPresent # IfNotPresent | Always
19 |
20 | resources:
21 | requests:
22 | memory: "256Mi"
23 | cpu: "250m"
24 | limits:
25 | memory: "512Mi"
26 | cpu: "500m"
--------------------------------------------------------------------------------
/install-manifests/helm/mysql/.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 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
--------------------------------------------------------------------------------
/install-manifests/helm/mysql/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v3
2 | name: mysql
3 | version: 0.1.0 # Chart version
4 | description: Fast, reliable, scalable, and easy to use open-source relational database system.
5 | type: application
6 | keywords:
7 | - mysql
8 | - database
9 | - sql
10 | home: https://spaceuptech.com/
11 | sources:
12 | - https://github.com/spaceuptech/space-cloud/tree/master/install-manifests/helm/mysql
13 |
--------------------------------------------------------------------------------
/install-manifests/helm/mysql/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for mysql.
2 | # This is a YAML-formatted file.
3 | # Declare variables to be passed into your templates.
4 |
5 | credentials:
6 | password: my-secret-pw
7 |
8 | # Name used for creating kubernetes resources
9 | name: "mysql"
10 |
11 | # Storage size of mysql
12 | size: "10Gi"
13 |
14 | image:
15 | name: mysql
16 | tag: "8.0"
17 | pullPolicy: IfNotPresent
18 |
19 | resources:
20 | requests:
21 | memory: "256Mi"
22 | cpu: "250m"
23 | limits:
24 | memory: "512Mi"
25 | cpu: "500m"
--------------------------------------------------------------------------------
/install-manifests/helm/postgres/.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 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
--------------------------------------------------------------------------------
/install-manifests/helm/postgres/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v3
2 | name: postgres
3 | version: 0.1.0 # Chart version
4 | description: Chart for PostgreSQL, an object-relational database management system (ORDBMS) with an emphasis on extensibility and on standards-compliance.
5 | type: application
6 | keywords:
7 | - postgresql
8 | - postgres
9 | - database
10 | - sql
11 | - replication
12 | - cluster
13 | home: https://spaceuptech.com/
14 | sources:
15 | - https://github.com/spaceuptech/space-cloud/tree/master/install-manifests/helm/postgres
16 |
--------------------------------------------------------------------------------
/install-manifests/helm/postgres/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for mongo
2 | # This is a YAML-formatted file.
3 | # Declare variables to be passed into your templates.
4 |
5 | credentials:
6 | password: mysecretpassword
7 | username: postgres
8 |
9 | # name used for creating kubernetes resources
10 | dbAlias: "postgres"
11 |
12 | # Storage size of mysql
13 | size: "10Gi"
14 |
15 | image:
16 | name: "postgres"
17 | tag: "latest"
18 | pullPolicy: IfNotPresent # IfNotPresent | Always
19 |
20 | resources:
21 | requests:
22 | memory: "256Mi"
23 | cpu: "250m"
24 | limits:
25 | memory: "512Mi"
26 | cpu: "500m"
--------------------------------------------------------------------------------
/install-manifests/helm/space-cloud/.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 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
--------------------------------------------------------------------------------
/install-manifests/helm/space-cloud/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v3
2 | name: space-cloud
3 | version: 0.21.5 # Chart version
4 | appVersion: 0.21.5 # Space Cloud version
5 | description: Helm Chart to install Space Cloud
6 | type: application
7 | keywords:
8 | - baaS
9 | home: https://spaceuptech.com/
10 | sources:
11 | - https://github.com/spaceuptech/space-cloud/tree/master/install-manifests/helm/space-cloud
12 |
--------------------------------------------------------------------------------
/install-manifests/helm/space-cloud/templates/01-core.yaml:
--------------------------------------------------------------------------------
1 | #############################################################################################
2 | ################################ Set some global parameters ################################
3 | #############################################################################################
4 | apiVersion: "security.istio.io/v1beta1"
5 | kind: "PeerAuthentication"
6 | metadata:
7 | name: "default"
8 | namespace: "istio-system"
9 | spec:
10 | mtls:
11 | mode: STRICT
12 | ---
13 | apiVersion: v1
14 | kind: Namespace
15 | metadata:
16 | name: space-cloud
17 | labels:
18 | istio-injection: enabled
19 | ---
20 | apiVersion: security.istio.io/v1beta1
21 | kind: AuthorizationPolicy
22 | metadata:
23 | name: deny-all
24 | namespace: default
25 | spec:
26 | {}
27 | ---
--------------------------------------------------------------------------------
/install-manifests/helm/sqlserver/.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 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
--------------------------------------------------------------------------------
/install-manifests/helm/sqlserver/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v3
2 | name: sqlserver
3 | version: 0.1.0 # Chart version
4 | description: SQL Server Helm Chart
5 | type: application
6 | keywords:
7 | - database
8 | home: https://spaceuptech.com/
9 | sources:
10 | - https://github.com/spaceuptech/space-cloud/tree/master/install-manifests/helm/sqlserver
11 |
--------------------------------------------------------------------------------
/install-manifests/helm/sqlserver/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for mongo
2 | # This is a YAML-formatted file.
3 | # Declare variables to be passed into your templates.
4 |
5 | credentials:
6 | password: yourStrong(!)Password
7 | username: sqlserver
8 |
9 | # name used for creating kubernetes resources
10 | name: "sqlserver"
11 |
12 | image:
13 | name: "mcr.microsoft.com/mssql/server"
14 | tag: "latest"
15 | pullPolicy: IfNotPresent # IfNotPresent | Always
16 |
17 | resources:
18 | requests:
19 | memory: "500Mi"
20 | cpu: "250m"
21 | limits:
22 | memory: "1000Mi"
23 | cpu: "500m"
--------------------------------------------------------------------------------
/install-manifests/kubernetes/01-core.yaml:
--------------------------------------------------------------------------------
1 | #############################################################################################
2 | ################################ Set some global parameters ################################
3 | #############################################################################################
4 | apiVersion: "security.istio.io/v1beta1"
5 | kind: "PeerAuthentication"
6 | metadata:
7 | name: "default"
8 | namespace: "istio-system"
9 | spec:
10 | mtls:
11 | mode: STRICT
12 | ---
13 | apiVersion: v1
14 | kind: Namespace
15 | metadata:
16 | name: space-cloud
17 | labels:
18 | istio-injection: enabled
19 | ---
20 | apiVersion: security.istio.io/v1beta1
21 | kind: AuthorizationPolicy
22 | metadata:
23 | name: deny-all
24 | namespace: default
25 | spec:
26 | {}
27 | ---
--------------------------------------------------------------------------------
/install-manifests/quick-start/docker-compose/mongo/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.6'
2 | services:
3 | mongo:
4 | image: mongo
5 | restart: always
6 | space-cloud:
7 | image: spaceuptech/space-cloud
8 | ports:
9 | - "4122:4122"
10 | - "4126:4126"
11 | depends_on:
12 | - "mongo"
13 | restart: always
14 | environment:
15 | ## The DEV environment lets you use Mission Control (Admin UI) without login
16 | ## Change the dev mode to false if you want a login to your Mission Control UI
17 | DEV: "true"
18 | ## Uncomment next lines to change the login credentials of Mission Control UI
19 | # ADMIN_USER: "admin"
20 | # ADMIN_PASS: "123"
21 | # ADMIN_SECRET: "some-secret" # This is the JWT secret used for login authentication in Mission Control
--------------------------------------------------------------------------------
/install-manifests/quick-start/docker-compose/mysql/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.6'
2 | services:
3 | mysql:
4 | image: mysql
5 | restart: always
6 | environment:
7 | MYSQL_ROOT_PASSWORD: 'my-secret-pw'
8 | space-cloud:
9 | image: spaceuptech/space-cloud
10 | ports:
11 | - "4122:4122"
12 | - "4126:4126"
13 | depends_on:
14 | - "mysql"
15 | restart: always
16 | environment:
17 | ## The DEV environment lets you use Mission Control (Admin UI) without login
18 | ## Change the dev mode to false if you want a login to your Mission Control UI
19 | DEV: "true"
20 | ## Uncomment next lines to change the login credentials of Mission Control UI
21 | # ADMIN_USER: "admin"
22 | # ADMIN_PASS: "123"
23 | # ADMIN_SECRET: "some-secret" # This is the JWT secret used for login authentication in Mission Control
--------------------------------------------------------------------------------
/install-manifests/quick-start/docker-compose/postgres/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.6'
2 | services:
3 | postgres:
4 | image: postgres
5 | restart: always
6 | space-cloud:
7 | image: spaceuptech/space-cloud
8 | ports:
9 | - "4122:4122"
10 | - "4126:4126"
11 | depends_on:
12 | - "postgres"
13 | restart: always
14 | environment:
15 | ## The DEV environment lets you use Mission Control (Admin UI) without login
16 | ## Change the dev mode to false if you want a login to your Mission Control UI
17 | DEV: "true"
18 | ## Uncomment next lines to change the login credentials of Mission Control UI
19 | # ADMIN_USER: "admin"
20 | # ADMIN_PASS: "123"
21 | # ADMIN_SECRET: "some-secret" # This is the JWT secret used for login authentication in Mission Control
--------------------------------------------------------------------------------
/runner/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.15.3-alpine3.12
2 | WORKDIR /build
3 | COPY . .
4 | #RUN apk --no-cache add build-base
5 | RUN GOOS=linux CGO_ENABLED=0 go build -a -ldflags '-s -w -extldflags "-static"' -o app .
6 |
7 | FROM alpine:3.12
8 | RUN apk --no-cache add ca-certificates
9 | WORKDIR /app
10 | COPY --from=0 /build/app .
11 | CMD ["./app", "start"]
--------------------------------------------------------------------------------
/runner/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/spaceuptech/space-cloud/runner
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/AlecAivazis/survey/v2 v2.0.7
7 | github.com/ghodss/yaml v1.0.0
8 | github.com/go-redis/redis/v8 v8.3.3
9 | github.com/go-test/deep v1.0.4
10 | github.com/gogo/protobuf v1.3.1
11 | github.com/golang-jwt/jwt v3.2.2+incompatible
12 | github.com/golang/protobuf v1.4.3
13 | github.com/gorilla/mux v1.7.4
14 | github.com/kedacore/keda v1.5.1-0.20201104115818-50bec808f8b4
15 | github.com/mitchellh/mapstructure v1.3.3
16 | github.com/prometheus/client_golang v1.8.0
17 | github.com/prometheus/common v0.14.0
18 | github.com/rs/cors v1.7.0
19 | github.com/segmentio/ksuid v1.0.2
20 | github.com/spaceuptech/helpers v0.2.1
21 | github.com/spaceuptech/space-api-go v0.17.3
22 | github.com/urfave/cli v1.22.2
23 | google.golang.org/grpc v1.31.1
24 | google.golang.org/protobuf v1.25.0
25 | istio.io/api v0.0.0-20200518203817-6d29a38039bd
26 | istio.io/client-go v0.0.0-20200521172153-8555211db875
27 | k8s.io/api v0.18.8
28 | k8s.io/apimachinery v0.18.8
29 | k8s.io/client-go v12.0.0+incompatible
30 | )
31 |
32 | replace k8s.io/client-go => k8s.io/client-go v0.18.8
33 |
--------------------------------------------------------------------------------
/runner/model/artifact.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | const (
4 | // ArtifactURL is the environment variable used to download the artifact from
5 | ArtifactURL string = "ARTIFACT_URL"
6 |
7 | // ArtifactToken is the token used to authenticate
8 | ArtifactToken string = "ARTIFACT_TOKEN"
9 |
10 | // ArtifactProject is the environment variable used to identify the project id of the service
11 | ArtifactProject string = "ARTIFACT_PROJECT"
12 |
13 | // ArtifactService is the environment variable used to identify the id of the service
14 | ArtifactService string = "ARTIFACT_SERVICE"
15 |
16 | // ArtifactVersion is the environment variable used to identify the version of the service
17 | ArtifactVersion string = "ARTIFACT_VERSION"
18 | )
19 |
--------------------------------------------------------------------------------
/runner/model/config.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "net/http"
5 |
6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7 | )
8 |
9 | // Config is a map with key = projectId-serviceId and the value being the routes([]Route)
10 | type Config map[string]Routes // key = projectId-serviceId
11 |
12 | // Response is the object returned by every handler to client
13 | type Response struct {
14 | Error string `json:"error,omitempty"`
15 | Result interface{} `json:"result,omitempty"`
16 | }
17 |
18 | // RequestParams describes the params passed down in every request
19 | type RequestParams struct {
20 | RequestID string `json:"requestId"`
21 | Resource string `json:"resource"`
22 | Op string `json:"op"`
23 | Attributes map[string]string `json:"attributes"`
24 | Headers http.Header `json:"headers"`
25 | Claims map[string]interface{} `json:"claims"`
26 | Method string `json:"method"`
27 | Path string `json:"path"`
28 | Payload interface{} `json:"payload"`
29 | }
30 |
31 | // LogRequest represent log request structure
32 | type LogRequest struct {
33 | TaskID string `json:"taskId"`
34 | ReplicaID string `json:"replicaId"`
35 | Since *int64 `json:"since"`
36 | SinceTime *metav1.Time `json:"sinceTime"`
37 | Tail *int64 `json:"tail"`
38 | IsFollow bool `json:"isFollow"`
39 | }
40 |
--------------------------------------------------------------------------------
/runner/model/driver.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // DriverType is used to describe which deployment target is to be used
4 | type DriverType string
5 |
6 | const (
7 | // TypeIstio is the driver type used to target istio on kubernetes
8 | TypeIstio DriverType = "istio"
9 |
10 | // TypeDocker is the driver type used to target docker
11 | TypeDocker DriverType = "docker"
12 | )
13 |
--------------------------------------------------------------------------------
/runner/model/environment.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // Project describes the configuration of a project
4 | type Project struct {
5 | ID string `json:"id" yaml:"id"`
6 | Kind string `json:"kind" yaml:"kind"`
7 | Environment string `json:"env" yaml:"env"`
8 | }
9 |
--------------------------------------------------------------------------------
/runner/model/eventing.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // CloudEventPayload is the the JSON event spec by Cloud Events Specification
4 | type CloudEventPayload struct {
5 | SpecVersion string `json:"specversion"`
6 | Type string `json:"type"`
7 | Source string `json:"source"`
8 | ID string `json:"id"`
9 | Time string `json:"time"`
10 | Data struct {
11 | Path string `json:"path"`
12 | Meta ServiceRequest `json:"meta"`
13 | } `json:"data"`
14 | }
15 |
16 | // ServiceRequest is the meta format of the meta data received from artifact store
17 | type ServiceRequest struct {
18 | IsDeploy bool `json:"isDeploy"`
19 | Service *Service `json:"service"`
20 | }
21 |
--------------------------------------------------------------------------------
/runner/model/key.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // PublicKeyPayload holds the pem encoded public key
4 | type PublicKeyPayload struct {
5 | PemData string `json:"pem"`
6 | }
7 |
--------------------------------------------------------------------------------
/runner/model/kubeSecret.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // Secret is an object for kubernetes! :|
4 | type Secret struct {
5 | ID string `json:"id" yaml:"id"`
6 | Type string `json:"type" yaml:"type"`
7 | RootPath string `json:"rootPath" yaml:"rootPath"`
8 | Data map[string]string `json:"data" yaml:"data"`
9 | }
10 |
11 | // SecretValue is the non-encoded secret value in the request body
12 | type SecretValue struct {
13 | Value string `json:"value" yaml:"value"`
14 | }
15 |
16 | // FileType is the secretType:file
17 | const FileType string = "file"
18 |
19 | // EnvType is the secretType:file
20 | const EnvType string = "env"
21 |
22 | // DockerType is the secretType:file
23 | const DockerType string = "docker"
24 |
--------------------------------------------------------------------------------
/runner/model/metrics.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // ServiceCallMetricHook logs apply service operation
4 | type ServiceCallMetricHook func(projectID string)
5 |
--------------------------------------------------------------------------------
/runner/model/proxy.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // ProxyMessage is the payload send by the proxy
4 | type ProxyMessage struct {
5 | ActiveRequests int32 `json:"active,omitempty"`
6 | Project string `json:"project,omitempty"`
7 | Service string `json:"service,omitempty"`
8 | NodeID string `json:"id,omitempty"`
9 | Version string `json:"version,omitempty"`
10 | }
11 |
12 | // EnvoyMetrics is the metrics collected from envoy
13 | type EnvoyMetrics struct {
14 | Stats []EnvoyStat `json:"stats"`
15 | }
16 |
17 | // EnvoyStat describes the stats received from envoy
18 | type EnvoyStat struct {
19 | Name string `json:"name"`
20 | Value uint64 `json:"value"`
21 | }
22 |
--------------------------------------------------------------------------------
/runner/model/pubsub.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/mitchellh/mapstructure"
7 | )
8 |
9 | // PubSubMessage describes the format of pubsub send message
10 | type PubSubMessage struct {
11 | ReplyTo string `json:"replyTo"`
12 | Payload interface{} `json:"payload"`
13 | }
14 |
15 | // Unmarshal parses the payload into the object provided
16 | func (m *PubSubMessage) Unmarshal(ptr interface{}) error {
17 | if m.Payload == nil {
18 | return errors.New("no payload has been provided")
19 | }
20 |
21 | return mapstructure.Decode(m.Payload, ptr)
22 | }
23 |
--------------------------------------------------------------------------------
/runner/model/roles.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // Role describes the configuration for the service role
4 | type Role struct {
5 | ID string `json:"id" yaml:"id"`
6 | Project string `json:"project" yaml:"project"`
7 | Service string `json:"service" yaml:"service"`
8 | Type string `json:"type" yaml:"type"` // Can be `project` or `cluster`
9 | Rules []Rule `json:"rules" yaml:"rules"`
10 | }
11 |
12 | // Rule describe rule for service role
13 | type Rule struct {
14 | APIGroups []string `json:"apiGroups" yaml:"apiGroups"`
15 | Verbs []string `json:"verbs" yaml:"verbs"`
16 | Resources []string `json:"resources" yaml:"resources"`
17 | }
18 |
19 | // ServiceRoleProject is used to provide Type Project to role
20 | const ServiceRoleProject string = "project"
21 |
22 | // ServiceRoleCluster is used to provide Type Cluster to role
23 | const ServiceRoleCluster string = "cluster"
24 |
--------------------------------------------------------------------------------
/runner/model/version.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // Version represents the current runner version
4 | const Version string = "0.21.5"
5 |
--------------------------------------------------------------------------------
/runner/modules/pubsub/helpers.go:
--------------------------------------------------------------------------------
1 | package pubsub
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/go-redis/redis/v8"
7 | )
8 |
9 | type subscription struct {
10 | ch <-chan *redis.Message
11 | pubsub *redis.PubSub
12 | }
13 |
14 | func (m *Module) getTopicName(topic string) string {
15 | return fmt.Sprintf("%s-%s", m.projectID, topic)
16 | }
17 |
--------------------------------------------------------------------------------
/runner/modules/pubsub/pubsub.go:
--------------------------------------------------------------------------------
1 | package pubsub
2 |
3 | import (
4 | "context"
5 | "sync"
6 | "time"
7 |
8 | "github.com/go-redis/redis/v8"
9 | )
10 |
11 | // Module deals with pub sub related activities
12 | type Module struct {
13 | lock sync.Mutex
14 |
15 | // Redis client
16 | client *redis.Client
17 |
18 | // Internal variables
19 | projectID string
20 | mapping map[string]*subscription
21 | }
22 |
23 | // New creates a new instance of the client
24 | func New(projectID, conn string) (*Module, error) {
25 | // Set a default connection string if not provided
26 | if conn == "" {
27 | conn = "localhost:6379"
28 | }
29 |
30 | c := redis.NewClient(&redis.Options{
31 | Addr: conn,
32 | Password: "",
33 | DB: 0,
34 | })
35 |
36 | // Create a temporary context
37 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
38 | defer cancel()
39 |
40 | if _, err := c.Ping(ctx).Result(); err != nil {
41 | return nil, err
42 | }
43 |
44 | return &Module{client: c, projectID: projectID, mapping: map[string]*subscription{}}, nil
45 | }
46 |
47 | // Close closes the redis client along with the active subscriptions on it
48 | func (m *Module) Close() {
49 | m.lock.Lock()
50 | defer m.lock.Unlock()
51 |
52 | // Close all active subscriptions first
53 | for _, sub := range m.mapping {
54 | _ = sub.pubsub.Close()
55 | }
56 | m.mapping = map[string]*subscription{}
57 |
58 | // Close the redis client
59 | _ = m.client.Close()
60 | }
61 |
--------------------------------------------------------------------------------
/runner/modules/pubsub/store.go:
--------------------------------------------------------------------------------
1 | package pubsub
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/go-redis/redis/v8"
8 | )
9 |
10 | // CheckAndSet sets the key value pair and returns if the key already existed or not
11 | func (m *Module) CheckAndSet(ctx context.Context, key, value string, ttl time.Duration) (bool, error) {
12 | // Flag to check if the value already existed
13 | exists := true
14 |
15 | // See if the key exists
16 | key = m.getTopicName(key)
17 | err := m.client.Get(ctx, key).Err()
18 | if err != nil && err != redis.Nil {
19 | return exists, err
20 | }
21 |
22 | // Mark key as exists
23 | if err == redis.Nil {
24 | exists = false
25 | }
26 |
27 | // Set the key value pair
28 | if err := m.client.Set(ctx, key, value, ttl).Err(); err != nil {
29 | return exists, err
30 | }
31 |
32 | return exists, nil
33 | }
34 |
35 | // CheckIfKeyExists checks if the key exists
36 | func (m *Module) CheckIfKeyExists(ctx context.Context, key string) (bool, error) {
37 | // See if the key exists
38 | key = m.getTopicName(key)
39 | err := m.client.Get(ctx, key).Err()
40 | if err != nil && err != redis.Nil {
41 | return false, err
42 | }
43 |
44 | // Mark key as exists
45 | if err == redis.Nil {
46 | return false, nil
47 | }
48 |
49 | return true, nil
50 | }
51 |
--------------------------------------------------------------------------------
/runner/modules/routing/actions.go:
--------------------------------------------------------------------------------
1 | package routing
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/helpers"
7 | "github.com/urfave/cli"
8 |
9 | "github.com/spaceuptech/space-cloud/runner/utils"
10 | )
11 |
12 | // ActionGenerateServiceRouting creates spec object for service routing
13 | func ActionGenerateServiceRouting(c *cli.Context) error {
14 | argsArr := c.Args()
15 | if len(argsArr) != 1 {
16 | return helpers.Logger.LogError(helpers.GetRequestID(context.TODO()), "incorrect number of arguments", nil, nil)
17 | }
18 | dbruleConfigFile := argsArr[0]
19 | dbrule, err := generateServiceRouting()
20 | if err != nil {
21 | return err
22 | }
23 |
24 | return utils.AppendConfigToDisk(dbrule, dbruleConfigFile)
25 | }
26 |
--------------------------------------------------------------------------------
/runner/modules/scaler/externalscaler/externalscaler.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package externalscaler;
4 | option go_package = ".;externalscaler";
5 |
6 | service ExternalScaler {
7 | rpc IsActive(ScaledObjectRef) returns (IsActiveResponse) {}
8 | rpc StreamIsActive(ScaledObjectRef) returns (stream IsActiveResponse) {}
9 | rpc GetMetricSpec(ScaledObjectRef) returns (GetMetricSpecResponse) {}
10 | rpc GetMetrics(GetMetricsRequest) returns (GetMetricsResponse) {}
11 | }
12 |
13 | message ScaledObjectRef {
14 | string name = 1;
15 | string namespace = 2;
16 | map scalerMetadata = 3;
17 | }
18 |
19 | message IsActiveResponse {
20 | bool result = 1;
21 | }
22 |
23 | message GetMetricSpecResponse {
24 | repeated MetricSpec metricSpecs = 1;
25 | }
26 |
27 | message MetricSpec {
28 | string metricName = 1;
29 | int64 targetSize = 2;
30 | }
31 |
32 | message GetMetricsRequest {
33 | ScaledObjectRef scaledObjectRef = 1;
34 | string metricName = 2;
35 | }
36 |
37 | message GetMetricsResponse {
38 | repeated MetricValue metricValues = 1;
39 | }
40 |
41 | message MetricValue {
42 | string metricName = 1;
43 | int64 metricValue = 2;
44 | }
45 |
--------------------------------------------------------------------------------
/runner/modules/scaler/operations.go:
--------------------------------------------------------------------------------
1 | package scaler
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net"
7 | "time"
8 |
9 | "google.golang.org/grpc"
10 |
11 | pb "github.com/spaceuptech/space-cloud/runner/modules/scaler/externalscaler"
12 | )
13 |
14 | // ScaleUp instructs keda to scale the service from 0 to 1
15 | func (s *Scaler) ScaleUp(ctx context.Context, project, service, version string) error {
16 | key := generateKey(project, service, version)
17 | // Check if service is active
18 | exists, err := s.pubsubClient.CheckAndSet(ctx, key, "service", 10*time.Second)
19 | if err != nil {
20 | return err
21 | }
22 |
23 | // Notify everyone to scale up the service
24 | if !exists {
25 | if err := s.pubsubClient.PublishString(ctx, "scale-up", key); err != nil {
26 | return err
27 | }
28 | }
29 |
30 | return nil
31 | }
32 |
33 | // Start begins the grpc server
34 | func (s *Scaler) Start() {
35 | // Start the internal routines
36 | go s.routineScaleUp()
37 |
38 | // Create a gRPC server
39 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 4060))
40 | if err != nil {
41 | panic("Unable to start grpc server: " + err.Error())
42 | }
43 |
44 | grpcServer := grpc.NewServer()
45 | pb.RegisterExternalScalerServer(grpcServer, s)
46 | if err := grpcServer.Serve(lis); err != nil {
47 | panic("Unable to start grpc server: " + err.Error())
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/runner/modules/scaler/prometheus.go:
--------------------------------------------------------------------------------
1 | package scaler
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/prometheus/common/model"
9 | )
10 |
11 | func (s *Scaler) queryPrometheus(ctx context.Context, project, service, version, scalingMode string) (int64, error) {
12 | // Extract the prometheus metric name
13 | var metricName string
14 | switch scalingMode {
15 | case "requests-per-second":
16 | metricName = "sc:total_requests:rate"
17 | case "active-requests":
18 | metricName = "sc:active_requests:avg"
19 | default:
20 | return 0, fmt.Errorf("invalid scalingMode (%s) provided", scalingMode)
21 | }
22 |
23 | query := preparePrometheusQuery(project, service, version, metricName, "30s")
24 | result, _, err := s.prometheusClient.Query(ctx, query, time.Now())
25 | if err != nil {
26 | return 0, err
27 | }
28 | vector := result.(model.Vector)
29 | if len(vector) == 0 {
30 | return 0, nil
31 | }
32 |
33 | return int64(vector[0].Value), nil
34 | }
35 |
36 | func preparePrometheusQuery(project, service, version, metric, duration string) string {
37 | return fmt.Sprintf("ceil(%s%s{kubernetes_namespace=\"%s\", app=\"%s\", version=\"%s\"})", metric, duration, project, service, version)
38 | }
39 |
--------------------------------------------------------------------------------
/runner/modules/scaler/routine.go:
--------------------------------------------------------------------------------
1 | package scaler
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | func (s *Scaler) routineScaleUp() {
8 | messages, err := s.pubsubClient.Subscribe(context.Background(), "scale-up")
9 | if err != nil {
10 | panic(err)
11 | }
12 |
13 | for msg := range messages {
14 | // Notify if the stream is present internally
15 | s.lock.RLock()
16 | if isActive, p := s.isActiveStreams[msg.Payload]; p {
17 | isActive.Notify()
18 | }
19 | s.lock.RUnlock()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/runner/modules/scaler/scaler.go:
--------------------------------------------------------------------------------
1 | package scaler
2 |
3 | import (
4 | "os"
5 | "sync"
6 |
7 | "github.com/prometheus/client_golang/api"
8 | v1 "github.com/prometheus/client_golang/api/prometheus/v1"
9 |
10 | "github.com/spaceuptech/space-cloud/runner/modules/pubsub"
11 | )
12 |
13 | // Scaler is responsible to implement an external-push scaler for keda
14 | type Scaler struct {
15 | lock sync.RWMutex
16 |
17 | // Map to store is active streams
18 | isActiveStreams map[string]*isActiveStream
19 |
20 | // Client drivers
21 | prometheusClient v1.API
22 | pubsubClient *pubsub.Module
23 | }
24 |
25 | // New creates an instance of the scaler object
26 | func New(prometheusAddr string) (*Scaler, error) {
27 | // Create a new prometheus client
28 | client, err := api.NewClient(api.Config{
29 | Address: prometheusAddr,
30 | })
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | // Create a new pubsub client
36 | pubsubClient, err := pubsub.New("runner", os.Getenv("REDIS_CONN"))
37 | if err != nil {
38 | return nil, err
39 | }
40 |
41 | return &Scaler{
42 | prometheusClient: v1.NewAPI(client),
43 | pubsubClient: pubsubClient,
44 | isActiveStreams: map[string]*isActiveStream{},
45 | }, nil
46 | }
47 |
--------------------------------------------------------------------------------
/runner/modules/secrets/actions.go:
--------------------------------------------------------------------------------
1 | package secrets
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/spaceuptech/helpers"
7 | "github.com/urfave/cli"
8 |
9 | "github.com/spaceuptech/space-cloud/runner/utils"
10 | )
11 |
12 | // ActionGenerateSecret creates spec object for service routing
13 | func ActionGenerateSecret(c *cli.Context) error {
14 | argsArr := c.Args()
15 | if len(argsArr) != 1 {
16 | return helpers.Logger.LogError(helpers.GetRequestID(context.TODO()), "Incorrect number of arguments", nil, nil)
17 | }
18 | dbruleConfigFile := argsArr[0]
19 | dbrule, err := generateSecrets()
20 | if err != nil {
21 | return err
22 | }
23 |
24 | return utils.AppendConfigToDisk(dbrule, dbruleConfigFile)
25 | }
26 |
--------------------------------------------------------------------------------
/runner/scripts/docker-push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | docker rmi -f sc-runner
3 | docker rmi -f spacecloudio/runner:0.21.5
4 | docker rmi -f spacecloudio/runner:latest
5 |
6 | docker build --no-cache -t sc-runner .
7 |
8 | docker tag sc-runner spacecloudio/runner:0.21.5
9 | docker tag sc-runner spacecloudio/runner:latest
10 |
11 | docker push spacecloudio/runner:0.21.5
12 | docker push spacecloudio/runner:latest
13 |
14 |
--------------------------------------------------------------------------------
/runner/server/config.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "github.com/spaceuptech/space-cloud/runner/utils/auth"
5 | "github.com/spaceuptech/space-cloud/runner/utils/driver"
6 | )
7 |
8 | // Config is the object required to configure the runner
9 | type Config struct {
10 | Port string
11 | ProxyPort string
12 | IsMetricDisabled bool
13 |
14 | // Configuration for the driver
15 | Driver *driver.Config
16 |
17 | // Configuration for the auth module
18 | Auth *auth.Config
19 | }
20 |
--------------------------------------------------------------------------------
/runner/server/handle_env.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/helpers"
8 |
9 | "github.com/spaceuptech/space-cloud/runner/model"
10 | "github.com/spaceuptech/space-cloud/runner/utils"
11 | )
12 |
13 | func (s *Server) handleGetClusterType() http.HandlerFunc {
14 | return func(w http.ResponseWriter, r *http.Request) {
15 |
16 | // Close the body of the request
17 | defer utils.CloseTheCloser(r.Body)
18 | // Verify token
19 | _, err := s.auth.VerifyToken(utils.GetToken(r))
20 | if err != nil {
21 | _ = helpers.Response.SendErrorResponse(r.Context(), w, http.StatusUnauthorized, err)
22 | return
23 | }
24 |
25 | w.WriteHeader(http.StatusOK)
26 | _ = json.NewEncoder(w).Encode(model.Response{Result: s.driver.Type()})
27 | }
28 | }
29 |
30 | func (s *Server) handleGetMetrics() http.HandlerFunc {
31 | return func(w http.ResponseWriter, r *http.Request) {
32 |
33 | // Close the body of the request
34 | defer utils.CloseTheCloser(r.Body)
35 | // Verify token
36 | _, err := s.auth.VerifyToken(utils.GetToken(r))
37 | if err != nil {
38 | _ = helpers.Response.SendErrorResponse(r.Context(), w, http.StatusUnauthorized, err)
39 | return
40 | }
41 |
42 | w.WriteHeader(http.StatusOK)
43 | _ = json.NewEncoder(w).Encode(model.Response{Result: s.metrics.LoadMetrics()})
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/runner/server/middlware.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "bytes"
5 | "io/ioutil"
6 | "net/http"
7 |
8 | "github.com/segmentio/ksuid"
9 | "github.com/spaceuptech/helpers"
10 | )
11 |
12 | func loggerMiddleWare(next http.Handler) http.Handler {
13 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
14 |
15 | requestID := r.Header.Get(helpers.HeaderRequestID)
16 | if requestID == "" {
17 | // set a new request id header of request
18 | requestID = ksuid.New().String()
19 | r.Header.Set(helpers.HeaderRequestID, requestID)
20 | }
21 |
22 | var reqBody []byte
23 | if r.Header.Get("Content-Type") == "application/json" {
24 | reqBody, _ = ioutil.ReadAll(r.Body)
25 | r.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody))
26 | }
27 |
28 | helpers.Logger.LogInfo(requestID, "Request", map[string]interface{}{"method": r.Method, "url": r.URL.Path, "queryVars": r.URL.Query(), "body": string(reqBody)})
29 | next.ServeHTTP(w, r.WithContext(helpers.CreateContext(r)))
30 |
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/runner/utils/auth/admin.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/golang-jwt/jwt"
7 | )
8 |
9 | // VerifyToken checks if the token is valid and returns the token claims
10 | func (m *Module) VerifyToken(token string) (map[string]interface{}, error) {
11 | m.lock.RLock()
12 | defer m.lock.RUnlock()
13 | if m.config.IsDev {
14 | return nil, nil
15 | }
16 | // Parse the JWT token
17 | tokenObj, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
18 | // Don't forget to validate the alg is what you expect it to be
19 | if token.Method.Alg() != jwt.SigningMethodHS256.Alg() {
20 | return nil, errors.New("invalid signing method")
21 | }
22 |
23 | // Return the key
24 | return []byte(m.config.Secret), nil
25 | })
26 | if err != nil {
27 | return nil, err
28 | }
29 |
30 | // Get the claims
31 | if claims, ok := tokenObj.Claims.(jwt.MapClaims); ok && tokenObj.Valid {
32 | tokenClaims := make(map[string]interface{}, len(claims))
33 | for key, val := range claims {
34 | tokenClaims[key] = val
35 | }
36 |
37 | return tokenClaims, nil
38 | }
39 |
40 | return nil, errors.New("token could not be verified")
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/runner/utils/auth/auth.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // Module manages the auth module
8 | type Module struct {
9 | lock sync.RWMutex
10 |
11 | // For internal use
12 | config *Config
13 | }
14 |
15 | // Config is the object used to configure the auth module
16 | type Config struct {
17 | // For authentication of runner and store
18 | Secret string
19 |
20 | // disable authentication while development
21 | IsDev bool
22 | }
23 |
24 | // JWTAlgorithm describes the jwt algorithm to use
25 | type JWTAlgorithm string
26 |
27 | const (
28 | // RSA256 is used for rsa256 algorithm
29 | RSA256 JWTAlgorithm = "rsa256"
30 |
31 | // HS256 is used for hs256 algorithm
32 | HS256 JWTAlgorithm = "hs256"
33 | )
34 |
35 | // New creates a new instance of the auth module
36 | func New(config *Config) (*Module, error) {
37 | m := &Module{config: config}
38 |
39 | return m, nil
40 | }
41 |
--------------------------------------------------------------------------------
/runner/utils/domains.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "fmt"
4 |
5 | // GetServiceDomain is used for getting the main service domain
6 | func GetServiceDomain(projectID, serviceID string) string {
7 | return fmt.Sprintf("%s.%s.svc.cluster.local", serviceID, projectID)
8 | }
9 |
10 | // GetInternalServiceDomain is used for getting internal service domain
11 | func GetInternalServiceDomain(projectID, serviceID, version string) string {
12 | return fmt.Sprintf("%s.%s-%s.svc.cluster.local", serviceID, projectID, version)
13 | }
14 |
--------------------------------------------------------------------------------
/runner/utils/driver/istio/config.go:
--------------------------------------------------------------------------------
1 | package istio
2 |
3 | // Config describes the configuration used by the istio driver
4 | type Config struct {
5 | IsInsideCluster bool
6 | KubeConfigPath string
7 | PrometheusAddr string
8 | ProxyPort uint32
9 | }
10 |
11 | // GenerateInClusterConfig returns a in-cluster config
12 | func GenerateInClusterConfig() *Config {
13 | return &Config{IsInsideCluster: true}
14 | }
15 |
16 | // GenerateOutsideClusterConfig returns an out-of-cluster config
17 | func GenerateOutsideClusterConfig(kubeConfigPath string) *Config {
18 | return &Config{IsInsideCluster: false, KubeConfigPath: kubeConfigPath}
19 | }
20 |
21 | // SetProxyPort sets the port of the proxy runner
22 | func (c *Config) SetProxyPort(port uint32) {
23 | c.ProxyPort = port
24 | }
25 |
26 | const runtimeEnvVariable string = "SC_RUNTIME"
27 |
--------------------------------------------------------------------------------
/runner/utils/driver/istio/projects.go:
--------------------------------------------------------------------------------
1 | package istio
2 |
3 | import (
4 | "context"
5 |
6 | v1 "k8s.io/api/core/v1"
7 | kubeErrors "k8s.io/apimachinery/pkg/api/errors"
8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9 |
10 | "github.com/spaceuptech/space-cloud/runner/model"
11 | )
12 |
13 | // CreateProject creates a new namespace for the client
14 | func (i *Istio) CreateProject(ctx context.Context, project *model.Project) error {
15 | // Set the kind field if empty
16 | if project.Kind == "" {
17 | project.Kind = "project"
18 | }
19 |
20 | namespace := project.ID
21 | ns := &v1.Namespace{
22 | ObjectMeta: metav1.ObjectMeta{
23 | Name: namespace,
24 | Labels: map[string]string{
25 | "istio-injection": "enabled",
26 | "app.kubernetes.io/name": namespace,
27 | "app.kubernetes.io/managed-by": "space-cloud",
28 | "space-cloud.io/kind": project.Kind,
29 | },
30 | },
31 | }
32 | _, err := i.kube.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
33 | if kubeErrors.IsAlreadyExists(err) {
34 | return nil
35 | }
36 | return err
37 | }
38 |
39 | // DeleteProject deletes a namespace for the client
40 | func (i *Istio) DeleteProject(ctx context.Context, projectID string) error {
41 | err := i.kube.CoreV1().Namespaces().Delete(ctx, projectID, metav1.DeleteOptions{})
42 | if kubeErrors.IsNotFound(err) {
43 | return nil
44 | }
45 | return err
46 | }
47 |
--------------------------------------------------------------------------------
/runner/utils/file.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 |
7 | "github.com/ghodss/yaml"
8 |
9 | "github.com/spaceuptech/space-cloud/runner/model"
10 | )
11 |
12 | // AppendConfigToDisk creates a yml file or appends to existing
13 | func AppendConfigToDisk(specObj *model.SpecObject, filename string) error {
14 | // Marshal spec object to yaml
15 | data, err := yaml.Marshal(specObj)
16 | if err != nil {
17 | return err
18 | }
19 |
20 | // Check if file exists. We need to ammend the file if it does.
21 | if fileExists(filename) {
22 | f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
23 | if err != nil {
24 | return err
25 | }
26 |
27 | defer func() {
28 | _ = f.Close()
29 | }()
30 |
31 | _, err = f.Write(append([]byte("---\n"), data...))
32 | return err
33 | }
34 |
35 | // Create a new file with out specs
36 | return ioutil.WriteFile(filename, data, 0755)
37 | }
38 | func fileExists(filename string) bool {
39 | info, err := os.Stat(filename)
40 | if os.IsNotExist(err) {
41 | return false
42 | }
43 | return !info.IsDir()
44 | }
45 |
--------------------------------------------------------------------------------
/runner/utils/http.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | "strings"
7 |
8 | "github.com/rs/cors"
9 | )
10 |
11 | // GetToken retrieves the json web token present in the request
12 | func GetToken(r *http.Request) (token string) {
13 | // Get the JWT token from header
14 | tokens, ok := r.Header["Authorization"]
15 | if !ok {
16 | tokens = []string{""}
17 | }
18 |
19 | return strings.TrimPrefix(tokens[0], "Bearer ")
20 | }
21 |
22 | // CloseTheCloser closes an io read closer while explicitly ignoring the error
23 | func CloseTheCloser(r io.Closer) {
24 | _ = r.Close()
25 | }
26 |
27 | // CreateCorsObject returns a new cors object
28 | func CreateCorsObject() *cors.Cors {
29 | return cors.New(cors.Options{
30 | AllowCredentials: true,
31 | AllowOriginFunc: func(s string) bool {
32 | return true
33 | },
34 | AllowedMethods: []string{"GET", "PUT", "POST", "DELETE"},
35 | AllowedHeaders: []string{"Authorization", "Content-Type"},
36 | ExposedHeaders: []string{"Authorization", "Content-Type"},
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/accounts/delete.go:
--------------------------------------------------------------------------------
1 | package accounts
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
8 | )
9 |
10 | func deleteAccount(prefix string) error {
11 | credential, err := utils.GetCredentials()
12 | if err != nil {
13 | return err
14 | }
15 |
16 | prefix = strings.ToLower(prefix)
17 | prefix, err = filterAccounts(credential.Accounts, prefix)
18 | if err != nil {
19 | return err
20 | }
21 |
22 | if prefix == credential.SelectedAccount {
23 | return utils.LogError("Chosen account cannot be deleted. Use space-cli accounts set to change the selected account", nil)
24 | }
25 |
26 | for i, v := range credential.Accounts {
27 | if v.ID == prefix {
28 | credential.Accounts = removeAccount(credential.Accounts, i)
29 | }
30 | }
31 |
32 | if err := utils.GenerateAccountsFile(credential); err != nil {
33 | return utils.LogError("Couldn't update accounts.yaml file", err)
34 | }
35 |
36 | return nil
37 | }
38 |
39 | func removeAccount(accounts []*model.Account, index int) []*model.Account {
40 | return append(accounts[:index], accounts[index+1:]...)
41 | }
42 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/auth/delete.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/filter"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/transport"
10 | )
11 |
12 | func deleteAuthProvider(project, prefix string) error {
13 |
14 | objs, err := GetAuthProviders(project, "auth-provider", map[string]string{"id": "*"})
15 | if err != nil {
16 | return err
17 | }
18 |
19 | providers := []string{}
20 | for _, spec := range objs {
21 | providers = append(providers, spec.Meta["id"])
22 | }
23 |
24 | resourceID, err := filter.DeleteOptions(prefix, providers)
25 | if err != nil {
26 | return err
27 | }
28 |
29 | // Delete the provider from the server
30 | url := fmt.Sprintf("/v1/config/projects/%s/user-management/provider/%s", project, resourceID)
31 |
32 | if err := transport.Client.MakeHTTPRequest(http.MethodDelete, url, map[string]string{"id": resourceID}, new(model.Response)); err != nil {
33 | return err
34 | }
35 |
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/auth/generate.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "github.com/AlecAivazis/survey/v2"
5 |
6 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/input"
8 | )
9 |
10 | func generateUserManagement() (*model.SpecObject, error) {
11 | project := ""
12 | if err := input.Survey.AskOne(&survey.Input{Message: "Enter Project"}, &project); err != nil {
13 | return nil, err
14 | }
15 | provider := ""
16 | if err := input.Survey.AskOne(&survey.Input{Message: "Enter Provider Name"}, &provider); err != nil {
17 | return nil, err
18 | }
19 |
20 | v := &model.SpecObject{
21 | API: "/v1/config/projects/{project}/user-management/provider/{id}",
22 | Type: "auth-providers",
23 | Meta: map[string]string{
24 | "project": project,
25 | "id": provider,
26 | },
27 | Spec: map[string]interface{}{
28 | "enabled": true,
29 | "secret": "",
30 | },
31 | }
32 |
33 | return v, nil
34 | }
35 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/auth/getters.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/transport"
10 | )
11 |
12 | // GetAuthProviders gets auth providers
13 | func GetAuthProviders(project, commandName string, params map[string]string) ([]*model.SpecObject, error) {
14 | url := fmt.Sprintf("/v1/config/projects/%s/user-management/provider", project)
15 |
16 | // Get the spec from the server
17 | payload := new(model.Response)
18 | if err := transport.Client.MakeHTTPRequest(http.MethodGet, url, params, payload); err != nil {
19 | return nil, err
20 | }
21 |
22 | var objs []*model.SpecObject
23 | for _, item := range payload.Result {
24 | spec := item.(map[string]interface{})
25 | meta := map[string]string{"project": project, "id": spec["id"].(string)}
26 |
27 | // Delete the unwanted keys from spec
28 | delete(spec, "id")
29 |
30 | // Printing the object on the screen
31 | s, err := utils.CreateSpecObject("/v1/config/projects/{project}/user-management/provider/{id}", commandName, meta, spec)
32 | if err != nil {
33 | return nil, err
34 | }
35 | objs = append(objs, s)
36 | }
37 | return objs, nil
38 | }
39 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/auth/helpers.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | func authProvidersAutoCompleteFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
9 | project, check := utils.GetProjectID()
10 | if !check {
11 | utils.LogDebug("Project not specified in flag", nil)
12 | return nil, cobra.ShellCompDirectiveDefault
13 | }
14 | objs, err := GetAuthProviders(project, "auth-providers", map[string]string{})
15 | if err != nil {
16 | return nil, cobra.ShellCompDirectiveDefault
17 | }
18 | var ids []string
19 | for _, v := range objs {
20 | ids = append(ids, v.Meta["id"])
21 | }
22 | return ids, cobra.ShellCompDirectiveDefault
23 | }
24 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/delete.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import (
4 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/auth"
5 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/database"
6 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/eventing"
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/filestore"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/ingress"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/letsencrypt"
10 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/project"
11 | remoteservices "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/remote-services"
12 | "github.com/spf13/cobra"
13 | )
14 |
15 | // FetchDeleteSubCommands fetches all the delete subcommands from different modules
16 | func FetchDeleteSubCommands() *cobra.Command {
17 | var deleteCmd = &cobra.Command{
18 | Use: "delete",
19 | Short: "",
20 | SilenceErrors: true,
21 | }
22 | deleteCmd.AddCommand(auth.DeleteSubCommands()...)
23 | deleteCmd.AddCommand(database.DeleteSubCommands()...)
24 | deleteCmd.AddCommand(ingress.DeleteSubCommands()...)
25 | deleteCmd.AddCommand(filestore.DeleteSubCommands()...)
26 | deleteCmd.AddCommand(eventing.DeleteSubCommands()...)
27 | deleteCmd.AddCommand(letsencrypt.DeleteSubCommands()...)
28 | deleteCmd.AddCommand(project.DeleteSubCommands()...)
29 | deleteCmd.AddCommand(remoteservices.DeleteSubCommands()...)
30 |
31 | return deleteCmd
32 | }
33 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/filestore/delete.go:
--------------------------------------------------------------------------------
1 | package filestore
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/filter"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/transport"
10 | )
11 |
12 | func deleteFileStoreConfig(project string) error {
13 |
14 | // Delete the filestore config from the server
15 | url := fmt.Sprintf("/v1/config/projects/%s/file-storage/config/%s", project, "filestore-config")
16 |
17 | if err := transport.Client.MakeHTTPRequest(http.MethodPost, url, map[string]string{}, new(model.Response)); err != nil {
18 | return err
19 | }
20 |
21 | return nil
22 | }
23 |
24 | func deleteFileStoreRule(project, prefix string) error {
25 |
26 | objs, err := GetFileStoreRule(project, "filestore-rule", map[string]string{})
27 | if err != nil {
28 | return err
29 | }
30 |
31 | ruleIDs := []string{}
32 | for _, spec := range objs {
33 | ruleIDs = append(ruleIDs, spec.Meta["id"])
34 | }
35 |
36 | resourceID, err := filter.DeleteOptions(prefix, ruleIDs)
37 | if err != nil {
38 | return err
39 | }
40 |
41 | // Delete the filestore rule from the server
42 | url := fmt.Sprintf("/v1/config/projects/%s/file-storage/rules/%s", project, resourceID)
43 |
44 | if err := transport.Client.MakeHTTPRequest(http.MethodDelete, url, map[string]string{}, new(model.Response)); err != nil {
45 | return err
46 | }
47 |
48 | return nil
49 | }
50 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/filestore/helpers.go:
--------------------------------------------------------------------------------
1 | package filestore
2 |
3 | import (
4 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | func fileStoreRulesAutoCompleteFun(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
9 | project, check := utils.GetProjectID()
10 | if !check {
11 | utils.LogDebug("Project not specified in flag", nil)
12 | return nil, cobra.ShellCompDirectiveDefault
13 | }
14 | objs, err := GetFileStoreRule(project, "filestore-rule", map[string]string{})
15 | if err != nil {
16 | return nil, cobra.ShellCompDirectiveDefault
17 | }
18 | var ids []string
19 | for _, v := range objs {
20 | ids = append(ids, v.Meta["id"])
21 | }
22 | return ids, cobra.ShellCompDirectiveDefault
23 | }
24 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/generator.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 |
6 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/auth"
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/database"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/eventing"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/filestore"
10 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/ingress"
11 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/letsencrypt"
12 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/project"
13 | remoteservices "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/remote-services"
14 | "github.com/spaceuptech/space-cloud/space-cli/cmd/modules/services"
15 | )
16 |
17 | // FetchGenerateSubCommands fetches all the generatesubcommands from different modules
18 | func FetchGenerateSubCommands() *cobra.Command {
19 | var generateCmd = &cobra.Command{
20 | Use: "generate",
21 | Short: "",
22 | SilenceErrors: true,
23 | }
24 | generateCmd.AddCommand(database.GenerateSubCommands()...)
25 | generateCmd.AddCommand(eventing.GenerateSubCommands()...)
26 | generateCmd.AddCommand(filestore.GenerateSubCommands()...)
27 | generateCmd.AddCommand(ingress.GenerateSubCommands()...)
28 | generateCmd.AddCommand(letsencrypt.GenerateSubCommands()...)
29 | generateCmd.AddCommand(remoteservices.GenerateSubCommands()...)
30 | generateCmd.AddCommand(services.GenerateSubCommands()...)
31 | generateCmd.AddCommand(auth.GenerateSubCommands()...)
32 | generateCmd.AddCommand(project.GenerateSubCommands()...)
33 |
34 | return generateCmd
35 | }
36 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/ingress/delete.go:
--------------------------------------------------------------------------------
1 | package ingress
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/filter"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/transport"
10 | )
11 |
12 | func deleteIngressGlobalConfig(project string) error {
13 |
14 | // Delete the ingress global config from the server
15 | url := fmt.Sprintf("/v1/config/projects/%s/routing/ingress/global", project)
16 |
17 | if err := transport.Client.MakeHTTPRequest(http.MethodPost, url, map[string]string{}, new(model.Response)); err != nil {
18 | return err
19 | }
20 |
21 | return nil
22 | }
23 |
24 | func deleteIngressRoute(project, prefix string) error {
25 |
26 | objs, err := GetIngressRoutes(project, "ingress-route", map[string]string{}, []string{})
27 | if err != nil {
28 | return err
29 | }
30 |
31 | routeIDs := []string{}
32 | for _, spec := range objs {
33 | routeIDs = append(routeIDs, spec.Meta["id"])
34 | }
35 |
36 | resourceID, err := filter.DeleteOptions(prefix, routeIDs)
37 | if err != nil {
38 | return err
39 | }
40 |
41 | // Delete the ingress route from the server
42 | url := fmt.Sprintf("/v1/config/projects/%s/routing/ingress/%s", project, resourceID)
43 |
44 | if err := transport.Client.MakeHTTPRequest(http.MethodDelete, url, map[string]string{}, new(model.Response)); err != nil {
45 | return err
46 | }
47 |
48 | return nil
49 | }
50 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/ingress/helpers.go:
--------------------------------------------------------------------------------
1 | package ingress
2 |
3 | import (
4 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | func ingressRoutesAutoCompleteFun(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
9 | project, check := utils.GetProjectID()
10 | if !check {
11 | utils.LogDebug("Project not specified in flag", nil)
12 | return nil, cobra.ShellCompDirectiveDefault
13 | }
14 | objs, err := GetIngressRoutes(project, "ingress-route", map[string]string{}, []string{})
15 | if err != nil {
16 | return nil, cobra.ShellCompDirectiveDefault
17 | }
18 | var ids []string
19 | for _, v := range objs {
20 | ids = append(ids, v.Meta["id"])
21 | }
22 | return ids, cobra.ShellCompDirectiveDefault
23 | }
24 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/letsencrypt/delete.go:
--------------------------------------------------------------------------------
1 | package letsencrypt
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/transport"
9 | )
10 |
11 | func deleteLetsencryptDomains(project string) error {
12 | // Delete the letsencrpyt domains from the server
13 | url := fmt.Sprintf("/v1/config/projects/%s/letsencrypt/config/%s", project, "letsencrypt")
14 |
15 | if err := transport.Client.MakeHTTPRequest(http.MethodPost, url, map[string]string{}, new(model.Response)); err != nil {
16 | return err
17 | }
18 |
19 | return nil
20 | }
21 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/letsencrypt/generate.go:
--------------------------------------------------------------------------------
1 | package letsencrypt
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/AlecAivazis/survey/v2"
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/input"
9 | )
10 |
11 | func generateLetsEncryptDomain() (*model.SpecObject, error) {
12 | whiteListedDomains := ""
13 | if err := input.Survey.AskOne(&survey.Input{Message: "Enter White Listed Domain by comma seperated value: "}, &whiteListedDomains); err != nil {
14 | return nil, err
15 | }
16 |
17 | email := ""
18 | if err := input.Survey.AskOne(&survey.Input{Message: "Enter Email ID: "}, &email); err != nil {
19 | return nil, err
20 | }
21 |
22 | whiteListedDomain := strings.Split(strings.TrimSuffix(whiteListedDomains, ","), ",")
23 | project := ""
24 | if err := input.Survey.AskOne(&survey.Input{Message: "Enter project: "}, &project); err != nil {
25 | return nil, err
26 | }
27 |
28 | v := &model.SpecObject{
29 | API: "/v1/config/projects/{project}/letsencrypt/config/{id}",
30 | Type: "letsencrypt",
31 | Meta: map[string]string{
32 | "project": project,
33 | "id": "letsencrypt-config",
34 | },
35 | Spec: map[string]interface{}{
36 | "domains": whiteListedDomain,
37 | "email": email,
38 | },
39 | }
40 |
41 | return v, nil
42 | }
43 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/letsencrypt/getters.go:
--------------------------------------------------------------------------------
1 | package letsencrypt
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/transport"
10 | )
11 |
12 | // GetLetsEncryptDomain gets encrypt domain
13 | func GetLetsEncryptDomain(project, commandName string, params map[string]string) ([]*model.SpecObject, error) {
14 | url := fmt.Sprintf("/v1/config/projects/%s/letsencrypt/config", project)
15 | // Get the spec from the server
16 | payload := new(model.Response)
17 | if err := transport.Client.MakeHTTPRequest(http.MethodGet, url, params, payload); err != nil {
18 | return nil, err
19 | }
20 |
21 | var objs []*model.SpecObject
22 | for _, item := range payload.Result {
23 | meta := map[string]string{"project": project, "id": "letsencrypt"}
24 | delete(item.(map[string]interface{}), "id")
25 | s, err := utils.CreateSpecObject("/v1/config/projects/{project}/letsencrypt/config/{id}", commandName, meta, item)
26 | if err != nil {
27 | return nil, err
28 | }
29 | objs = append(objs, s)
30 | }
31 |
32 | return objs, nil
33 | }
34 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/operations/inspect.go:
--------------------------------------------------------------------------------
1 | package operations
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ghodss/yaml"
7 |
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
10 | )
11 |
12 | // Inspect prints values file used for setting up cluster
13 | func Inspect(clusterID string) error {
14 | if clusterID == "" {
15 | charList, err := utils.HelmList(model.HelmSpaceCloudNamespace)
16 | if err != nil {
17 | return err
18 | }
19 | if len(charList) < 1 {
20 | utils.LogInfo("space cloud cluster not found, setup a new cluster using the setup command")
21 | return nil
22 | }
23 | clusterID = charList[0].Name
24 | }
25 |
26 | chartInfo, err := utils.HelmGet(clusterID)
27 | if err != nil {
28 | return err
29 | }
30 |
31 | data, err := yaml.Marshal(chartInfo.Config)
32 | if err != nil {
33 | return err
34 | }
35 | fmt.Println(string(data))
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/operations/list.go:
--------------------------------------------------------------------------------
1 | package operations
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/olekukonko/tablewriter"
7 |
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
10 | )
11 |
12 | // List initializes development environment
13 | func List() error {
14 | chartList, err := utils.HelmList(model.HelmSpaceCloudNamespace)
15 | if err != nil {
16 | return err
17 | }
18 | table := tablewriter.NewWriter(os.Stdout)
19 | table.SetHeader([]string{"CLUSTER ID", "NAMESPACE", "UPDATED", "STATUS", "CHART", "APP VERSION"})
20 |
21 | table.SetBorder(true)
22 | table.SetCenterSeparator("")
23 | table.SetColumnSeparator("|")
24 | table.SetAutoWrapText(false)
25 | for _, release := range chartList {
26 | table.Append([]string{release.Name, release.Namespace, release.Info.LastDeployed.String(), release.Info.Status.String(), release.Chart.Name(), release.Chart.AppVersion()})
27 | }
28 | table.Render()
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/project/delete.go:
--------------------------------------------------------------------------------
1 | package project
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/transport"
9 | )
10 |
11 | // DeleteProject deletes the specified project from space cloud
12 | func DeleteProject(project string) error {
13 | // Delete the project config from the server
14 | url := fmt.Sprintf("/v1/config/projects/%s", project)
15 |
16 | if err := transport.Client.MakeHTTPRequest(http.MethodDelete, url, map[string]string{}, new(model.Response)); err != nil {
17 | return err
18 | }
19 |
20 | return nil
21 | }
22 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/project/generate.go:
--------------------------------------------------------------------------------
1 | package project
2 |
3 | import (
4 | "github.com/AlecAivazis/survey/v2"
5 | "github.com/segmentio/ksuid"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/input"
10 | )
11 |
12 | func generateProject() (*model.SpecObject, error) {
13 | project := ""
14 | if err := input.Survey.AskOne(&survey.Input{Message: "Enter Project ID: "}, &project); err != nil {
15 | return nil, err
16 | }
17 | if project == "" {
18 | _ = utils.LogError("project id cannot be empty", nil)
19 | return nil, nil
20 | }
21 | projectName := ""
22 | if err := input.Survey.AskOne(&survey.Input{Message: "Enter Project Name: ", Default: project}, &projectName); err != nil {
23 | return nil, err
24 | }
25 |
26 | key := ""
27 | if err := input.Survey.AskOne(&survey.Input{Message: "Enter AES Key: "}, &key); err != nil {
28 | return nil, err
29 | }
30 |
31 | contextTime := 0
32 | if err := input.Survey.AskOne(&survey.Input{Message: "Enter Graphql Query Timeout: ", Default: "10"}, &contextTime); err != nil {
33 | return nil, err
34 | }
35 | v := &model.SpecObject{
36 | API: "/v1/config/projects/{project}",
37 | Type: "project",
38 | Meta: map[string]string{
39 | "project": project,
40 | },
41 | Spec: map[string]interface{}{
42 | "id": project,
43 | "aesKey": key,
44 | "name": projectName,
45 | "secrets": []map[string]interface{}{{"isPrimary": true, "secret": ksuid.New().String()}},
46 | "contextTimeGraphQL": contextTime,
47 | },
48 | }
49 |
50 | return v, nil
51 | }
52 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/project/getters.go:
--------------------------------------------------------------------------------
1 | package project
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/transport"
10 | )
11 |
12 | // GetProjectConfig gets global config
13 | func GetProjectConfig(project, commandName string, params map[string]string) ([]*model.SpecObject, error) {
14 | if project == "" {
15 | project = "*" // for getting all projects
16 | value, ok := params["id"]
17 | if ok {
18 | project = value
19 | }
20 | }
21 | url := fmt.Sprintf("/v1/config/projects/%s", project)
22 | // Get the spec from the server
23 | payload := new(model.Response)
24 | if err := transport.Client.MakeHTTPRequest(http.MethodGet, url, params, payload); err != nil {
25 | return nil, err
26 | }
27 |
28 | var objs []*model.SpecObject
29 | for _, item := range payload.Result {
30 | projectObj := item.(map[string]interface{})
31 | meta := map[string]string{"project": projectObj["id"].(string)}
32 | s, err := utils.CreateSpecObject("/v1/config/projects/{project}", commandName, meta, projectObj)
33 | if err != nil {
34 | return nil, err
35 | }
36 | objs = append(objs, s)
37 | }
38 | return objs, nil
39 | }
40 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/project/helpers.go:
--------------------------------------------------------------------------------
1 | package project
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | func projectAutoCompletionFun(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
9 | project := viper.GetString("project")
10 | objs, err := GetProjectConfig(project, "project", map[string]string{})
11 | if err != nil {
12 | return nil, cobra.ShellCompDirectiveDefault
13 | }
14 | var ids []string
15 | for _, v := range objs {
16 | ids = append(ids, v.Meta["id"])
17 | }
18 | return ids, cobra.ShellCompDirectiveDefault
19 | }
20 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/remote-services/delete.go:
--------------------------------------------------------------------------------
1 | package remoteservices
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/filter"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/transport"
10 | )
11 |
12 | func deleteRemoteService(project, prefix string) error {
13 |
14 | objs, err := GetRemoteServices(project, "remote-service", map[string]string{})
15 | if err != nil {
16 | return err
17 | }
18 |
19 | serviceIDs := []string{}
20 | for _, spec := range objs {
21 | serviceIDs = append(serviceIDs, spec.Meta["id"])
22 | }
23 |
24 | resourceID, err := filter.DeleteOptions(prefix, serviceIDs)
25 | if err != nil {
26 | return err
27 | }
28 |
29 | // Delete the remote service from the server
30 | url := fmt.Sprintf("/v1/config/projects/%s/remote-service/service/%s", project, resourceID)
31 |
32 | if err := transport.Client.MakeHTTPRequest(http.MethodDelete, url, map[string]string{}, new(model.Response)); err != nil {
33 | return err
34 | }
35 |
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/remote-services/getters.go:
--------------------------------------------------------------------------------
1 | package remoteservices
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/transport"
10 | )
11 |
12 | // GetRemoteServices gets remote services
13 | func GetRemoteServices(project, commandName string, params map[string]string) ([]*model.SpecObject, error) {
14 | url := fmt.Sprintf("/v1/config/projects/%s/remote-service/service", project)
15 |
16 | // Get the spec from the server
17 | payload := new(model.Response)
18 | if err := transport.Client.MakeHTTPRequest(http.MethodGet, url, params, payload); err != nil {
19 | return nil, err
20 | }
21 |
22 | var objs []*model.SpecObject
23 | for _, item := range payload.Result {
24 | spec := item.(map[string]interface{})
25 |
26 | meta := map[string]string{"project": project, "id": spec["id"].(string)}
27 |
28 | // Delete the unwanted keys from spec
29 | delete(spec, "id")
30 | delete(spec, "project")
31 | delete(spec, "version")
32 |
33 | // Printing the object on the screen
34 | s, err := utils.CreateSpecObject("/v1/config/projects/{project}/remote-service/service/{id}", commandName, meta, spec)
35 | if err != nil {
36 | return nil, err
37 | }
38 | objs = append(objs, s)
39 | }
40 |
41 | return objs, nil
42 | }
43 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/remote-services/helpers.go:
--------------------------------------------------------------------------------
1 | package remoteservices
2 |
3 | import (
4 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | func remoteServicesAutoCompleteFun(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
9 | project, check := utils.GetProjectID()
10 | if !check {
11 | utils.LogDebug("Project not specified in flag", nil)
12 | return nil, cobra.ShellCompDirectiveDefault
13 | }
14 | objs, err := GetRemoteServices(project, "remote-service", map[string]string{})
15 | if err != nil {
16 | return nil, cobra.ShellCompDirectiveDefault
17 | }
18 | var ids []string
19 | for _, v := range objs {
20 | ids = append(ids, v.Meta["id"])
21 | }
22 | return ids, cobra.ShellCompDirectiveDefault
23 | }
24 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/services/delete.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/filter"
9 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/transport"
10 | )
11 |
12 | func deleteSecret(project, prefix string) error {
13 |
14 | objs, err := GetServicesSecrets(project, "secret", map[string]string{})
15 | if err != nil {
16 | return err
17 | }
18 |
19 | secretIDs := []string{}
20 | for _, spec := range objs {
21 | secretIDs = append(secretIDs, spec.Meta["id"])
22 | }
23 |
24 | resourceID, err := filter.DeleteOptions(prefix, secretIDs)
25 | if err != nil {
26 | return err
27 | }
28 |
29 | // Delete the remote service from the server
30 | url := fmt.Sprintf("/v1/runner/%s/secrets/%s", project, resourceID)
31 |
32 | if err := transport.Client.MakeHTTPRequest(http.MethodDelete, url, map[string]string{}, new(model.Response)); err != nil {
33 | return err
34 | }
35 |
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/space-cli/cmd/modules/services/helpers.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | func secretsAutoCompleteFun(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
9 | project, check := utils.GetProjectID()
10 | if !check {
11 | utils.LogDebug("Project not specified in flag", nil)
12 | return nil, cobra.ShellCompDirectiveDefault
13 | }
14 | obj, err := GetServicesSecrets(project, "secret", map[string]string{})
15 | if err != nil {
16 | return nil, cobra.ShellCompDirectiveDefault
17 | }
18 | var ids []string
19 | for _, v := range obj {
20 | ids = append(ids, v.Meta["id"])
21 | }
22 | return ids, cobra.ShellCompDirectiveDefault
23 | }
24 |
--------------------------------------------------------------------------------
/space-cli/cmd/utils/domains.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "fmt"
4 |
5 | // GetServiceDomain is used for getting the main service domain
6 | func GetServiceDomain(projectID, serviceID string) string {
7 | return fmt.Sprintf("%s.%s.svc.cluster.local", serviceID, projectID)
8 | }
9 |
10 | // GetInternalServiceDomain is used for getting internal service domain
11 | func GetInternalServiceDomain(projectID, serviceID, version string) string {
12 | return fmt.Sprintf("%s.%s-%s.svc.cluster.local", serviceID, projectID, version)
13 | }
14 |
--------------------------------------------------------------------------------
/space-cli/cmd/utils/domains_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestGetServiceDomain(t *testing.T) {
8 | type args struct {
9 | projectID string
10 | serviceID string
11 | }
12 | tests := []struct {
13 | name string
14 | args args
15 | want string
16 | }{
17 | // TODO: Add test cases.
18 | {
19 | name: "test",
20 | args: args{
21 | projectID: "test",
22 | serviceID: "service",
23 | },
24 | want: "service.test.svc.cluster.local",
25 | },
26 | }
27 | for _, tt := range tests {
28 | t.Run(tt.name, func(t *testing.T) {
29 | if got := GetServiceDomain(tt.args.projectID, tt.args.serviceID); got != tt.want {
30 | t.Errorf("GetServiceDomain() = %v, want %v", got, tt.want)
31 | }
32 | })
33 | }
34 | }
35 |
36 | func TestGetInternalServiceDomain(t *testing.T) {
37 | type args struct {
38 | projectID string
39 | serviceID string
40 | version string
41 | }
42 | tests := []struct {
43 | name string
44 | args args
45 | want string
46 | }{
47 | // TODO: Add test cases.
48 | {
49 | name: "test",
50 | args: args{
51 | projectID: "test",
52 | serviceID: "service",
53 | version: "v1",
54 | },
55 | want: "service.test-v1.svc.cluster.local",
56 | },
57 | }
58 | for _, tt := range tests {
59 | t.Run(tt.name, func(t *testing.T) {
60 | if got := GetInternalServiceDomain(tt.args.projectID, tt.args.serviceID, tt.args.version); got != tt.want {
61 | t.Errorf("GetInternalServiceDomain() = %v, want %v", got, tt.want)
62 | }
63 | })
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/space-cli/cmd/utils/filter/filter.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/AlecAivazis/survey/v2"
7 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils"
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/utils/input"
9 | )
10 |
11 | // DeleteOptions filters the resource id based on prefix to delete
12 | func DeleteOptions(prefix string, resources []string) (string, error) {
13 | doesResourceExist := false
14 | filteredResources := []string{}
15 | for _, resource := range resources {
16 | if prefix != "" && strings.HasPrefix(strings.ToLower(resource), strings.ToLower(prefix)) {
17 | filteredResources = append(filteredResources, resource)
18 | doesResourceExist = true
19 | }
20 | }
21 |
22 | if doesResourceExist {
23 | if err := input.Survey.AskOne(&survey.Select{Message: "Choose the resource ID: ", Options: filteredResources, Default: filteredResources[0]}, &prefix); err != nil {
24 | return "", err
25 | }
26 | } else {
27 | if len(resources) == 1 {
28 | prefix = resources[0]
29 | } else {
30 | if prefix != "" {
31 | utils.LogInfo("Warning! No resource found for prefix provided, showing all")
32 | }
33 | if err := input.Survey.AskOne(&survey.Select{Message: "Choose the resource ID: ", Options: resources, Default: resources[0]}, &prefix); err != nil {
34 | return "", err
35 | }
36 | }
37 | }
38 |
39 | return prefix, nil
40 | }
41 |
--------------------------------------------------------------------------------
/space-cli/cmd/utils/http.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "io/ioutil"
8 | "net/http"
9 | )
10 |
11 | // Get gets spec object
12 | func Get(method, url string, params map[string]string, vPtr interface{}) error {
13 | account, token, err := LoginWithSelectedAccount()
14 | if err != nil {
15 | return LogError("Couldn't get account details or login token", err)
16 | }
17 | url = fmt.Sprintf("%s%s", account.ServerURL, url)
18 |
19 | req, err := http.NewRequest(method, url, nil)
20 | if err != nil {
21 | return err
22 | }
23 | if token != "" {
24 | req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
25 | }
26 | q := req.URL.Query()
27 | for k, v := range params {
28 | q.Add(k, v)
29 | }
30 | req.URL.RawQuery = q.Encode()
31 | resp, err := http.DefaultClient.Do(req)
32 | if err != nil {
33 | return err
34 | }
35 |
36 | defer CloseTheCloser(resp.Body)
37 |
38 | data, _ := ioutil.ReadAll(resp.Body)
39 |
40 | if resp.StatusCode != 200 {
41 | respBody := map[string]interface{}{}
42 | if err := json.Unmarshal(data, &respBody); err != nil {
43 | return err
44 | }
45 | _ = LogError(fmt.Sprintf("error while getting service got http status code %s - %s", resp.Status, respBody["error"]), nil)
46 | return fmt.Errorf("received invalid status code (%d)", resp.StatusCode)
47 | }
48 |
49 | if err := json.Unmarshal(data, vPtr); err != nil {
50 | return err
51 | }
52 |
53 | return nil
54 | }
55 |
56 | // CloseTheCloser closes the closer
57 | func CloseTheCloser(c io.Closer) {
58 | _ = c.Close()
59 | }
60 |
--------------------------------------------------------------------------------
/space-cli/cmd/utils/input/input.go:
--------------------------------------------------------------------------------
1 | package input
2 |
3 | import "github.com/AlecAivazis/survey/v2"
4 |
5 | type inputInterface interface {
6 | AskOne(p survey.Prompt, respone interface{}) error
7 | }
8 |
9 | // Input struct for parameter
10 | type input struct{}
11 |
12 | // AskOne calls survey.AskOne
13 | func (i *input) AskOne(p survey.Prompt, respone interface{}) error {
14 | if err := survey.AskOne(p, respone); err != nil {
15 | return err
16 | }
17 | return nil
18 | }
19 |
20 | // Survey package for survey.Askone
21 | var Survey inputInterface
22 |
23 | func init() {
24 | Survey = &input{}
25 | }
26 |
--------------------------------------------------------------------------------
/space-cli/cmd/utils/log.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 |
7 | "github.com/sirupsen/logrus"
8 | )
9 |
10 | // LogError logs the error in the proper format
11 | func LogError(message string, err error) error {
12 |
13 | // Log with error if provided
14 | if err != nil {
15 | logrus.WithField("error", err.Error()).Errorln(message)
16 | } else {
17 | logrus.Errorln(message)
18 | }
19 |
20 | // Return the error message
21 | return errors.New(message)
22 | }
23 |
24 | // LogInfo logs te info message in the proper format
25 | func LogInfo(message string) {
26 | logrus.Infoln(message)
27 | }
28 |
29 | // LogDebug logs the debug message in proper format
30 | func LogDebug(message string, extraFields map[string]interface{}) {
31 | if extraFields != nil {
32 | logrus.WithFields(extraFields).Debugln(message)
33 | return
34 | }
35 | logrus.Debugln(message)
36 | }
37 |
38 | // SetLogLevel sets a single verbosity level for log messages.
39 | func SetLogLevel(loglevel string) {
40 | switch loglevel {
41 | case "debug":
42 | logrus.SetLevel(logrus.DebugLevel)
43 | case "info":
44 | logrus.SetLevel(logrus.InfoLevel)
45 | case "error":
46 | logrus.SetLevel(logrus.ErrorLevel)
47 | default:
48 | _ = LogError(fmt.Sprintf("Invalid log level (%s) provided", loglevel), nil)
49 | LogInfo("Defaulting to `info` level")
50 | logrus.SetLevel(logrus.InfoLevel)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/space-cli/cmd/utils/printer.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ghodss/yaml"
7 |
8 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
9 | )
10 |
11 | // CreateSpecObject returns the string equivalent of the git op object
12 | func CreateSpecObject(api, objType string, meta map[string]string, spec interface{}) (*model.SpecObject, error) {
13 | v := model.SpecObject{
14 | API: api,
15 | Type: objType,
16 | Meta: meta,
17 | Spec: spec,
18 | }
19 |
20 | return &v, nil
21 | }
22 |
23 | // PrintYaml prints array of yaml object
24 | func PrintYaml(objs []*model.SpecObject) error {
25 | for _, val := range objs {
26 | b, err := yaml.Marshal(val)
27 | if err != nil {
28 | return err
29 | }
30 | fmt.Print(string(b))
31 | fmt.Println("---")
32 | }
33 | return nil
34 | }
35 |
--------------------------------------------------------------------------------
/space-cli/cmd/utils/types.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/AlecAivazis/survey/v2"
7 | "github.com/stretchr/testify/mock"
8 | )
9 |
10 | // MockInputInterface is the mock interface for survey package
11 | type MockInputInterface struct {
12 | mock.Mock
13 | }
14 |
15 | // AskOne is the mock method for MockInputInterface
16 | func (m *MockInputInterface) AskOne(p survey.Prompt, response interface{}) error {
17 | c := m.Called(p, response)
18 | a, _ := json.Marshal(c.Get(1))
19 | _ = json.Unmarshal(a, response)
20 | return c.Error(0)
21 | }
22 |
--------------------------------------------------------------------------------
/space-cli/cmd/utils/versioning.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // GetHelmChartDownloadURL adjusts the url prefixes according to the version
9 | func GetHelmChartDownloadURL(url, version string) string {
10 | arr := strings.Split(url, "/")
11 | chartName := fmt.Sprintf("%s-%s.tgz", arr[len(arr)-1], version)
12 | arr = append(arr, chartName)
13 | return strings.Join(arr, "/")
14 | }
15 |
--------------------------------------------------------------------------------
/space-cli/cmd/utils/versioning_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/spaceuptech/space-cloud/space-cli/cmd/model"
7 | )
8 |
9 | func TestGetChartDownloadURL(t *testing.T) {
10 | type args struct {
11 | url string
12 | version string
13 | }
14 | tests := []struct {
15 | name string
16 | args args
17 | want string
18 | }{
19 | {
20 | name: "Right URL and version",
21 | args: args{
22 | url: model.HelmSpaceCloudChartDownloadURL,
23 | version: "0.21.4",
24 | },
25 | want: "https://storage.googleapis.com/space-cloud/helm/space-cloud/space-cloud-0.21.4.tgz",
26 | },
27 | }
28 | for _, tt := range tests {
29 | t.Run(tt.name, func(t *testing.T) {
30 | if got := GetHelmChartDownloadURL(tt.args.url, tt.args.version); got != tt.want {
31 | t.Errorf("GetHelmChartDownloadURL() = %v, want %v", got, tt.want)
32 | }
33 | })
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/space-cli/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/spaceuptech/space-cloud/space-cli
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/AlecAivazis/survey/v2 v2.0.7
7 | github.com/briandowns/spinner v1.9.0
8 | github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible
9 | github.com/ghodss/yaml v1.0.0
10 | github.com/go-test/deep v1.0.7
11 | github.com/google/go-cmp v0.5.2
12 | github.com/google/uuid v1.1.2
13 | github.com/olekukonko/tablewriter v0.0.4
14 | github.com/segmentio/ksuid v1.0.2
15 | github.com/sirupsen/logrus v1.8.1
16 | github.com/spf13/cobra v1.1.3
17 | github.com/spf13/viper v1.7.0
18 | github.com/stretchr/testify v1.7.0
19 | github.com/txn2/txeh v1.3.0
20 | helm.sh/helm/v3 v3.6.3
21 | k8s.io/api v0.21.0
22 | k8s.io/apimachinery v0.21.0
23 | k8s.io/client-go v0.21.0
24 | rsc.io/letsencrypt v0.0.3 // indirect
25 | )
26 |
--------------------------------------------------------------------------------
/space-cli/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/spaceuptech/space-cloud/space-cli/cmd"
7 | )
8 |
9 | func main() {
10 | // rootCmd, err := getModule()
11 | // if err != nil {
12 | // fmt.Println(err)
13 | // os.Exit(1)
14 | // }
15 | //
16 | // if err := rootCmd.Execute(); err != nil {
17 | // fmt.Println(err)
18 | // os.Exit(1)
19 | // }
20 |
21 | if err := cmd.GetRootCommand().Execute(); err != nil {
22 | os.Exit(-1)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/space-cli/plugins.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // func getplugin(latestversion string) (*cobra.Command, error) {
4 | // mod := fmt.Sprintf("%s/cmd_%s.so", getSpaceCLIDirectory(), latestversion)
5 | // plug, err := plugin.Open(mod)
6 | // if err != nil {
7 | // return nil, err
8 | // }
9 | // commands, err := plug.Lookup("GetRootCommand")
10 | // if err != nil {
11 | // return nil, err
12 | // }
13 | //
14 | // rootCmd := commands.(func() *cobra.Command)()
15 | // return rootCmd, nil
16 | //
17 | // }
18 |
--------------------------------------------------------------------------------
/space-cli/scripts/push.sh:
--------------------------------------------------------------------------------
1 | rm ./space-cli.zip
2 | rm ./space-cli
3 | rm ./space-cli.exe
4 |
5 | SPACE_CLI_VERSION="0.21.5"
6 |
7 | GOOS=linux CGO_ENABLED=0 go build -a -ldflags '-s -w -extldflags "-static"' .
8 | zip space-cli.zip ./space-cli
9 | rm ./space-cli
10 |
11 | gsutil cp ./space-cli.zip gs://space-cloud/linux/space-cli.zip
12 | gsutil cp ./space-cli.zip gs://space-cloud/linux/space-cli-v$SPACE_CLI_VERSION.zip
13 | rm ./space-cli.zip
14 |
15 | GOOS=windows CGO_ENABLED=0 go build -a -ldflags '-s -w -extldflags "-static"' .
16 | zip space-cli.zip ./space-cli.exe
17 | rm ./space-cli.exe
18 |
19 | gsutil cp ./space-cli.zip gs://space-cloud/windows/space-cli.zip
20 | gsutil cp ./space-cli.zip gs://space-cloud/windows/space-cli-v$SPACE_CLI_VERSION.zip
21 | rm ./space-cli.zip
22 |
23 | GOOS=darwin CGO_ENABLED=0 go build -a -ldflags '-s -w -extldflags "-static"' .
24 | zip space-cli.zip ./space-cli
25 | rm ./space-cli
26 |
27 | gsutil cp ./space-cli.zip gs://space-cloud/darwin/space-cli.zip
28 | gsutil cp ./space-cli.zip gs://space-cloud/darwin/space-cli-v$SPACE_CLI_VERSION.zip
29 | rm ./space-cli.zip
30 |
--------------------------------------------------------------------------------
/space-cli/versioning.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // type cliVersionDoc struct {
4 | // VersionNo string `mapstructure:"version_no" json:"versionNo"`
5 | // VersionCode int32 `mapstructure:"version_code" json:"versionCode"`
6 | // ID string `mapstructure:"id" json:"id"`
7 | // }
8 | //
9 | // func getModule() (*cobra.Command, error) {
10 | //
11 | // _ = createDirIfNotExist(getSpaceCloudDirectory())
12 | // _ = createDirIfNotExist(getSpaceCLIDirectory())
13 | // _ = createFileIfNotExist(getSpaceCLIConfigPath(), "{}")
14 | //
15 | // currentVersion, err1 := readVersionConfig()
16 | // latestVersion, err2 := getLatestVersion()
17 | //
18 | // // Return error if we could not get the current or latest version
19 | // if err1 != nil && err2 != nil {
20 | // return nil, logError("Could not fetch space-cli plugin", err2)
21 | // }
22 | // // Return currentVersion if we could not get the latest version
23 | // if err1 == nil && err2 != nil {
24 | // return getplugin(currentVersion.VersionNo)
25 | // }
26 | // // Returns latestVersion if latestVersion is availabe or currentVersion not found
27 | // if err2 == nil {
28 | // if err1 == nil {
29 | // if latestVersion.VersionCode <= currentVersion.VersionCode {
30 | // return getplugin(currentVersion.VersionNo)
31 | // }
32 | // }
33 | // // Download the latest plugin version
34 | // if err := downloadPlugin(latestVersion); err != nil {
35 | // return nil, err
36 | // }
37 | // }
38 | // return getplugin(latestVersion.VersionNo)
39 | // }
40 |
--------------------------------------------------------------------------------