├── .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 | --------------------------------------------------------------------------------