├── .dockerignore ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .gitlab-ci.yml ├── .releaserc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.tests ├── EXAMPLE.env ├── LICENSE ├── LICENSE.third-party ├── README.md ├── UPGRADE.md ├── alembic.ini ├── bin └── .gitignore ├── docker-compose.yml ├── docs ├── API.md ├── ARCHITECTURE.md ├── CONFIG.md ├── CONFIG.md.in ├── CONTRIBUTING.md ├── LOCAL_DEVELOPMENT.md ├── MAIN.md ├── UPGRADE.md ├── customising.md ├── deployment.md ├── design │ ├── favicon.xcf │ ├── logo.xcf │ └── null.xcf ├── diagnosing_issues.md ├── example-terrareg-module-metadata.json ├── index.md ├── legal │ └── datadog_logo_usage.txt ├── logo.png ├── modules │ ├── analytics.md │ ├── best_practices.md │ ├── cost_analysis.md │ ├── misc.md │ ├── overview.md │ ├── security_scanning.md │ ├── storage.md │ └── uploading.md ├── providers │ └── index.md ├── quickstart.md ├── screenshots │ ├── example.png │ ├── homepage.png │ ├── module_provider.png │ └── search.png └── security.md ├── mkdocs.yml ├── package-lock.json ├── package.json ├── poetry.lock ├── pyproject.toml ├── renovate.json ├── sass └── cherry-dark.scss ├── scripts ├── check_spelling.sh ├── dictionary.txt ├── entrypoint.sh ├── generate_readme.py ├── generate_test_gpg_key.py ├── preview_release_notes.sh └── upload_module.sh ├── setup.py ├── terrareg.py ├── terrareg ├── __init__.py ├── alembic │ ├── README │ ├── env.py │ ├── script.py.mako │ └── versions │ │ ├── 02c137632f24_add_tables_for_user_groups_and_user_.py │ │ ├── 0776813feaeb_update_analaytics_to_perform_no_action_.py │ │ ├── 0832273b5d04_add_audit_type_for_module_provider_.py │ │ ├── 0e3de802e5e0_.py │ │ ├── 161e0f33603c_add_audit_table.py │ │ ├── 1f1ebdc845a0_add_audit_events_for_module_provider_.py │ │ ├── 210586684f86_add_namespace_display_name.py │ │ ├── 2bc9677e93d1_add_git_ref_column_to_module_version_.py │ │ ├── 2e9fe1292b2b_add_audit_events_for_namespace_deletion.py │ │ ├── 437ccafb9882_add_session_table_to_hold_active_.py │ │ ├── 47e45e505e22_move_common_submodule_moduleversion_.py │ │ ├── 51d3707d6334_add_git_base_path_to_module_provider_.py │ │ ├── 6416ffbf606d_add_infracost_output_column_to_module_.py │ │ ├── 6dd8adb5e1e3_postgres_fixes.py │ │ ├── 71d311c23025_add_module_version_column_for_.py │ │ ├── 7b6cac0d522f_add_columns_to_analytics_to_record_.py │ │ ├── 81609b772646_add_module_details_columns_for_.py │ │ ├── 8b594ed19f9d_add_columns_for_graph_data.py │ │ ├── 95f56a77a6e6_.py │ │ ├── 9dbcb4240c55_add_git_path_template_column_to_git_.py │ │ ├── __init__.py │ │ ├── a36ffbb6580e_add_index_to_parent_module_version_.py │ │ ├── acd5e83c690f_add_table_for_module_version_files.py │ │ ├── aef5947a7e1d_.py │ │ ├── b0f952e4a027_add_table_for_example_file_contents.py │ │ ├── d499042fad3b_add_module_details_column_for_holding_.py │ │ ├── d72c5339c21b_add_table_for_namespaces_and_replace_.py │ │ ├── db7a6e0d0b9d_add_namespace_redirect_table_and_audit_.py │ │ ├── ee14b27baeb1_add_tables_for_provider_sources_.py │ │ ├── ee82678fcda1_remove_foreign_key_constraint_from_.py │ │ ├── eea5e27cac2b_add_names_to_all_foreign_key_constaints.py │ │ ├── ef71db86c2a1_add_internal_flag_column_to_module_.py │ │ ├── f9a80ea383cc_add_archive_git_path_column_to_module_.py │ │ ├── fb6a94791a14_add_tables_for_terraform_idp_sessions.py │ │ └── ff6378a8ab9d_add_module_provider_redirect_table.py ├── analytics.py ├── audit.py ├── audit_action.py ├── auth │ ├── __init__.py │ ├── admin_api_key_auth_method.py │ ├── admin_session_auth_method.py │ ├── authentication_type.py │ ├── base_admin_auth_method.py │ ├── base_api_key_auth_method.py │ ├── base_auth_method.py │ ├── base_session_auth_method.py │ ├── base_sso_auth_method.py │ ├── base_terraform_static_token.py │ ├── github_auth_method.py │ ├── not_authenticated.py │ ├── openid_auth_method.py │ ├── publish_api_key_auth_method.py │ ├── saml_auth_method.py │ ├── terraform_analytics_auth_key_auth_method.py │ ├── terraform_ignore_analytics_auth_method.py │ ├── terraform_internal_extraction.py │ ├── terraform_oidc_auth_method.py │ └── upload_api_key_auth_method.py ├── auth_wrapper.py ├── config.py ├── constants.py ├── csrf.py ├── database.py ├── errors.py ├── file_storage.py ├── filters.py ├── loose_version.py ├── markdown_link_modifier.py ├── models.py ├── module_extractor.py ├── module_search.py ├── namespace_type.py ├── openid_connect.py ├── presigned_url.py ├── provider_binary_types.py ├── provider_category_model.py ├── provider_documentation_type.py ├── provider_extractor.py ├── provider_model.py ├── provider_search.py ├── provider_source │ ├── __init__.py │ ├── base.py │ ├── factory.py │ ├── github.py │ └── repository_release_metadata.py ├── provider_source_type.py ├── provider_tier.py ├── provider_version_binary_model.py ├── provider_version_documentation_model.py ├── provider_version_model.py ├── registry_resource_type.py ├── repository_kind.py ├── repository_model.py ├── result_data.py ├── saml.py ├── server │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── github │ │ │ ├── __init__.py │ │ │ ├── github_auth_status.py │ │ │ ├── github_login_callback.py │ │ │ ├── github_login_initiate.py │ │ │ ├── github_organisations.py │ │ │ ├── github_refresh_namespace.py │ │ │ ├── github_repositories.py │ │ │ └── github_repository_publish_provider.py │ │ ├── module_details.py │ │ ├── module_list.py │ │ ├── module_provider_details.py │ │ ├── module_provider_downloads_summary.py │ │ ├── module_search.py │ │ ├── module_version_create.py │ │ ├── module_version_create_bitbucket_hook.py │ │ ├── module_version_create_github_hook.py │ │ ├── module_version_details.py │ │ ├── module_version_download.py │ │ ├── module_version_import.py │ │ ├── module_version_source_download.py │ │ ├── module_version_upload.py │ │ ├── module_versions.py │ │ ├── namespace_modules.py │ │ ├── namespace_providers.py │ │ ├── open_id_callback.py │ │ ├── open_id_initiate.py │ │ ├── prometheus_metrics.py │ │ ├── provider.py │ │ ├── provider_list.py │ │ ├── provider_search.py │ │ ├── provider_version_download.py │ │ ├── provider_versions.py │ │ ├── saml_initiate.py │ │ ├── saml_metadata.py │ │ ├── terraform │ │ │ ├── __init__.py │ │ │ └── v2 │ │ │ │ ├── __init__.py │ │ │ │ ├── gpg_key.py │ │ │ │ ├── gpg_keys.py │ │ │ │ ├── provider.py │ │ │ │ ├── provider_categories.py │ │ │ │ ├── provider_doc.py │ │ │ │ ├── provider_docs.py │ │ │ │ └── provider_download_summary.py │ │ ├── terraform_oauth.py │ │ ├── terraform_well_known.py │ │ ├── terrareg_admin_authenticate.py │ │ ├── terrareg_audit_history.py │ │ ├── terrareg_auth_user_groups.py │ │ ├── terrareg_config.py │ │ ├── terrareg_example_details.py │ │ ├── terrareg_example_file.py │ │ ├── terrareg_example_file_list.py │ │ ├── terrareg_example_readme_html.py │ │ ├── terrareg_git_providers.py │ │ ├── terrareg_global_stats_summary.py │ │ ├── terrareg_global_usage_stats.py │ │ ├── terrareg_graph_data.py │ │ ├── terrareg_health.py │ │ ├── terrareg_initial_setup_data.py │ │ ├── terrareg_is_authenticated.py │ │ ├── terrareg_module_provider_analytics_token_versions.py │ │ ├── terrareg_module_provider_create.py │ │ ├── terrareg_module_provider_delete.py │ │ ├── terrareg_module_provider_integrations.py │ │ ├── terrareg_module_provider_redirect_delete.py │ │ ├── terrareg_module_provider_redirects.py │ │ ├── terrareg_module_provider_settings.py │ │ ├── terrareg_module_provider_versions.py │ │ ├── terrareg_module_providers.py │ │ ├── terrareg_module_search_filters.py │ │ ├── terrareg_module_version_analytics.py │ │ ├── terrareg_module_version_delete.py │ │ ├── terrareg_module_version_details.py │ │ ├── terrareg_module_version_examples.py │ │ ├── terrareg_module_version_file.py │ │ ├── terrareg_module_version_publish.py │ │ ├── terrareg_module_version_readme_html.py │ │ ├── terrareg_module_version_submodules.py │ │ ├── terrareg_module_version_variable_template.py │ │ ├── terrareg_most_downloaded_module_this_week.py │ │ ├── terrareg_most_recently_published_module_version.py │ │ ├── terrareg_namespace_details.py │ │ ├── terrareg_namespace_modules.py │ │ ├── terrareg_namespaces.py │ │ ├── terrareg_provider_integrations.py │ │ ├── terrareg_provider_logos.py │ │ ├── terrareg_provider_search_filters.py │ │ ├── terrareg_submodule_details.py │ │ ├── terrareg_submodule_readme_html.py │ │ ├── terrareg_user_group.py │ │ ├── terrareg_user_group_namespace_permissions.py │ │ ├── terrareg_version.py │ │ └── utils.py │ ├── base_handler.py │ └── error_catching_resource.py ├── static │ ├── css │ │ ├── bulma │ │ │ ├── LICENSE │ │ │ ├── bulma-0.9.3.min.css │ │ │ ├── cherry-dark │ │ │ │ └── bulmaswatch.min.css │ │ │ ├── lux │ │ │ │ └── bulmaswatch.min.css │ │ │ └── pulse │ │ │ │ └── bulmaswatch.min.css │ │ ├── datatables │ │ │ ├── LICENSE │ │ │ ├── buttons.dataTables-2.2.3.min.css │ │ │ ├── dataTables.bulma-1.11.5.min.css │ │ │ ├── responsive.bulma-2.3.0.min.css │ │ │ └── rowGroup.bulma-1.2.0.min.css │ │ ├── initial-setup.css │ │ ├── module_provider_page.css │ │ ├── prism │ │ │ ├── LICENSE │ │ │ └── prism-hcl-1.29.0.css │ │ ├── provider_page.css │ │ └── terrareg.css │ ├── images │ │ ├── PB_AWS_logo_RGB_stacked.547f032d90171cdea4dd90c258f47373c5573db5.png │ │ ├── consul.png │ │ ├── dd_logo_v_rgb.png │ │ ├── favicon.ico │ │ ├── gcp.png │ │ ├── logo.png │ │ ├── nomad.png │ │ ├── null.png │ │ ├── vagrant.png │ │ └── vault.png │ └── js │ │ ├── cytoscape │ │ ├── LICENSE │ │ ├── cose-base.js │ │ ├── cytoscape-fcose.js │ │ ├── cytoscape.min-3.26.0.js │ │ └── layout-base.js │ │ ├── datatables │ │ ├── LICENSE │ │ ├── dataTables.bulma-1.11.5.min.js │ │ ├── dataTables.buttons-2.2.3.min.js │ │ ├── dataTables.responsive-2.3.0.min.js │ │ ├── dataTables.rowGroup-1.2.0.min.js │ │ ├── full_numbers_no_ellipses.js │ │ ├── jquery.dataTables-1.11.5.min.js │ │ └── responsive.bulma-2.3.0.min.js │ │ ├── fontawesome │ │ ├── LICENSE │ │ └── all-v6.0.0.js │ │ ├── jquery │ │ ├── LICENSE │ │ ├── jquery-6.3.0.min.js │ │ └── jquery.cookie-1.4.1.js │ │ ├── navigo │ │ ├── LICENSE │ │ ├── navigo-8.11.1.min.js │ │ └── navigo.min.js.map │ │ ├── prism │ │ ├── LICENSE │ │ └── prism-hcl-1.29.0.js │ │ └── terrareg │ │ ├── config.js │ │ ├── initial-setup.js │ │ ├── module_provider.js │ │ ├── module_provider_page.js │ │ ├── provider.js │ │ ├── provider_page.js │ │ └── user-preferences.js ├── templates │ ├── audit_history.html │ ├── create_module_provider.html │ ├── create_namespace.html │ ├── create_provider.html │ ├── edit_namespace.html │ ├── error.html │ ├── graph.html │ ├── index.html │ ├── initial_setup.html │ ├── login.html │ ├── module.html │ ├── module_provider.html │ ├── module_search.html │ ├── namespace.html │ ├── namespace_list.html │ ├── provider.html │ ├── provider_search.html │ ├── search.html │ ├── template.html │ └── user_groups.html ├── terraform_idp.py ├── terraform_product.py ├── user_group_namespace_permission_type.py ├── utils.py ├── validators.py ├── version.py └── version_constraint.py ├── test ├── __init__.py ├── integration │ ├── README.md │ ├── __init__.py │ └── terrareg │ │ ├── __init__.py │ │ ├── analytics_engine │ │ ├── __init__.py │ │ ├── test_check_module_provider_redirect_usage.py │ │ ├── test_get_global_module_usage.py │ │ ├── test_get_prometheus_metrics.py │ │ └── test_record_module_version_download.py │ │ ├── audit │ │ ├── __init__.py │ │ └── test_audit_event.py │ │ ├── fixtures.py │ │ ├── models │ │ ├── __init__.py │ │ ├── provider_source │ │ │ ├── __init__.py │ │ │ ├── base_provider_source_tests.py │ │ │ ├── test_base_provider_source.py │ │ │ └── test_github_provider_source.py │ │ ├── test_base_submodule.py │ │ ├── test_example.py │ │ ├── test_example_file.py │ │ ├── test_git_provider.py │ │ ├── test_module.py │ │ ├── test_module_details.py │ │ ├── test_module_provider.py │ │ ├── test_module_provider_redirect.py │ │ ├── test_module_version.py │ │ ├── test_module_version_file.py │ │ ├── test_namespace.py │ │ ├── test_provider.py │ │ ├── test_provider_category.py │ │ ├── test_provider_category_factory.py │ │ ├── test_provider_logo.py │ │ ├── test_provider_source_factory.py │ │ ├── test_provider_version.py │ │ ├── test_provider_version_binary.py │ │ ├── test_provider_version_documentation.py │ │ ├── test_session.py │ │ ├── test_submodule.py │ │ ├── test_user_group.py │ │ └── test_user_group_namespace_permission.py │ │ ├── module_extractor │ │ ├── __init__.py │ │ └── test_process_upload.py │ │ ├── module_search │ │ ├── __init__.py │ │ ├── test_get_search_filters.py │ │ └── test_search_module_providers.py │ │ ├── provider_search │ │ ├── __init__.py │ │ ├── test_get_search_filters.py │ │ └── test_search_providers.py │ │ ├── server │ │ ├── __init__.py │ │ └── test_api_module_provider_bitbucket_hook_integration.py │ │ ├── test_data.py │ │ ├── test_provider_documentation_type.py │ │ ├── test_provider_extractor.py │ │ ├── test_provider_source_type.py │ │ ├── test_provider_tier.py │ │ ├── test_registry_resource_type.py │ │ ├── test_repository_kind.py │ │ ├── test_repository_release_metadata.py │ │ └── test_terraform_idp.py ├── selenium │ ├── __init__.py │ ├── test_audit_history.py │ ├── test_common_search_page.py │ ├── test_create_module_provider.py │ ├── test_create_namespace.py │ ├── test_data.py │ ├── test_edit_namespace.py │ ├── test_graph_canvas_images │ │ ├── moduledetails_fullypopulated_testprovider_1.5.0_example.png │ │ ├── moduledetails_fullypopulated_testprovider_1.5.0_example_full_modules.png │ │ ├── moduledetails_fullypopulated_testprovider_1.5.0_example_full_resources.png │ │ ├── moduledetails_fullypopulated_testprovider_1.5.0_example_full_resources_full_modules.png │ │ ├── moduledetails_fullypopulated_testprovider_1.5.0_root_module.png │ │ ├── moduledetails_fullypopulated_testprovider_1.5.0_root_module_full_modules.png │ │ ├── moduledetails_fullypopulated_testprovider_1.5.0_root_module_full_resources.png │ │ ├── moduledetails_fullypopulated_testprovider_1.5.0_root_module_full_resources_full_modules.png │ │ ├── moduledetails_fullypopulated_testprovider_1.5.0_submodule.png │ │ ├── moduledetails_fullypopulated_testprovider_1.5.0_submodule_full_modules.png │ │ ├── moduledetails_fullypopulated_testprovider_1.5.0_submodule_full_resources.png │ │ └── moduledetails_fullypopulated_testprovider_1.5.0_submodule_full_resources_full_modules.png │ ├── test_homepage.py │ ├── test_initial_setup.py │ ├── test_login.py │ ├── test_module_provider.py │ ├── test_module_search.py │ ├── test_namespace.py │ ├── test_namespace_list.py │ ├── test_provider.py │ ├── test_provider_search.py │ ├── test_terraform_login.py │ └── test_user_group.py ├── test_gpg_key.py └── unit │ ├── README.md │ ├── __init__.py │ └── terrareg │ ├── __init__.py │ ├── auth │ ├── __init__.py │ ├── base_auth_method_test.py │ ├── base_session_auth_method_tests.py │ ├── base_sso_auth_method_tests.py │ ├── base_terraform_static_token_tests.py │ ├── test_admin_api_key_auth_method.py │ ├── test_admin_session_auth_method.py │ ├── test_auth_factory.py │ ├── test_github_auth_method.py │ ├── test_not_authenticated.py │ ├── test_openid_connect_auth_method.py │ ├── test_publish_api_key_auth_method.py │ ├── test_saml_auth_method.py │ ├── test_terraform_analytics_auth_key_auth_method.py │ ├── test_terraform_ignore_analytics_auth_method.py │ ├── test_terraform_internal_extraction_auth_method.py │ ├── test_terraform_oidc_auth_method.py │ └── test_upload_api_key_auth_method.py │ ├── server │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ └── terraform │ │ │ ├── __init__.py │ │ │ └── v2 │ │ │ ├── __init__.py │ │ │ ├── test_gpg_key.py │ │ │ ├── test_gpg_keys.py │ │ │ ├── test_provider.py │ │ │ ├── test_provider_categories.py │ │ │ ├── test_provider_doc.py │ │ │ ├── test_provider_docs.py │ │ │ └── test_provider_download_summary.py │ ├── test_api_module_details.py │ ├── test_api_module_list.py │ ├── test_api_module_provider_bitbucket_hook.py │ ├── test_api_module_provider_details.py │ ├── test_api_module_provider_downloads_summary.py │ ├── test_api_module_provider_github_hook.py │ ├── test_api_module_search.py │ ├── test_api_module_version_create.py │ ├── test_api_module_version_details.py │ ├── test_api_module_version_download.py │ ├── test_api_module_version_import.py │ ├── test_api_module_version_source_download.py │ ├── test_api_module_versions.py │ ├── test_api_namespace_providers.py │ ├── test_api_open_id_callback.py │ ├── test_api_provider_list.py │ ├── test_api_provider_version_download.py │ ├── test_api_provider_versions.py │ ├── test_api_terrareg_admin_authenticate.py │ ├── test_api_terrareg_auth_user_group.py │ ├── test_api_terrareg_auth_user_group_namespace_permissions.py │ ├── test_api_terrareg_auth_user_groups.py │ ├── test_api_terrareg_git_providers.py │ ├── test_api_terrareg_global_usage_stats.py │ ├── test_api_terrareg_graph_data.py │ ├── test_api_terrareg_health.py │ ├── test_api_terrareg_is_authenticated.py │ ├── test_api_terrareg_module_provider_create.py │ ├── test_api_terrareg_module_provider_details.py │ ├── test_api_terrareg_module_provider_settings.py │ ├── test_api_terrareg_module_version_analytics.py │ ├── test_api_terrareg_module_version_delete.py │ ├── test_api_terrareg_module_version_details.py │ ├── test_api_terrareg_module_version_publish.py │ ├── test_api_terrareg_namespace_details.py │ ├── test_api_terrareg_namespace_list.py │ ├── test_api_terrareg_namespace_modules.py │ ├── test_api_terrareg_namespaces.py │ ├── test_auth_wrapper.py │ ├── test_csrf_functions.py │ ├── test_github_auth_callback.py │ ├── test_github_auth_initiate.py │ ├── test_github_auth_status.py │ ├── test_github_organisations.py │ ├── test_github_refresh_namespace.py │ ├── test_github_repositories.py │ ├── test_github_repository_publish_provider.py │ ├── test_module_provider_page_terraform_get.py │ ├── test_module_version_upload.py │ ├── test_prometheus_metrics.py │ ├── test_terraform_oauth.py │ └── test_terraform_well_known.py │ ├── test_config.py │ ├── test_data.py │ ├── test_file_storage.py │ ├── test_markdown_link_modifier.py │ ├── test_module_extractor.py │ ├── test_openid_connect.py │ ├── test_presigned_url.py │ ├── test_saml.py │ ├── test_utils.py │ └── test_version_constraint.py ├── tox.ini └── traefik ├── dynamic_conf.yaml └── traefik.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | example 2 | data 3 | venv 4 | modules.db 5 | local-dbs 6 | __pycache__ 7 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Release 15 | uses: softprops/action-gh-release@v1 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | example/* 2 | *.pyc 3 | venv 4 | data 5 | modules.db 6 | *.db 7 | bin/ 8 | .venv 9 | terrareg.egg-info/ 10 | mysql.env 11 | terrareg.env 12 | .env 13 | certs/* 14 | node_modules/ 15 | terrareg/version.txt -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["main", "+([0-9])?(.{+([0-9]),x}).x"], 3 | "gitPushOptions": ["ci.skip"], 4 | "plugins": [ 5 | "@semantic-release/commit-analyzer", 6 | "@semantic-release/release-notes-generator", 7 | [ 8 | "@semantic-release/changelog", 9 | { 10 | "changelogFile": "CHANGELOG.md", 11 | "changelogTitle": "# Changelog" 12 | } 13 | ], 14 | [ 15 | "@semantic-release/git", 16 | { 17 | "assets": [ 18 | "CHANGELOG.md" 19 | ], 20 | "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}" 21 | } 22 | ] 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing 3 | 4 | Please read the below before contributing any changes to the project. 5 | 6 | ## Committing 7 | 8 | All commits should use angular-style commit messages: https://www.conventionalcommits.org/en/v1.0.0/. 9 | 10 | Generally, the only exception around not having a 'type: ' prefix is whilst fixing an issue caused by a previous commit in a branch (i.e. a bug that didn't reach the main branch without the fix). 11 | 12 | A Gitlab issue must be included at the bottom of the commit message `Issue #123`. 13 | 14 | This format is important for the CI to perform releases and update release notes accurately. 15 | 16 | If there isn't a gitlab issue for the task that you are working on, please feel free to ask for one (raise a Github issue and maintainer can rcreate an issue for you. Alternatively, request access to Gitlab, which should be granted) 17 | 18 | 19 | ## Upstream 20 | 21 | The main upstream of this project is https://gitlab.dockstudios.co.uk/pub/terrareg. 22 | 23 | Any changes submitted to any other instance (e.g. Github, Gitlab Cloud etc.) will be manually applied to the upstream and will then replicate downstream to other locations. 24 | 25 | If you'd prefer, feel free to register an account to the main upstream - though registrations require approval, so just ping me an email :) 26 | 27 | Any issues raised in any hosting platform, other than the 'main upstream' will be manually replicated to the 'main upstream'. 28 | 29 | As a result, issue references in commit messages will only align with those in the 'main upstream'. 30 | -------------------------------------------------------------------------------- /EXAMPLE.env: -------------------------------------------------------------------------------- 1 | # Global variables, these are used for passing configurations into the docker-compose.yml file 2 | DOCKER_NAME=terrareg 3 | DOCKER_BASE_URL=app.localhost 4 | 5 | # MySQL configurations 6 | ALLOW_EMPTY_PASSWORD=yes 7 | MARIADB_DATABASE=terrareg 8 | MARIADB_USER=terrareg 9 | MARIADB_PASSWORD=terrareg 10 | 11 | # terrareg Configuration 12 | SECRET_KEY=InsertHere # Change This! Generate secret key per the Docs and update this 13 | ADMIN_AUTHENTICATION_TOKEN=admin123 # Change This! 14 | 15 | DATABASE_URL=mysql+mysqlconnector://terrareg:terrareg@mysql:3306/terrareg # If you change the MySQL Configuration update this accordingly 16 | MIGRATE_DATABASE=True 17 | ADMIN_SESSION_EXPIRY_MINS=30 18 | ALLOW_MODULE_HOSTING=true 19 | DEBUG=true 20 | GIT_PROVIDER_CONFIG='[{"name": "Github", "base_url": "https://github.com/{namespace}/terraform-{provider}-{module}", "clone_url": "https://github.com/{namespace}/terraform-{provider}-{module}.git", "browse_url": "https://github.com/{namespace}/terraform-{provider}-{module}/tree/{tag}/{path}"}, {"name": "Bitbucket", "base_url": "https://bitbucket.org/{namespace}/terraform-{provider}-{module}", "clone_url": "ssh://git@bitbucket.org/{namespace}/terraform-{provider}-{module}-{provider}.git", "browse_url": "https://bitbucket.org/{namespace}/terraform-{provider}-{module}-{provider}/src/{tag}/{path}"}, {"name": "Gitlab", "base_url": "https://gitlab.com/{namespace}/terraform-{provider}-{module}", "clone_url": "ssh://git@gitlab.com/{namespace}/terraform-{provider}-{module}-{provider}.git", "browse_url": "https://gitlab.com/{namespace}/terraform-{provider}-{module}-{provider}/-/tree/{tag}/{path}"}]' 21 | DOMAIN_NAME=terrareg.app.localhost 22 | 23 | # To use minio for S3 24 | AWS_ENDPOINT_URL="http://minio:9000" 25 | AWS_ACCESS_KEY_ID=GA7JXYR4LUFQ23YPMO_MFKLI 26 | AWS_SECRET_ACCESS_KEY="4x5JqXPd-1JOo97CwI0Pr_LBVS-gApjrU7JUNrclkbOXrdYU" 27 | AWS_BUCKET_NAME=terrareg 28 | AWS_REGION="us-east-1" 29 | -------------------------------------------------------------------------------- /LICENSE.third-party: -------------------------------------------------------------------------------- 1 | # Third party licenses 2 | 3 | Terrareg makes use of third party software and libraries. 4 | 5 | If the Terrareg source code was downloaded, please review the licenses in `terrareg/static/js` and `terrareg/static/css`. 6 | 7 | If the pre-built Terrareg Docker image was downloaded, please review all licenses in the `licenses` directory. 8 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade 2 | 3 | See the following notes for upgrading between major releases 4 | 5 | ## 3.0.0 6 | 7 | To upgrade from v2.x.x to v3.0.0, the following need to be reviewed to perform the upgrade. 8 | 9 | ### Github authentication 10 | 11 | The GITHUB_* environment variables have been replaced by a new [PROVIDER_SOURCES](docs/CONFIG.md#provider_sources) configuration. 12 | 13 | To retain the same functionality, create a PROVIDER_SOURCES configuration with the following, replacing the variables with the values from your previous configurations: 14 | 15 | ``` 16 | PROVIDER_SOURCES='[{"name": "Github", "type": "github", "base_url": "https://github.com", "api_url": "https://api.github.com", "client_id": "$GITHUB_APP_CLIENT_ID", "client_secret": "$GITHUB_APP_CLIENT_SECRET", "app_id": "123456", "private_key_path": "$GITHUB_APP_PRIVATE_KEY_PATH", "webhook_secret": "$GITHUB_APP_WEBHOOK_SECRET", "auto_generate_namespaces": false}]' 17 | ``` 18 | 19 | After changing the environment variable, after the upgrade, Terrareg will generate a provider source for Github, which will allow authentication via Github. 20 | -------------------------------------------------------------------------------- /bin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/bin/.gitignore -------------------------------------------------------------------------------- /docs/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Github Authentication 4 | 5 | Github authentication is used to: 6 | 7 | * Authenticate users to Terrareg: 8 | * determine which organisations the authenticated user is an owner of, 9 | * Authenticate Terrareg to github to allow it access information: 10 | * Obtain repository information (public) 11 | * Obtain releases for repositories (public) 12 | * Setup repository webhooks (private) 13 | 14 | To achieve this, several methods are used: 15 | 16 | * Terrareg should be setup as a Github app 17 | * If a user authenticates using Github, Terrareg will generated a token to obtain information about their organisations and their membership status of those organisations. 18 | * These tokens are temporary 19 | * When a user attempts to create a provider using a repository from a Github namespace, it will attempt to perform a Github app installation, to generate secrets to authenticate. 20 | * If a site admin attempts to create a provider for a github organisation that they are not a member of, the Github app credentials will be used to obtain the required information. 21 | -------------------------------------------------------------------------------- /docs/CONFIG.md.in: -------------------------------------------------------------------------------- 1 | ## Application environment variables 2 | 3 | The following environment variables are available to configure the application 4 | 5 | 6 | {{ CONFIG_CONTENTS }} 7 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ../CONTRIBUTING.md -------------------------------------------------------------------------------- /docs/MAIN.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /docs/UPGRADE.md: -------------------------------------------------------------------------------- 1 | ../UPGRADE.md -------------------------------------------------------------------------------- /docs/customising.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Customising Terrareg UI 4 | 5 | ## Rebranding 6 | 7 | The name of the application (in headings and titles) can be customised by setting [APPLICATION_NAME](./CONFIG.md#application_name) 8 | 9 | The logo displayed in the UI can be customised by setting [LOGO_URL](./CONFIG.md#logo_url) to a URL of an externally hosted image. 10 | 11 | ## Module page 12 | 13 | ### Version constraint 14 | 15 | The version constraint shown in the UI can be customised to display a default version that matches how you'd like users to use the module. 16 | 17 | For example, it can be used to provide an example to pin the current version or to pin the current minor version or the pin the current major version. 18 | 19 | This can be set using [TERRAFORM_EXAMPLE_VERSION_TEMPLATE](./CONFIG.md#terraform_example_version_template), which contains the available placeholders and some examples. 20 | 21 | ### Labels 22 | 23 | Modules can be labelled in two ways: 24 | 25 | * Trusted - this is applied on a per-namespace basis and all modules within the namespace are labeled as 'Trusted' 26 | * Contributed - this label is applied to any module that is not within a 'Trusted' namespace. 27 | * Verified - this can be applied on a per-module basis and can be set by anyone with MODIFY privileges of the namespace that contains the module. 28 | 29 | These labels are available as filters in the search results. 30 | 31 | The list of trusted namespaces can be configured by setting [TRUSTED_NAMESPACES](./CONFIG.md#trusted_namespaces) 32 | 33 | The textual representation of these labels can be modified by setting [TRUSTED_NAMESPACE_LABEL](./CONFIG.md#trusted_namespace_label), [VERIFIED_MODULE_LABEL](./CONFIG.md#verified_module_label) and [CONTRIBUTED_NAMESPACE_LABEL](./CONFIG.md#contributed_namespace_label). 34 | -------------------------------------------------------------------------------- /docs/design/favicon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/docs/design/favicon.xcf -------------------------------------------------------------------------------- /docs/design/logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/docs/design/logo.xcf -------------------------------------------------------------------------------- /docs/design/null.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/docs/design/null.xcf -------------------------------------------------------------------------------- /docs/diagnosing_issues.md: -------------------------------------------------------------------------------- 1 | 2 | # Diagnosing issues 3 | 4 | ## The domain shown in Terraform snippets contains the wrong URL 5 | 6 | This can happen if the following are true: 7 | 8 | * [PUBLIC_URL](./CONFIG.md#public_url) has not been set 9 | * [DOMAIN_NAME](./CONFIG.md#domain_name) (deprecated) has not been set 10 | * The domain is rewritten by a reverse proxy before traffic reaches Terrareg 11 | 12 | To fix this, set [PUBLIC_URL](./CONFIG.md#public_url) to the URL that users access the Terrareg instance. 13 | -------------------------------------------------------------------------------- /docs/example-terrareg-module-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "owner": "matt", 3 | "description": "Upstream module for VPC from AWS", 4 | "variable_template": [ 5 | { 6 | "name": "var_a", 7 | "type": "text", 8 | "quote_value": "true", 9 | "required": true 10 | }, 11 | { 12 | "name": "var_b", 13 | "type": "text", 14 | "quote_value": "true", 15 | "required": false, 16 | "default_value": "Some Value" 17 | }, 18 | { 19 | "name": "this_or_that", 20 | "type": "select", 21 | "choices": [ 22 | "var.this", 23 | "var.that" 24 | ], 25 | "allow_custom": true, 26 | "quote_value": false, 27 | "required": false 28 | }, 29 | { 30 | "name": "subnets", 31 | "type": "select", 32 | "choices": [ 33 | { 34 | "name": "hard_coded", 35 | "value": "[\"a static value\"]" 36 | }, 37 | { 38 | "name": "from_common_tf", 39 | "value": "data.terraform_remote_state.networking.blah", 40 | "additional_content": "data \"terraform_remote_state\" \"networking\" {\n\n}" 41 | } 42 | ], 43 | "quote_value": false, 44 | "required": true 45 | }, 46 | { 47 | "name": "var_d", 48 | "type": "boolean", 49 | "additional_help": "Some text that will provided, as well as the variable description", 50 | "required": true, 51 | "default_value": true 52 | }, 53 | { 54 | "name": "var_e", 55 | "type": "static", 56 | "value": "var.a_variable_present_in_parent_terraform", 57 | "required": true 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Terrareg is an open source Terraform registry. 4 | 5 | Provides features to aid usage and discovery of modules, providing: 6 | 7 | * Fully implemented Terraform modules API 8 | * Completely API driven for automating all features 9 | * Host Terraform modules internally or from an external Git source 10 | * Analytics about the usage of modules 11 | * All information about a module - README, inputs, outputs, provider requirements and managed resources 12 | * Security alerts for each module, submodule and examples 13 | * Cost estimation for each module example 14 | * Module example source code within the UI, with automatic rewriting of 'source' arguments 15 | * Interactive 'Usage builder', helping users build terraform to use the terraform modules 16 | * Hooks for git SCM applications to automatically index modules 17 | * Authentication via SSO (OpenIDConnect/SAML2) and GitHub 18 | * Terraform provider support (very early alpha version) 19 | 20 | If you like and use this project and are happy to let us know, please raise a GitHub issue, create a PR or contact [MatthewJohn](https://github.com/matthewjohn) so it can be added to the README :) 21 | 22 | For a full list of issues and pull requests, please see [https://gitlab.dockstudios.co.uk/pub/terrareg](https://gitlab.dockstudios.co.uk/pub/terrareg) 23 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/docs/logo.png -------------------------------------------------------------------------------- /docs/modules/cost_analysis.md: -------------------------------------------------------------------------------- 1 | 2 | # Cost Analysis 3 | 4 | Example cost analysis is performed using infracost. 5 | 6 | A valid API key must be provided to enable this functionality. 7 | 8 | Terrareg supports both: 9 | 10 | * Hosted Infracost solution (see [INFRACOST_API_KEY](../CONFIG.md#infracost_api_key) to setup) 11 | * Locally hosted Infracost API (see [INFRACOST_PRICING_API_ENDPOINT](../CONFIG.md#infracost_pricing_api_endpoint), [INFRACOST_API_KEY](../CONFIG.md#infracost_api_key)). 12 | 13 | To disable TLS verification for a locally hosted Infracost pricing API, see [INFRACOST_TLS_INSECURE_SKIP_VERIFY](../CONFIG.md#infracost_tls_insecure_skip_verify) 14 | -------------------------------------------------------------------------------- /docs/modules/misc.md: -------------------------------------------------------------------------------- 1 | ## Restricting providers 2 | 3 | The names of providers that can be used in the registry can be restricted using [ALLOWED_PROVIDERS](../CONFIG.md#allowed_providers). 4 | -------------------------------------------------------------------------------- /docs/modules/security_scanning.md: -------------------------------------------------------------------------------- 1 | 2 | # Security Scanning 3 | 4 | Security scanning of modules is performed automatically, without any additional setup. 5 | 6 | To disable security scanning results, set the [ENABLE_SECURITY_SCANNING](../CONFIG.md#enable_security_scanning) configuration. 7 | 8 | This configuration does not change whether security scans are performing during module indexing, instead, it disables the display of security vulnerabilities in the UI. This means that if the configuration is reverted in future, the security issues are immediately displayed without having to re-index modules. 9 | -------------------------------------------------------------------------------- /docs/providers/index.md: -------------------------------------------------------------------------------- 1 | # Provider support 2 | 3 | Terrareg supports Terraform providers and indexing via Github - this is currently in an alpha release and still requires some additional work to be completely usable. 4 | 5 | **WARNING**: (yes, these are fairly fundamental missing features, but this is an alpha feature) it is not possible to refresh new versions of providers at present, nor is it possble to delete/modify providers after creation. 6 | 7 | **Do not use this feature yet in any production environment.** 8 | 9 | To add providers: 10 | 11 | * Setup a "Provider source" for the VCS that the provider is hosted on. 12 | * Following the Hashicorp documentation for publishing providers in Github (adding GPG key, Github actions for creating releases and generated SHA and SHA.sig files). 13 | * Authenticate to Terrareg either via the VCS provider SSO and install the created Github application in the user/org that the provider is present OR (if `default_access_token` provider source config has been configured). 14 | * Goto 'Create -> Provider', select the namespace that matches the VCS org/user (use 'Refresh namespaces' if need be) 15 | * Select the repository and click 'Create provider' 16 | * Terrareg will index the latest version, which must be published in Github at the time of creation. 17 | -------------------------------------------------------------------------------- /docs/quickstart.md: -------------------------------------------------------------------------------- 1 | ## Quick start 2 | 3 | To easily get started, using a pre-built docker image: 4 | 5 | # Create secret key for session data 6 | export SECRET_KEY=$(python -c 'import secrets; print(secrets.token_hex())') 7 | 8 | # Run container, specifying secret key and admin password 9 | docker run -ti -p 5000:5000 -e PUBLIC_URL=http://localhost:5000 -e MIGRATE_DATABASE=True -e SECRET_KEY=$SECRET_KEY -e ADMIN_AUTHENTICATION_TOKEN=MySuperSecretPassword ghcr.io/matthewjohn/terrareg:latest 10 | 11 | 12 | Visit http://localhost:5000 in your browser and follow the initial setup guide. 13 | -------------------------------------------------------------------------------- /docs/screenshots/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/docs/screenshots/example.png -------------------------------------------------------------------------------- /docs/screenshots/homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/docs/screenshots/homepage.png -------------------------------------------------------------------------------- /docs/screenshots/module_provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/docs/screenshots/module_provider.png -------------------------------------------------------------------------------- /docs/screenshots/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/docs/screenshots/search.png -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Terrareg 2 | repo_url: https://github.com/matthewjohn/terrareg 3 | plugins: 4 | - glightbox 5 | theme: 6 | name: material 7 | logo: ./logo.png 8 | features: 9 | - content.action.edit 10 | - content.action.view 11 | - content.code.annotate 12 | - content.code.copy 13 | - content.tabs.link 14 | - content.tooltips 15 | - header.autohide 16 | - navigation.expand 17 | - navigation.footer 18 | - navigation.indexes 19 | - navigation.instant 20 | - navigation.sections 21 | - navigation.tabs 22 | - navigation.tabs.sticky 23 | - navigation.top 24 | - navigation.tracking 25 | - search.highlight 26 | - search.share 27 | - search.suggest 28 | - toc.follow 29 | - toc.integrate 30 | 31 | markdown_extensions: 32 | - admonition 33 | - attr_list 34 | - md_in_html 35 | 36 | nav: 37 | - Introduction: 38 | - index.md 39 | - README: MAIN.md 40 | - Quick Start: 41 | - quickstart.md 42 | - Deployment: 43 | - deployment.md 44 | - Upgrading: UPGRADE.md 45 | - diagnosing_issues.md 46 | - Configuration: 47 | - security.md 48 | - customising.md 49 | - Configuration Reference: CONFIG.md 50 | - API Reference: API.md 51 | - Modules: 52 | - modules/overview.md 53 | - modules/storage.md 54 | - modules/uploading.md 55 | - modules/best_practices.md 56 | - modules/analytics.md 57 | - modules/cost_analysis.md 58 | - modules/security_scanning.md 59 | - modules/misc.md 60 | - Providers: 61 | - providers/index.md 62 | - Development: 63 | - LOCAL_DEVELOPMENT.md 64 | - CONTRIBUTING.md 65 | 66 | extra: 67 | version: 68 | default: latest 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "bulma": "^0.9.4", 4 | "node-sass": "^8.0.0" 5 | }, 6 | "scripts": { 7 | "css-build": "node-sass --omit-source-map-url sass/cherry-dark.scss terrareg/static/css/bulma/cherry-dark/bulmaswatch.min.css --output-style compressed", 8 | "css-watch": "npm run css-build -- --watch", 9 | "start": "npm run css-watch" 10 | } 11 | } -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "dockstudios/renovate/renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /scripts/check_spelling.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | set -e 5 | 6 | pylint --disable all --enable spelling --spelling-dict=en_GB --spelling-private-dict-file=./scripts/dictionary.txt terrareg 7 | 8 | -------------------------------------------------------------------------------- /scripts/dictionary.txt: -------------------------------------------------------------------------------- 1 | Terrareg 2 | terrareg 3 | datetime 4 | JWT 5 | iglob 6 | init 7 | src 8 | img 9 | href 10 | alt 11 | IdP 12 | redirect's 13 | Infracost 14 | config 15 | artifacts 16 | terraformrc 17 | RC 18 | backend 19 | tf 20 | tfsec 21 | etc 22 | regex 23 | regexes 24 | pk 25 | inframap 26 | graphiz 27 | userinfo 28 | Userinfo 29 | pyop 30 | gz 31 | plugin 32 | tfswitch 33 | CWD 34 | cwd 35 | int 36 | IDP 37 | upsert 38 | NX 39 | graphviz 40 | arg 41 | args 42 | kwargs 43 | vars 44 | dict 45 | cytoscape 46 | Github 47 | github 48 | Bitbucket 49 | Gitlab 50 | gitlab 51 | json 52 | params 53 | com 54 | xxxxxx 55 | yyyyyy 56 | SSO 57 | SP 58 | heredocs 59 | lt 60 | gt 61 | url 62 | timezone 63 | parsable 64 | abspath 65 | Hashicorp 66 | TODO 67 | PEM 68 | Dockerfile 69 | cer 70 | keygen 71 | rsa 72 | publickey 73 | req 74 | openssl 75 | genrsa 76 | stdout 77 | artifact 78 | rc 79 | SQLalchemy 80 | ssh-keygen 81 | PL 82 | HCL 83 | pylonsproject 84 | DSN 85 | org 86 | noqa 87 | mysql 88 | mysqlconnector 89 | builtin 90 | submodulename 91 | myexample 92 | md 93 | ascii 94 | gpg 95 | armor 96 | SCM 97 | Terraform 98 | terraform 99 | namespace 100 | namespaces 101 | auth 102 | dev 103 | submodule 104 | submodules 105 | pre 106 | http 107 | https 108 | EOL 109 | organization 110 | repo 111 | UI 112 | SSL 113 | performant 114 | enum 115 | preprocessor 116 | hostname 117 | subquery 118 | overridable 119 | subclasses 120 | authorization 121 | readme 122 | subversions 123 | sanitisation 124 | Hacky 125 | targetted 126 | TLS 127 | html 128 | authorize 129 | subprocess 130 | xxxaaabbccc 131 | repos 132 | endpoint 133 | customisations 134 | py 135 | tfplugindocs 136 | SHA 137 | selectable 138 | abcdefg 139 | pem 140 | supersecretkey 141 | kwarg 142 | endpoint 143 | prepended 144 | prepending 145 | FH 146 | netloc 147 | opentofu 148 | pathspec -------------------------------------------------------------------------------- /scripts/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | # Check if database upgrades are to be performed 7 | if [ "${MIGRATE_DATABASE}" == "True" ] 8 | then 9 | poetry run alembic upgrade head 10 | fi 11 | 12 | # Check whether to upgrade database and exit 13 | if [ "${MIGRATE_DATABASE_ONLY}" == "True" ] 14 | then 15 | exit 16 | fi 17 | 18 | # If private key has been set, inject into SSH directory 19 | if [ "${SSH_PRIVATE_KEY}" != "" ] 20 | then 21 | mkdir ~/.ssh 22 | chmod 700 ~/.ssh 23 | touch ~/.ssh/id_rsa 24 | chmod 600 ~/.ssh/id_rsa 25 | echo -e "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa 26 | fi 27 | 28 | # Run main executable 29 | poetry run python ./terrareg.py 30 | -------------------------------------------------------------------------------- /scripts/generate_test_gpg_key.py: -------------------------------------------------------------------------------- 1 | #!python 2 | import gnupg 3 | import tempfile 4 | 5 | with tempfile.TemporaryDirectory() as temp_dir: 6 | gpg = gnupg.GPG(gnupghome=temp_dir, keyring=None, use_agent=False) 7 | key = gpg.gen_key(gpg.gen_key_input(key_type="RSA", key_length=1024, passphrase='password')) 8 | 9 | print(f""" 10 | {{ 11 | # {key.fingerprint} 12 | "ascii_armor": \""" 13 | {key.gpg.export_keys(key.fingerprint)} 14 | \""".strip(), 15 | }} 16 | """) -------------------------------------------------------------------------------- /scripts/preview_release_notes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker run \ 4 | -ti --rm -v `pwd`:/code \ 5 | -w /code \ 6 | --user=$UID \ 7 | semantic-release \ 8 | semantic-release \ 9 | --dry-run --no-ci \ 10 | --repository-url=file:///code \ 11 | --branches=$(git rev-parse --abbrev-ref HEAD) 12 | -------------------------------------------------------------------------------- /scripts/upload_module.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | base_url=$1 6 | 7 | namespace=$2 8 | name=$3 9 | provider=$4 10 | version=$5 11 | file=$6 12 | 13 | # Ensure source file exists 14 | if ! test -f $file 15 | then 16 | echo Source file does not exist: $file 17 | exit 1 18 | fi 19 | 20 | echo Uploading module 21 | curl -k -X POST \ 22 | "${base_url}/v1/terrareg/modules/${namespace}/${name}/${provider}/${version}/upload" \ 23 | -F file="@${file}" -H "X-Terrareg-ApiKey: $UPLOAD_API_KEY" 24 | 25 | echo Upload complete 26 | 27 | echo 28 | echo "Would you like to 'publish' the module version? (Y/N)" 29 | read publish 30 | 31 | if [ "$publish" == "Y" ] 32 | then 33 | curl -k -XPOST -H "X-Terrareg-ApiKey: $PUBLISH_API_KEY" \ 34 | "${base_url}/v1/terrareg/modules/${namespace}/${name}/${provider}/${version}/publish" 35 | fi 36 | 37 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | with open('requirements.txt') as f: 6 | required = f.read().splitlines() 7 | 8 | setup(name='terrareg', 9 | version='0.1.0', 10 | description='Terraform module regsitry with analytics', 11 | author='Matt Comben', 12 | author_email='matthew@dockstudios.co.uk', 13 | url='https://gitlab.dockstudios.co.uk/pub/terrareg', 14 | packages=['terrareg'], 15 | install_requires=required 16 | ) 17 | -------------------------------------------------------------------------------- /terrareg.py: -------------------------------------------------------------------------------- 1 | 2 | from argparse import ArgumentParser 3 | 4 | from terrareg.server import Server 5 | import terrareg.config 6 | 7 | 8 | parser = ArgumentParser('terrareg') 9 | config = terrareg.config.Config() 10 | 11 | parser.add_argument('--ssl-cert-private-key', dest='ssl_priv_key', 12 | default=config.SSL_CERT_PRIVATE_KEY, 13 | help='Path to SSL private key') 14 | parser.add_argument('--ssl-cert-public-key', dest='ssl_pub_key', 15 | default=config.SSL_CERT_PUBLIC_KEY, 16 | help='Path to SSL public key') 17 | 18 | args = parser.parse_args() 19 | 20 | s = Server(ssl_public_key=args.ssl_pub_key, ssl_private_key=args.ssl_priv_key) 21 | 22 | if config.SERVER == terrareg.config.ServerType.WAITRESS: 23 | s.run_waitress() 24 | else: 25 | s.run() 26 | 27 | -------------------------------------------------------------------------------- /terrareg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/__init__.py -------------------------------------------------------------------------------- /terrareg/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /terrareg/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/02c137632f24_add_tables_for_user_groups_and_user_.py: -------------------------------------------------------------------------------- 1 | """Add tables for user groups and user group namespace permissions 2 | 3 | Revision ID: 02c137632f24 4 | Revises: d72c5339c21b 5 | Create Date: 2022-10-05 06:57:38.384099 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '02c137632f24' 14 | down_revision = 'd72c5339c21b' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('user_group', 22 | sa.Column('id', sa.Integer(), nullable=False, autoincrement=True), 23 | sa.Column('name', sa.String(length=128), nullable=False, unique=True), 24 | sa.Column('site_admin', sa.Boolean(), nullable=False), 25 | sa.PrimaryKeyConstraint('id') 26 | ) 27 | op.create_table('user_group_namespace_permission', 28 | sa.Column('user_group_id', sa.Integer(), nullable=False), 29 | sa.Column('namespace_id', sa.Integer(), nullable=False), 30 | sa.Column('permission_type', sa.Enum('FULL', 'MODIFY', name='usergroupnamespacepermissiontype'), nullable=True), 31 | sa.ForeignKeyConstraint(['namespace_id'], ['namespace.id'], name='fk_user_group_namespace_permission_namespace_id_namespace_id', onupdate='CASCADE', ondelete='CASCADE'), 32 | sa.ForeignKeyConstraint(['user_group_id'], ['user_group.id'], name='fk_user_group_namespace_permission_user_group_id_user_group_id', onupdate='CASCADE', ondelete='CASCADE'), 33 | sa.PrimaryKeyConstraint('user_group_id', 'namespace_id') 34 | ) 35 | # ### end Alembic commands ### 36 | 37 | 38 | def downgrade(): 39 | # ### commands auto generated by Alembic - please adjust! ### 40 | op.drop_table('user_group_namespace_permission') 41 | op.drop_table('user_group') 42 | # ### end Alembic commands ### 43 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/0832273b5d04_add_audit_type_for_module_provider_.py: -------------------------------------------------------------------------------- 1 | """Add audit type for module provider redirect deletion 2 | 3 | Revision ID: 0832273b5d04 4 | Revises: 7b6cac0d522f 5 | Create Date: 2023-08-20 07:31:52.029767 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from terrareg.alembic.versions import Enum, update_enum 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '0832273b5d04' 14 | down_revision = '7b6cac0d522f' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | old_audit_values = [ 20 | 'NAMESPACE_CREATE', 'NAMESPACE_MODIFY_NAME', 'NAMESPACE_MODIFY_DISPLAY_NAME', 'MODULE_PROVIDER_CREATE', 'MODULE_PROVIDER_DELETE', 'MODULE_PROVIDER_UPDATE_GIT_TAG_FORMAT', 21 | 'MODULE_PROVIDER_UPDATE_GIT_PROVIDER', 'MODULE_PROVIDER_UPDATE_GIT_PATH', 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_BASE_URL', 22 | 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_CLONE_URL', 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_BROWSE_URL', 'MODULE_PROVIDER_UPDATE_VERIFIED', 23 | 'MODULE_VERSION_INDEX', 'MODULE_VERSION_PUBLISH', 'MODULE_VERSION_DELETE', 'USER_GROUP_CREATE', 'USER_GROUP_DELETE', 'USER_GROUP_NAMESPACE_PERMISSION_ADD', 24 | 'USER_GROUP_NAMESPACE_PERMISSION_MODIFY', 'USER_GROUP_NAMESPACE_PERMISSION_DELETE', 'USER_LOGIN', 25 | 'MODULE_PROVIDER_UPDATE_NAMESPACE', 'MODULE_PROVIDER_UPDATE_MODULE_NAME', 'MODULE_PROVIDER_UPDATE_PROVIDER_NAME', 'NAMESPACE_DELETE' 26 | ] 27 | new_audit_values = [ 28 | 'MODULE_PROVIDER_REDIRECT_DELETE' 29 | ] 30 | 31 | def upgrade(): 32 | # ### commands auto generated by Alembic - please adjust! ### 33 | update_enum( 34 | 'audit_history', 'action', 'auditaction', 35 | old_audit_values, 36 | old_audit_values + new_audit_values 37 | ) 38 | # ### end Alembic commands ### 39 | 40 | 41 | def downgrade(): 42 | # ### commands auto generated by Alembic - please adjust! ### 43 | update_enum( 44 | 'audit_history', 'action', 'auditaction', 45 | old_audit_values + new_audit_values, 46 | old_audit_values 47 | ) 48 | # ### end Alembic commands ### -------------------------------------------------------------------------------- /terrareg/alembic/versions/0e3de802e5e0_.py: -------------------------------------------------------------------------------- 1 | """Convert blobs to medium blobs 2 | 3 | Revision ID: 0e3de802e5e0 4 | Revises: aef5947a7e1d 5 | Create Date: 2022-05-07 09:08:33.674549 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | import sqlalchemy.dialects.mysql 11 | 12 | 13 | # revision identifiers, used by Alembic. 14 | revision = '0e3de802e5e0' 15 | down_revision = 'aef5947a7e1d' 16 | branch_labels = None 17 | depends_on = None 18 | 19 | def MediumBlob(): 20 | return sa.BLOB().with_variant(sqlalchemy.dialects.mysql.MEDIUMBLOB(), "mysql").with_variant(sa.LargeBinary(), "postgresql") 21 | 22 | def upgrade(): 23 | with op.batch_alter_table('module_version') as batch_op: 24 | # Convert blob columns to MEDIUMBLOB 25 | batch_op.alter_column('readme_content', existing_type=sa.BLOB, type_=MediumBlob()) 26 | batch_op.alter_column('module_details', existing_type=sa.BLOB, type_=MediumBlob()) 27 | batch_op.alter_column('variable_template', existing_type=sa.BLOB, type_=MediumBlob()) 28 | with op.batch_alter_table('submodule') as batch_op: 29 | batch_op.alter_column('readme_content', existing_type=sa.BLOB, type_=MediumBlob()) 30 | batch_op.alter_column('module_details', existing_type=sa.BLOB, type_=MediumBlob()) 31 | 32 | # ### end Alembic commands ### 33 | 34 | 35 | def downgrade(): 36 | with op.batch_alter_table('module_version') as batch_op: 37 | batch_op.alter_column('readme_content', existing_type=MediumBlob(), type_=sa.BLOB) 38 | batch_op.alter_column('module_details', existing_type=MediumBlob(), type_=sa.BLOB) 39 | batch_op.alter_column('variable_template', existing_type=MediumBlob(), type_=sa.BLOB) 40 | 41 | with op.batch_alter_table('submodule') as batch_op: 42 | batch_op.alter_column('readme_content', existing_type=MediumBlob(), type_=sa.BLOB) 43 | batch_op.alter_column('module_details', existing_type=MediumBlob(), type_=sa.BLOB) 44 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/161e0f33603c_add_audit_table.py: -------------------------------------------------------------------------------- 1 | """Add audit table 2 | 3 | Revision ID: 161e0f33603c 4 | Revises: 02c137632f24 5 | Create Date: 2022-11-21 17:50:45.798110 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '161e0f33603c' 14 | down_revision = '02c137632f24' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('audit_history', 22 | sa.Column('id', sa.Integer(), nullable=False, autoincrement=True), 23 | sa.Column('timestamp', sa.DateTime(), nullable=True), 24 | sa.Column('username', sa.String(length=128), nullable=True), 25 | sa.Column('action', sa.Enum('NAMESPACE_CREATE', 'MODULE_PROVIDER_CREATE', 'MODULE_PROVIDER_DELETE', 'MODULE_PROVIDER_UPDATE_GIT_TAG_FORMAT', 'MODULE_PROVIDER_UPDATE_GIT_PROVIDER', 'MODULE_PROVIDER_UPDATE_GIT_PATH', 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_BASE_URL', 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_CLONE_URL', 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_BROWSE_URL', 'MODULE_PROVIDER_UPDATE_VERIFIED', 'MODULE_VERSION_INDEX', 'MODULE_VERSION_PUBLISH', 'MODULE_VERSION_DELETE', 'USER_GROUP_CREATE', 'USER_GROUP_DELETE', 'USER_GROUP_NAMESPACE_PERMISSION_ADD', 'USER_GROUP_NAMESPACE_PERMISSION_MODIFY', 'USER_GROUP_NAMESPACE_PERMISSION_DELETE', 'USER_LOGIN', name='auditaction'), nullable=True), 26 | sa.Column('object_type', sa.String(length=128), nullable=True), 27 | sa.Column('object_id', sa.String(length=128), nullable=True), 28 | sa.Column('old_value', sa.String(length=128), nullable=True), 29 | sa.Column('new_value', sa.String(length=128), nullable=True), 30 | sa.PrimaryKeyConstraint('id') 31 | ) 32 | # ### end Alembic commands ### 33 | 34 | 35 | def downgrade(): 36 | # ### commands auto generated by Alembic - please adjust! ### 37 | op.drop_table('audit_history') 38 | # ### end Alembic commands ### 39 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/1f1ebdc845a0_add_audit_events_for_module_provider_.py: -------------------------------------------------------------------------------- 1 | """Add audit events for module provider name/namespace modification 2 | 3 | Revision ID: 1f1ebdc845a0 4 | Revises: ff6378a8ab9d 5 | Create Date: 2023-08-02 07:41:27.699393 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from terrareg.alembic.versions import Enum, update_enum 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '1f1ebdc845a0' 14 | down_revision = 'ff6378a8ab9d' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | old_audit_values = [ 19 | 'NAMESPACE_CREATE', 'NAMESPACE_MODIFY_NAME', 'NAMESPACE_MODIFY_DISPLAY_NAME', 'MODULE_PROVIDER_CREATE', 'MODULE_PROVIDER_DELETE', 'MODULE_PROVIDER_UPDATE_GIT_TAG_FORMAT', 20 | 'MODULE_PROVIDER_UPDATE_GIT_PROVIDER', 'MODULE_PROVIDER_UPDATE_GIT_PATH', 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_BASE_URL', 21 | 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_CLONE_URL', 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_BROWSE_URL', 'MODULE_PROVIDER_UPDATE_VERIFIED', 22 | 'MODULE_VERSION_INDEX', 'MODULE_VERSION_PUBLISH', 'MODULE_VERSION_DELETE', 'USER_GROUP_CREATE', 'USER_GROUP_DELETE', 'USER_GROUP_NAMESPACE_PERMISSION_ADD', 23 | 'USER_GROUP_NAMESPACE_PERMISSION_MODIFY', 'USER_GROUP_NAMESPACE_PERMISSION_DELETE', 'USER_LOGIN' 24 | ] 25 | new_audit_values = [ 26 | 'MODULE_PROVIDER_UPDATE_NAMESPACE', 'MODULE_PROVIDER_UPDATE_MODULE_NAME', 'MODULE_PROVIDER_UPDATE_PROVIDER_NAME' 27 | ] 28 | 29 | def upgrade(): 30 | # ### commands auto generated by Alembic - please adjust! ### 31 | update_enum( 32 | 'audit_history', 'action', 'auditaction', 33 | old_audit_values, 34 | old_audit_values + new_audit_values 35 | ) 36 | # ### end Alembic commands ### 37 | 38 | 39 | def downgrade(): 40 | # ### commands auto generated by Alembic - please adjust! ### 41 | update_enum( 42 | 'audit_history', 'action', 'auditaction', 43 | old_audit_values + new_audit_values, 44 | old_audit_values 45 | ) 46 | # ### end Alembic commands ### 47 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/210586684f86_add_namespace_display_name.py: -------------------------------------------------------------------------------- 1 | """Add namespace display_name 2 | 3 | Revision ID: 210586684f86 4 | Revises: 71d311c23025 5 | Create Date: 2023-01-24 07:07:45.170022 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '210586684f86' 14 | down_revision = '71d311c23025' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | with op.batch_alter_table("namespace") as namespace_batch: 21 | namespace_batch.add_column(sa.Column("display_name", sa.String(length=128), nullable=True)) 22 | 23 | 24 | def downgrade(): 25 | with op.batch_alter_table("namespace") as namespace_batch: 26 | namespace_batch.drop_column("display_name") 27 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/2bc9677e93d1_add_git_ref_column_to_module_version_.py: -------------------------------------------------------------------------------- 1 | """Add git ref column to module version table 2 | 3 | Revision ID: 2bc9677e93d1 4 | Revises: ee14b27baeb1 5 | Create Date: 2024-02-26 05:33:08.413995 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '2bc9677e93d1' 14 | down_revision = 'ee14b27baeb1' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('module_version', sa.Column('git_sha', sa.String(length=128), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('module_version', 'git_sha') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/2e9fe1292b2b_add_audit_events_for_namespace_deletion.py: -------------------------------------------------------------------------------- 1 | """Add audit events for namespace deletion 2 | 3 | Revision ID: 2e9fe1292b2b 4 | Revises: 1f1ebdc845a0 5 | Create Date: 2023-08-10 06:47:37.039325 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from terrareg.alembic.versions import update_enum 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '2e9fe1292b2b' 14 | down_revision = '1f1ebdc845a0' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | old_audit_values = [ 19 | 'NAMESPACE_CREATE', 'NAMESPACE_MODIFY_NAME', 'NAMESPACE_MODIFY_DISPLAY_NAME', 'MODULE_PROVIDER_CREATE', 'MODULE_PROVIDER_DELETE', 'MODULE_PROVIDER_UPDATE_GIT_TAG_FORMAT', 20 | 'MODULE_PROVIDER_UPDATE_GIT_PROVIDER', 'MODULE_PROVIDER_UPDATE_GIT_PATH', 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_BASE_URL', 21 | 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_CLONE_URL', 'MODULE_PROVIDER_UPDATE_GIT_CUSTOM_BROWSE_URL', 'MODULE_PROVIDER_UPDATE_VERIFIED', 22 | 'MODULE_VERSION_INDEX', 'MODULE_VERSION_PUBLISH', 'MODULE_VERSION_DELETE', 'USER_GROUP_CREATE', 'USER_GROUP_DELETE', 'USER_GROUP_NAMESPACE_PERMISSION_ADD', 23 | 'USER_GROUP_NAMESPACE_PERMISSION_MODIFY', 'USER_GROUP_NAMESPACE_PERMISSION_DELETE', 'USER_LOGIN', 24 | 'MODULE_PROVIDER_UPDATE_NAMESPACE', 'MODULE_PROVIDER_UPDATE_MODULE_NAME', 'MODULE_PROVIDER_UPDATE_PROVIDER_NAME', 25 | ] 26 | new_audit_values = [ 27 | 'NAMESPACE_DELETE' 28 | ] 29 | 30 | def upgrade(): 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | update_enum( 33 | 'audit_history', 'action', 'auditaction', 34 | old_audit_values, 35 | old_audit_values + new_audit_values 36 | ) 37 | # ### end Alembic commands ### 38 | 39 | 40 | def downgrade(): 41 | # ### commands auto generated by Alembic - please adjust! ### 42 | update_enum( 43 | 'audit_history', 'action', 'auditaction', 44 | old_audit_values + new_audit_values, 45 | old_audit_values 46 | ) 47 | # ### end Alembic commands ### 48 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/437ccafb9882_add_session_table_to_hold_active_.py: -------------------------------------------------------------------------------- 1 | """Add session table to hold active sessions 2 | 3 | Revision ID: 437ccafb9882 4 | Revises: d499042fad3b 5 | Create Date: 2022-07-18 07:23:46.651210 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '437ccafb9882' 14 | down_revision = 'd499042fad3b' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # Create session table 21 | op.create_table('session', 22 | sa.Column('id', sa.String(length=128), nullable=False), 23 | sa.Column('expiry', sa.DateTime(), nullable=False), 24 | sa.PrimaryKeyConstraint('id') 25 | ) 26 | 27 | 28 | def downgrade(): 29 | # Remove session table 30 | op.drop_table('session') 31 | 32 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/51d3707d6334_add_git_base_path_to_module_provider_.py: -------------------------------------------------------------------------------- 1 | """Add git_path to module provider table 2 | 3 | Revision ID: 51d3707d6334 4 | Revises: 437ccafb9882 5 | Create Date: 2022-07-22 07:57:02.500034 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '51d3707d6334' 14 | down_revision = '437ccafb9882' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # Add new column to module provider table for git path 21 | with op.batch_alter_table('module_provider', schema=None) as batch_op: 22 | batch_op.add_column(sa.Column('git_path', sa.String(length=1024), nullable=True)) 23 | 24 | 25 | def downgrade(): 26 | # Remove newly added column 27 | with op.batch_alter_table('module_provider', schema=None) as batch_op: 28 | batch_op.drop_column('git_path') 29 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/6416ffbf606d_add_infracost_output_column_to_module_.py: -------------------------------------------------------------------------------- 1 | """Add Infracost output column to module_details table 2 | 3 | Revision ID: 6416ffbf606d 4 | Revises: 51d3707d6334 5 | Create Date: 2022-08-17 07:09:19.890446 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import mysql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '6416ffbf606d' 14 | down_revision = '51d3707d6334' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('module_details', sa.Column('infracost', sa.LargeBinary(length=16777215).with_variant(mysql.MEDIUMBLOB(), 'mysql'), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | with op.batch_alter_table('module_details') as module_details_op: 28 | module_details_op.drop_column('infracost') 29 | # ### end Alembic commands ### 30 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/6dd8adb5e1e3_postgres_fixes.py: -------------------------------------------------------------------------------- 1 | """postgres-fixes 2 | 3 | Revision ID: 6dd8adb5e1e3 4 | Revises: f9a80ea383cc 5 | Create Date: 2025-04-23 04:40:38.180082 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '6dd8adb5e1e3' 14 | down_revision = 'f9a80ea383cc' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | if op.get_bind().engine.name == "postgresql": 22 | op.drop_constraint('analytics_parent_module_version_fkey', 'analytics', type_='foreignkey') 23 | # op.alter_column('module_provider', 'namespace_id', 24 | # existing_type=sa.INTEGER(), 25 | # nullable=False) 26 | # op.alter_column('namespace', 'namespace', 27 | # existing_type=sa.VARCHAR(length=128), 28 | # nullable=False) 29 | # op.create_foreign_key('fk_provider_latest_version_id_provider_version_id', 'provider', 'provider_version', ['latest_version_id'], ['id'], onupdate='CASCADE', ondelete='SET NULL', use_alter=True) 30 | # ### end Alembic commands ### 31 | 32 | 33 | def downgrade(): 34 | # ### commands auto generated by Alembic - please adjust! ### 35 | # op.drop_constraint('fk_provider_latest_version_id_provider_version_id', 'provider', type_='foreignkey') 36 | # op.alter_column('namespace', 'namespace', 37 | # existing_type=sa.VARCHAR(length=128), 38 | # nullable=True) 39 | # op.alter_column('module_provider', 'namespace_id', 40 | # existing_type=sa.INTEGER(), 41 | # nullable=True) 42 | op.create_foreign_key('analytics_parent_module_version_fkey', 'analytics', 'module_version', ['parent_module_version'], ['id'], onupdate='CASCADE', ondelete='CASCADE') 43 | # ### end Alembic commands ### 44 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/71d311c23025_add_module_version_column_for_.py: -------------------------------------------------------------------------------- 1 | """Add module version column for extraction version 2 | 3 | Revision ID: 71d311c23025 4 | Revises: 8b594ed19f9d 5 | Create Date: 2023-01-22 09:18:23.880577 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '71d311c23025' 14 | down_revision = '8b594ed19f9d' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | with op.batch_alter_table("module_version") as module_version_batch: 21 | module_version_batch.add_column(sa.Column('extraction_version', sa.Integer(), nullable=True)) 22 | 23 | # Update column with default value 24 | c = op.get_bind() 25 | c.execute(f"""UPDATE module_version SET extraction_version=1""") 26 | 27 | 28 | def downgrade(): 29 | with op.batch_alter_table("module_version") as module_version_batch: 30 | module_version_batch.drop_column('extraction_version') 31 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/81609b772646_add_module_details_columns_for_.py: -------------------------------------------------------------------------------- 1 | """Add module_details columns for terraform_version and terraform_modules 2 | 3 | Revision ID: 81609b772646 4 | Revises: 2e9fe1292b2b 5 | Create Date: 2023-08-10 07:04:38.026903 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import mysql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '81609b772646' 14 | down_revision = '2e9fe1292b2b' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('module_details', sa.Column('terraform_modules', sa.LargeBinary(length=16777215).with_variant(mysql.MEDIUMBLOB(), 'mysql'), nullable=True)) 22 | op.add_column('module_details', sa.Column('terraform_version', sa.LargeBinary(length=16777215).with_variant(mysql.MEDIUMBLOB(), 'mysql'), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | with op.batch_alter_table('module_details') as module_details_op: 29 | module_details_op.drop_column('terraform_version') 30 | module_details_op.drop_column('terraform_modules') 31 | # ### end Alembic commands ### 32 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/8b594ed19f9d_add_columns_for_graph_data.py: -------------------------------------------------------------------------------- 1 | """Add columns for graph data 2 | 3 | Revision ID: 8b594ed19f9d 4 | Revises: 161e0f33603c 5 | Create Date: 2023-01-04 07:23:38.528528 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import mysql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '8b594ed19f9d' 14 | down_revision = '161e0f33603c' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | with op.batch_alter_table("module_details") as batch_op: 22 | batch_op.add_column(sa.Column('terraform_graph', sa.LargeBinary(length=16777215).with_variant(mysql.MEDIUMBLOB(), 'mysql'), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | with op.batch_alter_table("module_details") as batch_op: 29 | batch_op.drop_column('terraform_graph') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/95f56a77a6e6_.py: -------------------------------------------------------------------------------- 1 | """Add beta flag to module version 2 | 3 | Revision ID: 95f56a77a6e6 4 | Revises: 0e3de802e5e0 5 | Create Date: 2022-05-21 07:49:04.047682 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '95f56a77a6e6' 14 | down_revision = '0e3de802e5e0' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # Add beta column, allowing nullable value 21 | op.add_column('module_version', sa.Column('beta', sa.BOOLEAN(), nullable=True)) 22 | 23 | # Set any pre-existing rows to beta 0 24 | false_value = "0" 25 | if op.get_bind().engine.name == 'postgresql': 26 | false_value = "false" 27 | op.execute(f"""UPDATE module_version set beta={false_value}""") 28 | 29 | # Disable nullable flag in column 30 | with op.batch_alter_table('module_version', schema=None) as batch_op: 31 | batch_op.alter_column('beta', existing_type=sa.BOOLEAN(), nullable=False) 32 | 33 | 34 | def downgrade(): 35 | op.drop_column('module_version', 'beta') 36 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/9dbcb4240c55_add_git_path_template_column_to_git_.py: -------------------------------------------------------------------------------- 1 | """Add git_path_template column to git provider 2 | 3 | Revision ID: 9dbcb4240c55 4 | Revises: 2bc9677e93d1 5 | Create Date: 2024-03-25 07:11:36.123751 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '9dbcb4240c55' 14 | down_revision = '2bc9677e93d1' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('git_provider', sa.Column('git_path_template', sa.String(length=1024), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | with op.batch_alter_table('git_provider') as batch_op: 28 | batch_op.drop_column('git_path_template') 29 | # ### end Alembic commands ### 30 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/a36ffbb6580e_add_index_to_parent_module_version_.py: -------------------------------------------------------------------------------- 1 | """Add index to parent_module_version column of analytics table 2 | 3 | Revision ID: a36ffbb6580e 4 | Revises: ee82678fcda1 5 | Create Date: 2022-06-25 13:03:41.700763 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | from terrareg.alembic.versions import constraint_exists 12 | 13 | 14 | # revision identifiers, used by Alembic. 15 | revision = 'a36ffbb6580e' 16 | down_revision = 'ee82678fcda1' 17 | branch_labels = None 18 | depends_on = None 19 | 20 | 21 | def upgrade(): 22 | with op.batch_alter_table('analytics', schema=None) as batch_op: 23 | if constraint_exists(op.get_bind(), 'analytics', 'fk_analytics_parent_module_version_module_version_id'): 24 | batch_op.drop_index('fk_analytics_parent_module_version_module_version_id') 25 | batch_op.create_index(op.f('ix_analytics_parent_module_version'), ['parent_module_version'], unique=False) 26 | 27 | def downgrade(): 28 | with op.batch_alter_table('analytics', schema=None) as batch_op: 29 | if constraint_exists(op.get_bind(), 'analytics', 'ix_analytics_parent_module_version'): 30 | batch_op.drop_index(op.f('ix_analytics_parent_module_version')) 31 | batch_op.create_index('fk_analytics_parent_module_version_module_version_id', ['parent_module_version'], unique=False) 32 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/acd5e83c690f_add_table_for_module_version_files.py: -------------------------------------------------------------------------------- 1 | """Add table for module version files 2 | 3 | Revision ID: acd5e83c690f 4 | Revises: 6416ffbf606d 5 | Create Date: 2022-09-01 08:02:54.337726 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import mysql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'acd5e83c690f' 14 | down_revision = '6416ffbf606d' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('module_version_file', 22 | sa.Column('id', sa.Integer(), nullable=False, autoincrement=True), 23 | sa.Column('module_version_id', sa.Integer(), nullable=False), 24 | sa.Column('path', sa.String(length=128), nullable=False), 25 | sa.Column('content', sa.LargeBinary(length=16777215).with_variant(mysql.MEDIUMBLOB(), 'mysql'), nullable=True), 26 | sa.ForeignKeyConstraint(['module_version_id'], ['module_version.id'], name='fk_module_version_file_module_version_id_module_version_id', onupdate='CASCADE', ondelete='CASCADE'), 27 | sa.PrimaryKeyConstraint('id') 28 | ) 29 | # ### end Alembic commands ### 30 | 31 | 32 | def downgrade(): 33 | # ### commands auto generated by Alembic - please adjust! ### 34 | op.drop_table('module_version_file') 35 | # ### end Alembic commands ### 36 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/b0f952e4a027_add_table_for_example_file_contents.py: -------------------------------------------------------------------------------- 1 | """Add table for example file contents 2 | 3 | Revision ID: b0f952e4a027 4 | Revises: 95f56a77a6e6 5 | Create Date: 2022-05-22 07:28:12.639592 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import mysql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'b0f952e4a027' 14 | down_revision = '95f56a77a6e6' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('example_file', 22 | sa.Column('id', sa.Integer(), nullable=False, autoincrement=True), 23 | sa.Column('submodule_id', sa.Integer(), nullable=False), 24 | sa.Column('path', sa.String(length=128), nullable=False), 25 | sa.Column('content', sa.LargeBinary(length=16777215).with_variant(mysql.MEDIUMBLOB(), 'mysql'), nullable=True), 26 | sa.ForeignKeyConstraint(['submodule_id'], ['submodule.id'], onupdate='CASCADE', ondelete='CASCADE'), 27 | sa.PrimaryKeyConstraint('id') 28 | ) 29 | # ### end Alembic commands ### 30 | 31 | 32 | def downgrade(): 33 | # ### commands auto generated by Alembic - please adjust! ### 34 | op.drop_table('example_file') 35 | # ### end Alembic commands ### 36 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/d499042fad3b_add_module_details_column_for_holding_.py: -------------------------------------------------------------------------------- 1 | """Add module details column for holding tfsec output 2 | 3 | Revision ID: d499042fad3b 4 | Revises: 47e45e505e22 5 | Create Date: 2022-07-04 22:14:53.819081 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import mysql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'd499042fad3b' 14 | down_revision = '47e45e505e22' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('module_details', sa.Column('tfsec', sa.LargeBinary(length=16777215).with_variant(mysql.MEDIUMBLOB(), 'mysql'), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | with op.batch_alter_table('module_details') as module_details_op: 28 | module_details_op.drop_column('tfsec') 29 | # ### end Alembic commands ### 30 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/ee82678fcda1_remove_foreign_key_constraint_from_.py: -------------------------------------------------------------------------------- 1 | """Remove foreign key constraint from analytics parent module version ID column 2 | 3 | Revision ID: ee82678fcda1 4 | Revises: eea5e27cac2b 5 | Create Date: 2022-06-25 12:54:28.649027 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'ee82678fcda1' 14 | down_revision = 'eea5e27cac2b' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | """Remove foreign key constraint from analytics parent_module_version column.""" 21 | with op.batch_alter_table('analytics', schema=None) as batch_op: 22 | batch_op.drop_constraint('fk_analytics_parent_module_version_module_version_id', type_='foreignkey') 23 | 24 | 25 | def downgrade(): 26 | """Re-add foreign key constraint from analytics parent_module_version column.""" 27 | with op.batch_alter_table('analytics', schema=None) as batch_op: 28 | batch_op.create_foreign_key('fk_analytics_parent_module_version_module_version_id', 'module_version', ['parent_module_version'], ['id'], onupdate='CASCADE') 29 | 30 | -------------------------------------------------------------------------------- /terrareg/alembic/versions/ff6378a8ab9d_add_module_provider_redirect_table.py: -------------------------------------------------------------------------------- 1 | """Add module provider redirect table 2 | 3 | Revision ID: ff6378a8ab9d 4 | Revises: db7a6e0d0b9d 5 | Create Date: 2023-08-01 06:56:46.223040 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'ff6378a8ab9d' 14 | down_revision = 'db7a6e0d0b9d' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('module_provider_redirect', 22 | sa.Column('id', sa.Integer(), nullable=False, autoincrement=True), 23 | sa.Column('module', sa.String(length=128), nullable=False), 24 | sa.Column('provider', sa.String(length=128), nullable=False), 25 | sa.Column('namespace_id', sa.Integer(), nullable=False), 26 | sa.Column('module_provider_id', sa.Integer(), nullable=False), 27 | sa.ForeignKeyConstraint(['module_provider_id'], ['module_provider.id'], name='fk_module_provider_redirect_module_provider_id', onupdate='CASCADE', ondelete='CASCADE'), 28 | sa.ForeignKeyConstraint(['namespace_id'], ['namespace.id'], name='fk_module_provider_redirect_namespace_id', onupdate='CASCADE', ondelete='CASCADE'), 29 | sa.PrimaryKeyConstraint('id') 30 | ) 31 | # ### end Alembic commands ### 32 | 33 | 34 | def downgrade(): 35 | # ### commands auto generated by Alembic - please adjust! ### 36 | op.drop_table('module_provider_redirect') 37 | # ### end Alembic commands ### 38 | -------------------------------------------------------------------------------- /terrareg/auth/admin_api_key_auth_method.py: -------------------------------------------------------------------------------- 1 | 2 | from .base_admin_auth_method import BaseAdminAuthMethod 3 | from .base_api_key_auth_method import BaseApiKeyAuthMethod 4 | import terrareg.config 5 | 6 | 7 | class AdminApiKeyAuthMethod(BaseAdminAuthMethod, BaseApiKeyAuthMethod): 8 | """Auth method for admin API key""" 9 | 10 | @classmethod 11 | def check_auth_state(cls): 12 | """Check if admin API key is provided""" 13 | return cls._check_api_key([terrareg.config.Config().ADMIN_AUTHENTICATION_TOKEN]) 14 | -------------------------------------------------------------------------------- /terrareg/auth/admin_session_auth_method.py: -------------------------------------------------------------------------------- 1 | 2 | from .base_admin_auth_method import BaseAdminAuthMethod 3 | from .base_session_auth_method import BaseSessionAuthMethod 4 | from .authentication_type import AuthenticationType 5 | 6 | 7 | class AdminSessionAuthMethod(BaseAdminAuthMethod, BaseSessionAuthMethod): 8 | """Auth method for admin session""" 9 | 10 | SESSION_AUTH_TYPE_VALUE = AuthenticationType.SESSION_PASSWORD 11 | 12 | @classmethod 13 | def check_session(cls): 14 | """Check admin session""" 15 | # There are no additional attributes to check 16 | return True 17 | -------------------------------------------------------------------------------- /terrareg/auth/authentication_type.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from enum import Enum 4 | 5 | 6 | class AuthenticationType(Enum): 7 | """Determine the method of authentication.""" 8 | NOT_CHECKED = 0 9 | NOT_AUTHENTICATED = 1 10 | AUTHENTICATION_TOKEN = 2 11 | SESSION_PASSWORD = 3 12 | SESSION_OPENID_CONNECT = 4 13 | SESSION_SAML = 5 14 | SESSION_GITHUB = 6 15 | -------------------------------------------------------------------------------- /terrareg/auth/base_admin_auth_method.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import terrareg.config 4 | from .base_auth_method import BaseAuthMethod 5 | 6 | 7 | class BaseAdminAuthMethod(BaseAuthMethod): 8 | """Base auth method admin authentication""" 9 | 10 | def is_admin(self): 11 | """Return whether user is an admin""" 12 | return True 13 | 14 | def is_built_in_admin(self): 15 | """Whether user is the built-in admin""" 16 | return True 17 | 18 | def can_publish_module_version(self, namespace): 19 | """Whether user can publish module version within a namespace.""" 20 | return True 21 | 22 | def can_upload_module_version(self, namespace): 23 | """Whether user can upload/index module version within a namespace.""" 24 | return True 25 | 26 | def check_namespace_access(self, permission_type, namespace): 27 | """Check access level to a given namespace.""" 28 | # Allow full access to all namespaces 29 | return True 30 | 31 | @classmethod 32 | def is_enabled(cls): 33 | return bool(terrareg.config.Config().ADMIN_AUTHENTICATION_TOKEN) 34 | 35 | def get_username(self): 36 | """Get username of current user""" 37 | return 'Built-in admin' 38 | 39 | def can_access_read_api(self): 40 | """Whether the user can access 'read' APIs""" 41 | return True 42 | -------------------------------------------------------------------------------- /terrareg/auth/base_api_key_auth_method.py: -------------------------------------------------------------------------------- 1 | 2 | from flask import request 3 | 4 | from .base_auth_method import BaseAuthMethod 5 | 6 | 7 | class BaseApiKeyAuthMethod(BaseAuthMethod): 8 | """Base auth method for API key-based authentication""" 9 | 10 | @property 11 | def requires_csrf_tokens(self): 12 | """Whether auth type requires CSRF tokens""" 13 | return False 14 | 15 | @classmethod 16 | def _check_api_key(cls, valid_keys): 17 | """Whether whether API key is valid""" 18 | if not isinstance(valid_keys, list): 19 | return False 20 | 21 | # Obtain API key from request, ensuring that it is 22 | # not empty 23 | actual_key = request.headers.get('X-Terrareg-ApiKey', '') 24 | if not actual_key: 25 | return False 26 | 27 | # Iterate through API keys, ensuring 28 | for valid_key in valid_keys: 29 | # Ensure the valid key is not empty: 30 | if actual_key and actual_key == valid_key: 31 | return True 32 | return False 33 | 34 | def can_access_read_api(self): 35 | """Whether the user can access 'read' APIs""" 36 | # API keys can only access APIs that use different auth check methods 37 | return False 38 | -------------------------------------------------------------------------------- /terrareg/auth/base_session_auth_method.py: -------------------------------------------------------------------------------- 1 | 2 | import flask 3 | 4 | import terrareg.config 5 | import terrareg.models 6 | from .base_auth_method import BaseAuthMethod 7 | import terrareg.auth 8 | 9 | 10 | class BaseSessionAuthMethod(BaseAuthMethod): 11 | """Base auth method for session-based authentication""" 12 | 13 | SESSION_AUTH_TYPE_KEY = 'authentication_type' 14 | SESSION_AUTH_TYPE_VALUE = None 15 | 16 | @property 17 | def requires_csrf_tokens(self): 18 | """Whether auth type requires CSRF tokens""" 19 | return True 20 | 21 | @classmethod 22 | def check_session_auth_type(cls): 23 | """Check if the current type of authenticate is set in session.""" 24 | # Check if auth type value has been overridden 25 | if cls.SESSION_AUTH_TYPE_VALUE is None: 26 | raise NotImplementedError 27 | 28 | return flask.session.get(cls.SESSION_AUTH_TYPE_KEY, None) == cls.SESSION_AUTH_TYPE_VALUE.value 29 | 30 | @classmethod 31 | def check_session(cls): 32 | """Check if auth-specific session is valid.""" 33 | raise NotImplementedError 34 | 35 | @classmethod 36 | def check_auth_state(cls): 37 | """Check if session is valid.""" 38 | # Ensure session secret key is set, 39 | # session ID is present and valid and 40 | # is_admin_authenticated session is set 41 | if (not terrareg.config.Config().SECRET_KEY or 42 | not terrareg.auth.AuthFactory.get_current_session() or 43 | not flask.session.get('is_admin_authenticated', False)): 44 | return False 45 | 46 | # Ensure session type is set to the current session and session is valid 47 | return cls.check_session_auth_type() and cls.check_session() 48 | 49 | def can_access_read_api(self): 50 | """Whether the user can access 'read' APIs""" 51 | # Logged in SSO users can access any 'read' APIs 52 | return True 53 | -------------------------------------------------------------------------------- /terrareg/auth/openid_auth_method.py: -------------------------------------------------------------------------------- 1 | 2 | import datetime 3 | 4 | import flask 5 | 6 | import terrareg.openid_connect 7 | from .base_sso_auth_method import BaseSsoAuthMethod 8 | from .authentication_type import AuthenticationType 9 | 10 | 11 | class OpenidConnectAuthMethod(BaseSsoAuthMethod): 12 | """Auth method for OpenID authentication""" 13 | 14 | SESSION_AUTH_TYPE_VALUE = AuthenticationType.SESSION_OPENID_CONNECT 15 | 16 | @classmethod 17 | def check_session(cls): 18 | """Check OpenID session""" 19 | # Check OpenID connect expiry time 20 | try: 21 | session_timestamp = float(flask.session.get('openid_connect_expires_at', 0)) 22 | except ValueError: 23 | return False 24 | 25 | if datetime.datetime.now() >= datetime.datetime.fromtimestamp(session_timestamp): 26 | return False 27 | 28 | try: 29 | terrareg.openid_connect.OpenidConnect.validate_session_token(flask.session.get('openid_connect_id_token')) 30 | except: 31 | # Catch any exceptions when validating session and return False 32 | return False 33 | # If validate did not raise errors, return True 34 | return True 35 | 36 | def get_group_memberships(self): 37 | """Return list of groups that the user a member of""" 38 | return flask.session.get('openid_groups', []) or [] 39 | 40 | @classmethod 41 | def is_enabled(cls): 42 | return terrareg.openid_connect.OpenidConnect.is_enabled() 43 | 44 | def get_username(self): 45 | """Get username of current user""" 46 | return flask.session.get('openid_username') 47 | -------------------------------------------------------------------------------- /terrareg/auth/publish_api_key_auth_method.py: -------------------------------------------------------------------------------- 1 | 2 | import terrareg.config 3 | from .base_api_key_auth_method import BaseApiKeyAuthMethod 4 | 5 | 6 | class PublishApiKeyAuthMethod(BaseApiKeyAuthMethod): 7 | """Auth method for publish API key""" 8 | 9 | @classmethod 10 | def check_auth_state(cls): 11 | """Check if upload API key is provided""" 12 | return cls._check_api_key(terrareg.config.Config().PUBLISH_API_KEYS) 13 | 14 | @classmethod 15 | def is_enabled(cls): 16 | return bool(terrareg.config.Config().PUBLISH_API_KEYS) 17 | 18 | def can_publish_module_version(self, namespace): 19 | """Whether user can publish module version within a namespace.""" 20 | return True 21 | 22 | def check_namespace_access(self, permission_type, namespace): 23 | """Check access level to a given namespace.""" 24 | return False 25 | 26 | def get_username(self): 27 | """Get username of current user""" 28 | return 'Publish API Key' 29 | -------------------------------------------------------------------------------- /terrareg/auth/saml_auth_method.py: -------------------------------------------------------------------------------- 1 | 2 | import flask 3 | 4 | import terrareg.saml 5 | import terrareg.config 6 | from .base_sso_auth_method import BaseSsoAuthMethod 7 | from .authentication_type import AuthenticationType 8 | 9 | 10 | class SamlAuthMethod(BaseSsoAuthMethod): 11 | """Auth method for SAML authentication""" 12 | 13 | SESSION_AUTH_TYPE_VALUE = AuthenticationType.SESSION_SAML 14 | 15 | @classmethod 16 | def check_session(cls): 17 | """Check SAML session is valid""" 18 | return bool(flask.session.get('samlUserdata')) 19 | 20 | @classmethod 21 | def is_enabled(cls): 22 | return terrareg.saml.Saml2.is_enabled() 23 | 24 | def get_group_memberships(self): 25 | """Return list of groups that the user a member of""" 26 | user_data_groups = flask.session.get('samlUserdata', None) 27 | if user_data_groups and isinstance(user_data_groups, dict): 28 | groups = user_data_groups.get(terrareg.config.Config().SAML2_GROUP_ATTRIBUTE) 29 | if isinstance(groups, list): 30 | return groups 31 | return [] 32 | 33 | def get_username(self): 34 | """Get username of current user""" 35 | return flask.session.get('samlNameId') 36 | -------------------------------------------------------------------------------- /terrareg/auth/terraform_analytics_auth_key_auth_method.py: -------------------------------------------------------------------------------- 1 | 2 | from .base_terraform_static_token import BaseTerraformStaticToken 3 | import terrareg.config 4 | 5 | 6 | class TerraformAnalyticsAuthKeyAuthMethod(BaseTerraformStaticToken): 7 | """Auth method for handling Terraform authentication using an 'analytics auth key' deployment token""" 8 | 9 | @classmethod 10 | def get_valid_terraform_tokens(cls): 11 | """Obtain list of valid tokens""" 12 | # Split each auth key 'xxxxxx:dev', 'yyyyyy:prod' by colon to obtain the auth key 13 | return [ 14 | auth_key.split(':')[0] 15 | for auth_key in terrareg.config.Config().ANALYTICS_AUTH_KEYS 16 | if auth_key.split(':')[0] 17 | ] 18 | 19 | def get_username(self): 20 | """Return username""" 21 | return "Terraform deployment analytics token" 22 | -------------------------------------------------------------------------------- /terrareg/auth/terraform_ignore_analytics_auth_method.py: -------------------------------------------------------------------------------- 1 | 2 | from .base_terraform_static_token import BaseTerraformStaticToken 3 | import terrareg.config 4 | 5 | 6 | class TerraformIgnoreAnalyticsAuthMethod(BaseTerraformStaticToken): 7 | """Auth method for handling Terraform authentication using an 'ignore analytics' token""" 8 | 9 | @classmethod 10 | def get_valid_terraform_tokens(cls): 11 | """Obtain list of valid tokens""" 12 | return [ 13 | token 14 | for token in terrareg.config.Config().IGNORE_ANALYTICS_TOKEN_AUTH_KEYS 15 | if token 16 | ] 17 | 18 | def get_username(self): 19 | """Return username""" 20 | return "Terraform ignore analytics token" 21 | 22 | def should_record_terraform_analytics(self): 23 | """Whether Terraform downloads by the user should be recorded""" 24 | return False 25 | -------------------------------------------------------------------------------- /terrareg/auth/terraform_internal_extraction.py: -------------------------------------------------------------------------------- 1 | 2 | from .base_terraform_static_token import BaseTerraformStaticToken 3 | import terrareg.config 4 | 5 | 6 | class TerraformInternalExtractionAuthMethod(BaseTerraformStaticToken): 7 | """Auth method for handling Terraform authentication for internal extraction""" 8 | 9 | @classmethod 10 | def get_valid_terraform_tokens(cls): 11 | """Obtain list of valid tokens""" 12 | config = terrareg.config.Config() 13 | return [config.INTERNAL_EXTRACTION_ANALYTICS_TOKEN] if config.INTERNAL_EXTRACTION_ANALYTICS_TOKEN else [] 14 | 15 | def get_username(self): 16 | """Return username""" 17 | return "Terraform internal extraction" 18 | 19 | def should_record_terraform_analytics(self): 20 | """Whether Terraform downloads by the user should be recorded""" 21 | return False 22 | -------------------------------------------------------------------------------- /terrareg/auth/upload_api_key_auth_method.py: -------------------------------------------------------------------------------- 1 | 2 | import terrareg.config 3 | from .base_api_key_auth_method import BaseApiKeyAuthMethod 4 | 5 | 6 | class UploadApiKeyAuthMethod(BaseApiKeyAuthMethod): 7 | """Auth method for upload API key""" 8 | 9 | @classmethod 10 | def check_auth_state(cls): 11 | """Check if upload API key is provided""" 12 | return cls._check_api_key(terrareg.config.Config().UPLOAD_API_KEYS) 13 | 14 | @classmethod 15 | def is_enabled(cls): 16 | return bool(terrareg.config.Config().UPLOAD_API_KEYS) 17 | 18 | def can_upload_module_version(self, namespace): 19 | """Whether user can upload/index module version within a namespace.""" 20 | return True 21 | 22 | def check_namespace_access(self, permission_type, namespace): 23 | """Check access level to a given namespace.""" 24 | return False 25 | 26 | def get_username(self): 27 | """Get username of current user""" 28 | return 'Upload API Key' 29 | 30 | -------------------------------------------------------------------------------- /terrareg/constants.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | The latest extraction version to verify whether 4 | module versions have been extracted using the latest methods 5 | """ 6 | EXTRACTION_VERSION = 2 7 | 8 | """ 9 | The latest extraction version to verify whether 10 | provider versions have been extracted using the latest methods 11 | """ 12 | PROVIDER_EXTRACTION_VERSION = 1 13 | 14 | """ 15 | Valid ports for Terraform redirect URI 16 | """ 17 | TERRAFORM_REDIRECT_URI_PORT_RANGE = [10000, 10010] 18 | -------------------------------------------------------------------------------- /terrareg/csrf.py: -------------------------------------------------------------------------------- 1 | 2 | from flask import session 3 | 4 | import terrareg.auth 5 | from terrareg.errors import NoSessionSetError, IncorrectCSRFTokenError 6 | 7 | 8 | def get_csrf_token(): 9 | """Return current session CSRF token.""" 10 | return session.get('csrf_token', '') 11 | 12 | 13 | def check_csrf_token(csrf_token): 14 | """Check CSRF token.""" 15 | # If user is authenticated using authentication token, 16 | # do not required CSRF token 17 | if not terrareg.auth.AuthFactory().get_current_auth_method().requires_csrf_tokens: 18 | return False 19 | 20 | session_token = get_csrf_token() 21 | if not session_token: 22 | raise NoSessionSetError('No session is presesnt to check CSRF token') 23 | elif session_token != csrf_token: 24 | raise IncorrectCSRFTokenError('CSRF token is incorrect') 25 | else: 26 | return True 27 | -------------------------------------------------------------------------------- /terrareg/filters.py: -------------------------------------------------------------------------------- 1 | 2 | from enum import Enum 3 | 4 | class NamespaceTrustFilter(Enum): 5 | """Enum to be information about trusted namespaces.""" 6 | 7 | UNSPECIFIED = 0 8 | TRUSTED_NAMESPACES = 1 9 | CONTRIBUTED = 2 10 | ALL = 3 11 | 12 | -------------------------------------------------------------------------------- /terrareg/loose_version.py: -------------------------------------------------------------------------------- 1 | import re 2 | from packaging.version import Version 3 | 4 | 5 | class LooseVersion(Version): 6 | 7 | VERSION_PATTERN = r""" 8 | v? 9 | (?: 10 | (?:(?P[0-9]+)!)? # epoch 11 | (?P[0-9]+(?:\.[0-9]+)*) # release segment 12 | (?P
                                          # pre-release
13 |             [-_\.]?
14 |             (?Palpha|a|beta|b|preview|pre|c|rc|.*?)
15 |             [-_\.]?
16 |             (?P[0-9]+)?
17 |         )?
18 |         (?P                                         # post release
19 |             (?:-(?P[0-9]+))
20 |             |
21 |             (?:
22 |                 [-_\.]?
23 |                 (?Ppost|rev|r)
24 |                 [-_\.]?
25 |                 (?P[0-9]+)?
26 |             )
27 |         )?
28 |         (?P                                          # dev release
29 |             [-_\.]?
30 |             (?Pdev)
31 |             [-_\.]?
32 |             (?P[0-9]+)?
33 |         )?
34 |     )
35 |     (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
36 |     """
37 | 
38 |     _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
39 | 


--------------------------------------------------------------------------------
/terrareg/namespace_type.py:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | from enum import Enum
 5 | 
 6 | 
 7 | class NamespaceType(Enum):
 8 |     """Namespace type"""
 9 | 
10 |     NONE = "None"
11 |     GITHUB_USER = "GithubUser"
12 |     GITHUB_ORGANISATION = "GithubOrganisation"
13 | 


--------------------------------------------------------------------------------
/terrareg/provider_binary_types.py:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | from enum import Enum
 4 | 
 5 | 
 6 | class ProviderBinaryOperatingSystemType(Enum):
 7 |     """Provider binary operating systems"""
 8 | 
 9 |     FREEBSD = "freebsd"
10 |     DARWIN = "darwin"
11 |     WINDOWS = "windows"
12 |     LINUX = "linux"
13 | 
14 | 
15 | class ProviderBinaryArchitectureType(Enum):
16 |     """Provider binary architectures"""
17 | 
18 |     AMD64 = "amd64"
19 |     ARM = "arm"
20 |     ARM64 = "arm64"
21 |     I386 = "386"
22 | 


--------------------------------------------------------------------------------
/terrareg/provider_documentation_type.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from enum import Enum
 3 | 
 4 | 
 5 | class ProviderDocumentationType(Enum):
 6 | 
 7 |     OVERVIEW = "overview"
 8 |     PROVIDER = "provider"
 9 |     RESOURCE = "resources"
10 |     DATA_SOURCE = "data-sources"
11 |     GUIDE = "guides"
12 | 


--------------------------------------------------------------------------------
/terrareg/provider_source/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import BaseProviderSource
2 | from .github import GithubProviderSource


--------------------------------------------------------------------------------
/terrareg/provider_source_type.py:
--------------------------------------------------------------------------------
1 | 
2 | from enum import Enum
3 | 
4 | 
5 | class ProviderSourceType(Enum):
6 | 
7 |     GITHUB = "github"
8 | 


--------------------------------------------------------------------------------
/terrareg/provider_tier.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from enum import Enum
 3 | 
 4 | 
 5 | class ProviderTier(Enum):
 6 | 
 7 |     OFFICIAL = "official"
 8 |     COMMUNITY = "community"
 9 | 
10 | 


--------------------------------------------------------------------------------
/terrareg/registry_resource_type.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | 
3 | 
4 | class RegistryResourceType(Enum):
5 |     """Provide enum for defining resource types"""
6 |     MODULE = "module"
7 |     PROVIDER = "provider"
8 | 


--------------------------------------------------------------------------------
/terrareg/repository_kind.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from enum import Enum
 3 | 
 4 | 
 5 | class RepositoryKind(Enum):
 6 |     """Kind of repository"""
 7 | 
 8 |     PROVIDER = "provider"
 9 |     MODULE = "module"
10 | 


--------------------------------------------------------------------------------
/terrareg/result_data.py:
--------------------------------------------------------------------------------
 1 | 
 2 | class ResultData:
 3 |     """Object containing search results."""
 4 | 
 5 |     @property
 6 |     def rows(self):
 7 |         """Return data rows."""
 8 |         return self._rows
 9 | 
10 |     @property
11 |     def count(self):
12 |         """Return count."""
13 |         return self._count
14 | 
15 |     @property
16 |     def meta(self):
17 |         """Return API meta for limit/offsets."""
18 |         # Setup base metadata with current offset and limit
19 |         meta_data = {
20 |             "limit": self._limit,
21 |             "current_offset": self._offset,
22 |         }
23 | 
24 |         # If current offset is not 0,
25 |         # Set previous offset as current offset minus the current limit,
26 |         # or 0, depending on whichever is higher.
27 |         if self._offset > 0:
28 |             meta_data['prev_offset'] = (self._offset - self._limit) if (self._offset >= self._limit) else 0
29 | 
30 |         # If the current count of results is greater than the next offset,
31 |         # provide the next offset in the metadata
32 |         next_offset = (self._offset + self._limit)
33 |         if self.count > next_offset:
34 |             meta_data['next_offset'] = next_offset
35 | 
36 |         return meta_data
37 | 
38 |     def __init__(self, offset: int, limit: int, rows: list, count: str):
39 |         """Store member variables"""
40 |         self._offset = offset
41 |         self._limit = limit
42 |         self._rows = rows
43 |         self._count = count
44 | 


--------------------------------------------------------------------------------
/terrareg/server/api/github/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/server/api/github/__init__.py


--------------------------------------------------------------------------------
/terrareg/server/api/github/github_auth_status.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.auth
 4 | import terrareg.provider_source.factory
 5 | 
 6 | 
 7 | class GithubAuthStatus(ErrorCatchingResource):
 8 |     """Interface to provide details about current authentication status with Github"""
 9 | 
10 |     def _get(self, provider_source: str):
11 |         """Provide authentication status."""
12 | 
13 |         # Obtain provider source
14 |         provider_source_factory = terrareg.provider_source.factory.ProviderSourceFactory.get()
15 |         provider_source_obj = provider_source_factory.get_provider_source_by_api_name(provider_source)
16 |         if not provider_source_obj:
17 |             return self._get_404_response()
18 | 
19 |         github_authenticated = False
20 |         username = None
21 |         if auth_method := terrareg.auth.GithubAuthMethod.get_current_instance():
22 |             github_authenticated = True
23 |             username = auth_method.get_username()
24 | 
25 |         return {
26 |             "auth": github_authenticated,
27 |             "username": username
28 |         }
29 | 


--------------------------------------------------------------------------------
/terrareg/server/api/github/github_login_initiate.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask import session, make_response, render_template, redirect
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.provider_source.factory
 6 | 
 7 | 
 8 | class GithubLoginInitiate(ErrorCatchingResource):
 9 |     """Interface to initiate authentication via Github"""
10 | 
11 |     def _get(self, provider_source):
12 |         """Redirect to github login."""
13 |         # Obtain provider source
14 |         provider_source_factory = terrareg.provider_source.factory.ProviderSourceFactory.get()
15 |         provider_source_obj = provider_source_factory.get_provider_source_by_api_name(provider_source)
16 |         if not provider_source_obj:
17 |             return make_response(self._render_template(
18 |                 'error.html',
19 |                 error_title='Login error',
20 |                 error_description=f'{provider_source} authentication is not enabled',
21 |                 root_bread_brumb='Login'
22 |             ))
23 | 
24 | 
25 |         if self.create_session() is None:
26 |             return make_response(render_template(
27 |                 'error.html',
28 |                 error_title='Login error',
29 |                 error_description='Sessions are not available'
30 |             ))
31 | 
32 |         redirect_url = provider_source_obj.get_login_redirect_url()
33 | 
34 |         # Set session for provider source
35 |         session['provider_source'] = provider_source_obj.name
36 | 
37 |         return redirect(redirect_url, code=302)
38 | 


--------------------------------------------------------------------------------
/terrareg/server/api/github/github_organisations.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.auth
 4 | import terrareg.models
 5 | import terrareg.namespace_type
 6 | import terrareg.provider_source.factory
 7 | 
 8 | 
 9 | class GithubOrganisations(ErrorCatchingResource):
10 |     """Interface to provide details about current Github organisations for the logged in user"""
11 | 
12 |     def _get(self, provider_source):
13 |         """Provide organisation details."""
14 |         # Obtain provider source
15 |         provider_source_factory = terrareg.provider_source.factory.ProviderSourceFactory.get()
16 |         provider_source_obj = provider_source_factory.get_provider_source_by_api_name(provider_source)
17 |         if not provider_source_obj:
18 |             return self._get_404_response()
19 | 
20 |         organisations = []
21 |         if auth_method := terrareg.auth.GithubAuthMethod.get_current_instance():
22 | 
23 |             for namespace_name in auth_method.get_github_organisations():
24 |                 if (namespace := terrareg.models.Namespace.get(name=namespace_name, include_redirect=False)):
25 |                     organisations.append({
26 |                         "name": namespace.name,
27 |                         "type": "user" if namespace.namespace_type is terrareg.namespace_type.NamespaceType.GITHUB_USER else "organization",
28 |                         "admin": True,
29 |                         "can_publish_providers": namespace.can_publish_providers
30 |                     })
31 | 
32 |         return organisations
33 | 


--------------------------------------------------------------------------------
/terrareg/server/api/github/github_repositories.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.auth
 4 | import terrareg.models
 5 | import terrareg.namespace_type
 6 | import terrareg.provider_source.factory
 7 | import terrareg.repository_model
 8 | 
 9 | 
10 | class GithubRepositories(ErrorCatchingResource):
11 |     """Interface to provide details about current Github repositories for the logged in user"""
12 | 
13 |     # @TODO Add permission checking for read API
14 | 
15 |     def _get(self, provider_source):
16 |         """Provide organisation details."""
17 |         # Obtain provider source
18 |         provider_source_factory = terrareg.provider_source.factory.ProviderSourceFactory.get()
19 |         provider_source_obj = provider_source_factory.get_provider_source_by_api_name(provider_source)
20 |         if not provider_source_obj:
21 |             return self._get_404_response()
22 | 
23 |         owners = []
24 |         if (github_auth_method := terrareg.auth.GithubAuthMethod.get_current_instance()):
25 |             owners = github_auth_method.get_github_organisations()
26 |         elif terrareg.auth.AuthFactory().get_current_auth_method().is_admin():
27 |             owners = [n.name for n in terrareg.models.Namespace.get_all().rows]
28 |         
29 |         if not owners:
30 |             return []
31 | 
32 |         # @TODO Add organisation/namespace argument and filter by this
33 |         return [
34 |             {
35 |                 "kind": repository.kind.value,
36 |                 "id": repository.provider_id,
37 |                 "full_name": repository.id,
38 |                 "owner_login": repository.owner,
39 |                 "owner_type": "owner",
40 |                 "published_id": None
41 |             }
42 |             # @TODO filter repos by provider source
43 |             for repository in terrareg.repository_model.Repository.get_repositories_by_owner_list(owners=owners)
44 |             if repository.kind is not None
45 |         ]
46 | 


--------------------------------------------------------------------------------
/terrareg/server/api/module_details.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | import terrareg.analytics
 5 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 6 | import terrareg.models
 7 | import terrareg.module_search
 8 | import terrareg.auth_wrapper
 9 | 
10 | 
11 | class ApiModuleDetails(ErrorCatchingResource):
12 | 
13 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
14 | 
15 |     def _get(self, namespace, name):
16 |         """Return latest version for each module provider."""
17 | 
18 |         parser = reqparse.RequestParser()
19 |         parser.add_argument(
20 |             'offset', type=int, location='args',
21 |             default=0, help='Pagination offset')
22 |         parser.add_argument(
23 |             'limit', type=int, location='args',
24 |             default=10, help='Pagination limit'
25 |         )
26 |         args = parser.parse_args()
27 | 
28 |         namespace, _ = terrareg.analytics.AnalyticsEngine.extract_analytics_token(namespace)
29 | 
30 |         search_results = terrareg.module_search.ModuleSearch.search_module_providers(
31 |             offset=args.offset,
32 |             limit=args.limit,
33 |             namespaces=[namespace],
34 |             modules=[name]
35 |         )
36 | 
37 |         if not search_results.rows:
38 |             return self._get_404_response()
39 | 
40 |         return {
41 |             "meta": search_results.meta,
42 |             "modules": [
43 |                 module_provider.get_latest_version().get_api_outline()
44 |                 for module_provider in search_results.rows
45 |             ]
46 |         }
47 | 
48 | 


--------------------------------------------------------------------------------
/terrareg/server/api/module_list.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse, inputs
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.module_search
 6 | import terrareg.auth_wrapper
 7 | 
 8 | 
 9 | class ApiModuleList(ErrorCatchingResource):
10 | 
11 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
12 | 
13 |     def _get(self):
14 |         """Return list of modules."""
15 |         parser = reqparse.RequestParser()
16 |         parser.add_argument(
17 |             'offset', type=int, location='args',
18 |             default=0, help='Pagination offset')
19 |         parser.add_argument(
20 |             'limit', type=int, location='args',
21 |             default=10, help='Pagination limit'
22 |         )
23 |         parser.add_argument(
24 |             'provider', type=str, location='args',
25 |             default=None, help='Limits modules to a specific provider.',
26 |             action='append', dest='providers'
27 |         )
28 |         parser.add_argument(
29 |             'verified', type=inputs.boolean, location='args',
30 |             default=False, help='Limits modules to only verified modules.'
31 |         )
32 | 
33 |         args = parser.parse_args()
34 | 
35 |         search_results = terrareg.module_search.ModuleSearch.search_module_providers(
36 |             providers=args.providers,
37 |             verified=args.verified,
38 |             offset=args.offset,
39 |             limit=args.limit
40 |         )
41 | 
42 |         return {
43 |             "meta": search_results.meta,
44 |             "modules": [
45 |                 module_provider.get_latest_version().get_api_outline()
46 |                 for module_provider in search_results.rows
47 |             ]
48 |         }
49 | 


--------------------------------------------------------------------------------
/terrareg/server/api/module_provider_details.py:
--------------------------------------------------------------------------------
 1 | 
 2 | import terrareg.analytics
 3 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 4 | import terrareg.models
 5 | import terrareg.auth_wrapper
 6 | 
 7 | 
 8 | class ApiModuleProviderDetails(ErrorCatchingResource):
 9 | 
10 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
11 | 
12 |     def _get(self, namespace, name, provider):
13 |         """Return list of version."""
14 | 
15 |         namespace, _ = terrareg.analytics.AnalyticsEngine.extract_analytics_token(namespace)
16 |         _, _, module_provider, error = self.get_module_provider_by_names(namespace, name, provider)
17 |         if error:
18 |             return self._get_404_response()
19 |         module_version = module_provider.get_latest_version()
20 | 
21 |         if not module_version:
22 |             return self._get_404_response()
23 | 
24 |         return module_version.get_api_details()
25 | 
26 | 


--------------------------------------------------------------------------------
/terrareg/server/api/module_provider_downloads_summary.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.analytics
 4 | import terrareg.auth_wrapper
 5 | 
 6 | 
 7 | class ApiModuleProviderDownloadsSummary(ErrorCatchingResource):
 8 |     """Provide download summary for module provider."""
 9 | 
10 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
11 | 
12 |     def _get(self, namespace, name, provider):
13 |         """Return list of download counts for module provider."""
14 |         _, _, module_provider, error = self.get_module_provider_by_names(namespace, name, provider)
15 |         if error:
16 |             return error
17 | 
18 |         return {
19 |             "data": {
20 |                 "type": "module-downloads-summary",
21 |                 "id": module_provider.id,
22 |                 "attributes": terrareg.analytics.AnalyticsEngine.get_module_provider_download_stats(module_provider)
23 |             }
24 |         }
25 | 


--------------------------------------------------------------------------------
/terrareg/server/api/module_version_details.py:
--------------------------------------------------------------------------------
 1 | 
 2 | import terrareg.analytics
 3 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 4 | import terrareg.models
 5 | import terrareg.auth_wrapper
 6 | 
 7 | 
 8 | class ApiModuleVersionDetails(ErrorCatchingResource):
 9 | 
10 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
11 | 
12 |     def _get(self, namespace, name, provider, version):
13 |         """Return list of version."""
14 | 
15 |         namespace, _ = terrareg.analytics.AnalyticsEngine.extract_analytics_token(namespace)
16 |         _, _, _, module_version, error = self.get_module_version_by_name(namespace, name, provider, version)
17 |         if error:
18 |             return self._get_404_response()
19 | 
20 |         return module_version.get_api_details()
21 | 
22 | 


--------------------------------------------------------------------------------
/terrareg/server/api/module_versions.py:
--------------------------------------------------------------------------------
 1 | 
 2 | import terrareg.analytics
 3 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 4 | import terrareg.models
 5 | import terrareg.auth_wrapper
 6 | 
 7 | 
 8 | class ApiModuleVersions(ErrorCatchingResource):
 9 | 
10 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_terraform_api')]
11 | 
12 |     def _get(self, namespace, name, provider):
13 |         """Return list of version."""
14 | 
15 |         namespace, _ = terrareg.analytics.AnalyticsEngine.extract_analytics_token(namespace)
16 |         namespace, module, module_provider, error = self.get_module_provider_by_names(namespace, name, provider)
17 |         if error:
18 |             return self._get_404_response()
19 | 
20 |         return {
21 |             "modules": [
22 |                 {
23 |                     "source": "{namespace}/{module}/{provider}".format(
24 |                         namespace=namespace.name,
25 |                         module=module.name,
26 |                         provider=module_provider.name
27 |                     ),
28 |                     "versions": [
29 |                         {
30 |                             "version": v.version,
31 |                             "root": {
32 |                                 # @TODO: Add providers/dependencies
33 |                                 "providers": [],
34 |                                 "dependencies": []
35 |                             },
36 |                             # @TODO: Add submodule information
37 |                             "submodules": []
38 |                         }
39 |                         for v in module_provider.get_versions()
40 |                     ]
41 |                 }
42 |             ]
43 |         }
44 | 


--------------------------------------------------------------------------------
/terrareg/server/api/namespace_modules.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.module_search
 6 | import terrareg.auth_wrapper
 7 | 
 8 | 
 9 | class ApiNamespaceModules(ErrorCatchingResource):
10 |     """Interface to obtain list of modules in namespace."""
11 | 
12 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
13 | 
14 |     def _get(self, namespace):
15 |         """Return list of modules in namespace"""
16 | 
17 |         parser = reqparse.RequestParser()
18 |         parser.add_argument(
19 |             'offset', type=int, location='args',
20 |             default=0, help='Pagination offset')
21 |         parser.add_argument(
22 |             'limit', type=int, location='args',
23 |             default=10, help='Pagination limit'
24 |         )
25 |         args = parser.parse_args()
26 | 
27 |         search_results = terrareg.module_search.ModuleSearch.search_module_providers(
28 |             offset=args.offset,
29 |             limit=args.limit,
30 |             namespaces=[namespace],
31 |             include_internal=True
32 |         )
33 | 
34 |         if not search_results.rows:
35 |             return self._get_404_response()
36 | 
37 |         return {
38 |             "meta": search_results.meta,
39 |             "modules": [
40 |                 module_provider.get_latest_version().get_api_outline()
41 |                 for module_provider in search_results.rows
42 |             ]
43 |         }
44 | 


--------------------------------------------------------------------------------
/terrareg/server/api/namespace_providers.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.provider_search
 6 | import terrareg.auth_wrapper
 7 | import terrareg.models
 8 | 
 9 | 
10 | class ApiNamespaceProviders(ErrorCatchingResource):
11 |     """Interface to obtain list of providers in namespace."""
12 | 
13 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
14 | 
15 |     def _get(self, namespace):
16 |         """Return list of providers in namespace"""
17 | 
18 |         parser = reqparse.RequestParser()
19 |         parser.add_argument(
20 |             'offset', type=int, location='args',
21 |             default=0, help='Pagination offset')
22 |         parser.add_argument(
23 |             'limit', type=int, location='args',
24 |             default=10, help='Pagination limit'
25 |         )
26 |         args = parser.parse_args()
27 | 
28 |         # Check if namespace exists
29 |         if not terrareg.models.Namespace.get(name=namespace):
30 |             return self._get_404_response()
31 | 
32 |         search_results = terrareg.provider_search.ProviderSearch.search_providers(
33 |             offset=args.offset,
34 |             limit=args.limit,
35 |             namespaces=[namespace]
36 |         )
37 | 
38 |         return {
39 |             "meta": search_results.meta,
40 |             "providers": [
41 |                 provider.get_latest_version().get_api_outline()
42 |                 for provider in search_results.rows
43 |             ]
44 |         }
45 | 


--------------------------------------------------------------------------------
/terrareg/server/api/open_id_initiate.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask import session, make_response, render_template, redirect
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.openid_connect
 6 | 
 7 | 
 8 | class ApiOpenIdInitiate(ErrorCatchingResource):
 9 |     """Interface to initiate authentication via OpenID connect"""
10 | 
11 |     def _get(self):
12 |         """Generate session for storing OpenID state token and redirect to OpenID login provider."""
13 |         redirect_url, state = terrareg.openid_connect.OpenidConnect.get_authorize_redirect_url()
14 | 
15 |         if redirect_url is None:
16 |             res = make_response(render_template(
17 |                 'error.html',
18 |                 error_title='Login error',
19 |                 error_description='SSO is incorrectly configured'
20 |             ))
21 |             res.headers['Content-Type'] = 'text/html'
22 |             return res
23 | 
24 |         session['openid_connect_state'] = state
25 |         session.modified = True
26 | 
27 |         return redirect(redirect_url, code=302)


--------------------------------------------------------------------------------
/terrareg/server/api/prometheus_metrics.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask import make_response
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.analytics
 6 | 
 7 | 
 8 | class PrometheusMetrics(ErrorCatchingResource):
 9 |     """Provide usage analytics for Prometheus scraper"""
10 | 
11 |     def _get(self):
12 |         """
13 |         Return Prometheus metrics for global statistics and module provider statistics
14 |         """
15 |         response = make_response(terrareg.analytics.AnalyticsEngine.get_prometheus_metrics())
16 |         response.headers['content-type'] = 'text/plain; version=0.0.4'
17 | 
18 |         return response
19 | 


--------------------------------------------------------------------------------
/terrareg/server/api/provider.py:
--------------------------------------------------------------------------------
 1 | 
 2 | import terrareg.analytics
 3 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 4 | import terrareg.models
 5 | import terrareg.auth_wrapper
 6 | import terrareg.provider_model
 7 | import terrareg.provider_version_model
 8 | 
 9 | 
10 | class ApiProvider(ErrorCatchingResource):
11 | 
12 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_terraform_api')]
13 | 
14 |     def _get(self, namespace, provider, version=None):
15 |         """Return provider details."""
16 | 
17 |         namespace, _ = terrareg.analytics.AnalyticsEngine.extract_analytics_token(namespace)
18 | 
19 |         namespace_obj = terrareg.models.Namespace.get(name=namespace)
20 |         if namespace_obj is None:
21 |             return self._get_404_response()
22 | 
23 |         provider_obj = terrareg.provider_model.Provider.get(namespace=namespace_obj, name=provider)
24 |         if provider_obj is None:
25 |             return self._get_404_response()
26 | 
27 |         provider_version = None
28 |         if version is not None:
29 |             provider_version = terrareg.provider_version_model.ProviderVersion.get(
30 |                 provider=provider_obj,
31 |                 version=version
32 |             )
33 |         else:
34 |             provider_version = provider_obj.get_latest_version()
35 | 
36 |         if provider_version is None:
37 |             return self._get_404_response()
38 | 
39 |         return provider_version.get_api_details()
40 | 
41 | 


--------------------------------------------------------------------------------
/terrareg/server/api/provider_list.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse, inputs
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.provider_search
 6 | import terrareg.auth_wrapper
 7 | 
 8 | 
 9 | class ApiProviderList(ErrorCatchingResource):
10 |     """Interface to list all providers"""
11 | 
12 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
13 | 
14 |     def _get(self):
15 |         """Return list of modules."""
16 |         parser = reqparse.RequestParser()
17 |         parser.add_argument(
18 |             'offset', type=int, location='args',
19 |             default=0, help='Pagination offset')
20 |         parser.add_argument(
21 |             'limit', type=int, location='args',
22 |             default=10, help='Pagination limit'
23 |         )
24 |         parser.add_argument(
25 |             'provider', type=str, location='args',
26 |             default=None, help='Limits providers by specific providers.',
27 |             action='append', dest='providers'
28 |         )
29 | 
30 |         args = parser.parse_args()
31 | 
32 |         search_results = terrareg.provider_search.ProviderSearch.search_providers(
33 |             providers=args.providers,
34 |             offset=args.offset,
35 |             limit=args.limit
36 |         )
37 | 
38 |         return {
39 |             "meta": search_results.meta,
40 |             "providers": [
41 |                 provider.get_latest_version().get_api_outline()
42 |                 for provider in search_results.rows
43 |             ]
44 |         }
45 | 


--------------------------------------------------------------------------------
/terrareg/server/api/saml_metadata.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask import request, make_response
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.saml
 6 | 
 7 | 
 8 | class ApiSamlMetadata(ErrorCatchingResource):
 9 |     """Meta-data endpoint for SAML"""
10 | 
11 |     def _get(self):
12 |         """Return SAML SP metadata."""
13 |         auth = terrareg.saml.Saml2.initialise_request_auth_object(request)
14 |         settings = auth.get_settings()
15 |         metadata = settings.get_sp_metadata()
16 |         errors = settings.validate_metadata(metadata)
17 | 
18 |         if len(errors) == 0:
19 |             resp = make_response(metadata, 200)
20 |             resp.headers['Content-Type'] = 'text/xml'
21 |         else:
22 |             resp = make_response(', '.join(errors), 500)
23 |         return resp
24 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terraform/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/server/api/terraform/__init__.py


--------------------------------------------------------------------------------
/terrareg/server/api/terraform/v2/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/server/api/terraform/v2/__init__.py


--------------------------------------------------------------------------------
/terrareg/server/api/terraform/v2/provider_categories.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.provider_category_model
 6 | import terrareg.auth_wrapper
 7 | 
 8 | 
 9 | class ApiProviderCategories(ErrorCatchingResource):
10 |     """Interface to obtain list of provider categories."""
11 | 
12 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
13 | 
14 |     def _get(self):
15 |         """Return list of all provider categories"""
16 |         return {
17 |             "data": [
18 |                 {
19 |                     "type": "categories",
20 |                     "id": str(provider_category.pk),
21 |                     "attributes": {
22 |                         "name": provider_category.name,
23 |                         "slug": provider_category.slug,
24 |                         "user-selectable": provider_category.user_selectable
25 |                     },
26 |                     "links": {
27 |                         "self": f"/v2/categories/{provider_category.pk}"
28 |                     }
29 |                 }
30 |                 for provider_category in terrareg.provider_category_model.ProviderCategoryFactory.get().get_all_provider_categories()
31 |             ]
32 |         }
33 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terraform/v2/provider_doc.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.auth_wrapper
 6 | import terrareg.provider_version_model
 7 | import terrareg.provider_version_documentation_model
 8 | import terrareg.provider_documentation_type
 9 | 
10 | 
11 | class ApiV2ProviderDoc(ErrorCatchingResource):
12 |     """Interface for obtain provider doc details"""
13 | 
14 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
15 | 
16 |     def _get_arg_parser(self):
17 |         """Return argument parser for endpoint"""
18 |         arg_parser = reqparse.RequestParser()
19 |         arg_parser.add_argument(
20 |             'output',
21 |             type=str,
22 |             location='args',
23 |             default='md',
24 |             dest='output',
25 |             help='Content output type, either "html" or "md"'
26 |         )
27 |         return arg_parser
28 | 
29 |     def _get(self, doc_id):
30 |         """
31 |         Obtain details about provider document
32 |         """
33 |         args = self._get_arg_parser().parse_args()
34 | 
35 |         document = terrareg.provider_version_documentation_model.ProviderVersionDocumentation.get_by_pk(
36 |             pk=doc_id
37 |         )
38 | 
39 |         if not document:
40 |             return self._get_404_response()
41 | 
42 |         return {
43 |             "data": document.get_v2_api_details(html=(args.output == "html"))
44 |         }
45 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terraform/v2/provider_download_summary.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.models
 4 | import terrareg.auth_wrapper
 5 | import terrareg.provider_model
 6 | import terrareg.provider_version_model
 7 | import terrareg.analytics
 8 | 
 9 | 
10 | class ApiProviderProviderDownloadSummary(ErrorCatchingResource):
11 |     """Interface for providing download summary for providers"""
12 | 
13 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
14 | 
15 |     def _get(self, provider_id: int):
16 |         """Return download summary."""
17 | 
18 |         provider_obj = terrareg.provider_model.Provider.get_by_pk(provider_id)
19 |         if provider_obj is None:
20 |             return self._get_404_response()
21 | 
22 |         download_stats = terrareg.analytics.ProviderAnalytics.get_provider_download_stats(provider_obj)
23 | 
24 |         return {
25 |             "data": {
26 |                 "type":"provider-downloads-summary",
27 |                 "id": str(provider_obj.pk),
28 |                 "attributes": download_stats
29 |             }
30 |         }
31 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terraform_well_known.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import Resource
 3 | 
 4 | from terrareg.constants import TERRAFORM_REDIRECT_URI_PORT_RANGE
 5 | import terrareg.terraform_idp
 6 | 
 7 | 
 8 | class ApiTerraformWellKnown(Resource):
 9 |     """Terraform .well-known discovery"""
10 | 
11 |     def get(self):
12 |         """Return well-known JSON"""
13 |         data = {
14 |             "modules.v1": "/v1/modules/",
15 |             "providers.v1": "/v1/providers/"
16 |         }
17 |         if terrareg.terraform_idp.TerraformIdp.get().is_enabled:
18 |             data["login.v1"] = {
19 |                 "client": "terraform-cli",
20 |                 "grant_types": ["authz_code", "token"],
21 |                 "authz": "/terraform/oauth/authorization",
22 |                 "token": "/terraform/oauth/token",
23 |                 "ports": TERRAFORM_REDIRECT_URI_PORT_RANGE,
24 |             }
25 |         return data
26 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_admin_authenticate.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask import session
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.auth_wrapper
 6 | import terrareg.audit
 7 | import terrareg.audit_action
 8 | import terrareg.auth
 9 | import terrareg.models
10 | 
11 | 
12 | class ApiTerraregAdminAuthenticate(ErrorCatchingResource):
13 |     """Interface to perform authentication as an admin and set appropriate cookie."""
14 | 
15 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('is_built_in_admin')]
16 | 
17 |     def _post(self):
18 |         """Handle POST requests to the authentication endpoint."""
19 | 
20 |         session_obj = self.create_session()
21 |         if not isinstance(session_obj, terrareg.models.Session):
22 |             return {'message': 'Sessions not enabled in configuration'}, 403
23 | 
24 |         session['is_admin_authenticated'] = True
25 |         session['authentication_type'] = terrareg.auth.AuthenticationType.SESSION_PASSWORD.value
26 |         session.modified = True
27 | 
28 |         # Create audit event
29 |         terrareg.audit.AuditEvent.create_audit_event(
30 |             action=terrareg.audit_action.AuditAction.USER_LOGIN,
31 |             object_type=None, object_id=None,
32 |             old_value=None, new_value=None
33 |         )
34 | 
35 |         return {'authenticated': True}
36 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_auth_user_groups.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask import request
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.auth_wrapper
 6 | import terrareg.models
 7 | 
 8 | 
 9 | class ApiTerraregAuthUserGroups(ErrorCatchingResource):
10 |     """Interface to list and create user groups."""
11 | 
12 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('is_admin')]
13 | 
14 |     def _get(self):
15 |         """Obtain list of user groups."""
16 |         return [
17 |             {
18 |                 'name': user_group.name,
19 |                 'site_admin': user_group.site_admin,
20 |                 'namespace_permissions': [
21 |                     {
22 |                         'namespace': permission.namespace.name,
23 |                         'permission_type': permission.permission_type.value
24 |                     }
25 |                     for permission in terrareg.models.UserGroupNamespacePermission.get_permissions_by_user_group(user_group=user_group)
26 |                 ]
27 |             }
28 |             for user_group in terrareg.models.UserGroup.get_all_user_groups()
29 |         ]
30 | 
31 |     def _post(self):
32 |         """Create user group"""
33 |         attributes = request.json
34 |         name = attributes.get('name')
35 |         site_admin = attributes.get('site_admin')
36 | 
37 |         if site_admin is not True and site_admin is not False:
38 |             return {}, 400
39 | 
40 |         user_group = terrareg.models.UserGroup.create(name=name, site_admin=site_admin)
41 |         if user_group:
42 |             return {
43 |                 'name': user_group.name,
44 |                 'site_admin': user_group.site_admin
45 |             }, 201
46 |         else:
47 |             return {}, 400


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_example_details.py:
--------------------------------------------------------------------------------
 1 | 
 2 | import urllib.parse
 3 | 
 4 | from flask import request
 5 | 
 6 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 7 | import terrareg.models
 8 | import terrareg.auth_wrapper
 9 | 
10 | 
11 | class ApiTerraregExampleDetails(ErrorCatchingResource):
12 |     """Interface to obtain example details."""
13 | 
14 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
15 | 
16 |     def _get(self, namespace, name, provider, version, example):
17 |         """Return details of example."""
18 |         _, _, _, module_version, error = self.get_module_version_by_name(namespace, name, provider, version)
19 |         if error:
20 |             return error
21 | 
22 |         example_obj = terrareg.models.Example.get(module_version=module_version, module_path=example)
23 |         if example_obj is None:
24 |             return self._get_404_response()
25 | 
26 |         return example_obj.get_terrareg_api_details(
27 |             request_domain=urllib.parse.urlparse(request.base_url).hostname)
28 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_example_file.py:
--------------------------------------------------------------------------------
 1 | 
 2 | import urllib.parse
 3 | 
 4 | from flask import request
 5 | 
 6 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 7 | import terrareg.models
 8 | import terrareg.auth_wrapper
 9 | 
10 | 
11 | class ApiTerraregExampleFile(ErrorCatchingResource):
12 |     """Interface to obtain content of example file."""
13 | 
14 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
15 | 
16 |     def _get(self, namespace, name, provider, version, example_file):
17 |         """Return content of example file in example module."""
18 |         _, _, _, module_version, error = self.get_module_version_by_name(namespace, name, provider, version)
19 |         if error:
20 |             return error
21 | 
22 |         example_file_obj = terrareg.models.ExampleFile.get_by_path(module_version=module_version, file_path=example_file)
23 | 
24 |         if example_file_obj is None:
25 |             return {'message': 'Example file object does not exist.'}
26 | 
27 |         return example_file_obj.get_content(server_hostname=urllib.parse.urlparse(request.base_url).hostname)
28 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_example_file_list.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.models
 4 | import terrareg.auth_wrapper
 5 | 
 6 | 
 7 | class ApiTerraregExampleFileList(ErrorCatchingResource):
 8 |     """Interface to obtain list of example files."""
 9 | 
10 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
11 | 
12 |     def _get(self, namespace, name, provider, version, example):
13 |         """Return list of files available in example."""
14 |         _, _, _, module_version, error = self.get_module_version_by_name(namespace, name, provider, version)
15 |         if error:
16 |             return error
17 | 
18 |         example_obj = terrareg.models.Example(module_version=module_version, module_path=example)
19 | 
20 |         if example_obj is None:
21 |             return self._get_404_response()
22 | 
23 |         return [
24 |             {
25 |                 'filename': example_file.file_name,
26 |                 'path': example_file.path,
27 |                 'content_href': '/v1/terrareg/modules/{module_version_id}/example/files/{file_path}'.format(
28 |                     module_version_id=module_version.id,
29 |                     file_path=example_file.path)
30 |             }
31 |             for example_file in sorted(example_obj.get_files())
32 |         ]
33 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_example_readme_html.py:
--------------------------------------------------------------------------------
 1 | 
 2 | import urllib.parse
 3 | 
 4 | from flask import request
 5 | 
 6 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 7 | import terrareg.models
 8 | import terrareg.auth_wrapper
 9 | 
10 | 
11 | class ApiTerraregExampleReadmeHtml(ErrorCatchingResource):
12 |     """Interface to obtain example REAMDE in HTML format."""
13 | 
14 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
15 | 
16 |     def _get(self, namespace, name, provider, version, example):
17 |         """Return HTML formatted README of example."""
18 |         _, _, _, module_version, error = self.get_module_version_by_name(namespace, name, provider, version)
19 |         if error:
20 |             return error
21 | 
22 |         example_obj = terrareg.models.Example.get(module_version=module_version, module_path=example)
23 | 
24 |         if not example_obj:
25 |             return self._get_404_response()
26 | 
27 |         return example_obj.get_readme_html(server_hostname=urllib.parse.urlparse(request.base_url).hostname)
28 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_git_providers.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.models
 4 | import terrareg.auth_wrapper
 5 | 
 6 | 
 7 | class ApiTerraregGitProviders(ErrorCatchingResource):
 8 |     """Interface to obtain git provider configurations."""
 9 | 
10 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
11 | 
12 |     def _get(self):
13 |         """Return list of git providers"""
14 |         return [
15 |             {
16 |                 "id": git_provider.pk,
17 |                 "name": git_provider.name,
18 |                 "git_path_template": git_provider.git_path_template,
19 |             }
20 |             for git_provider in terrareg.models.GitProvider.get_all()
21 |         ]
22 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_global_stats_summary.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.models
 4 | import terrareg.analytics
 5 | import terrareg.auth_wrapper
 6 | 
 7 | 
 8 | class ApiTerraregGlobalStatsSummary(ErrorCatchingResource):
 9 |     """Provide global download stats for homepage."""
10 | 
11 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
12 | 
13 |     def _get(self):
14 |         """Return number of namespaces, modules, module versions and downloads"""
15 |         return {
16 |             'namespaces': terrareg.models.Namespace.get_total_count(),
17 |             'modules': terrareg.models.ModuleProvider.get_total_count(),
18 |             'module_versions': terrareg.models.ModuleVersion.get_total_count(),
19 |             'downloads': terrareg.analytics.AnalyticsEngine.get_total_downloads()
20 |         }
21 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_global_usage_stats.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.analytics
 4 | import terrareg.models
 5 | import terrareg.auth_wrapper
 6 | 
 7 | 
 8 | class ApiTerraregGlobalUsageStats(ErrorCatchingResource):
 9 |     """Provide interface to obtain statistics about global module usage."""
10 | 
11 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
12 | 
13 |     def _get(self):
14 |         """
15 |         Return stats on total module providers,
16 |         total unique analytics tokens per module
17 |         (with and without auth token).
18 |         """
19 |         module_usage_with_auth_token = terrareg.analytics.AnalyticsEngine.get_global_module_usage_counts()
20 |         module_usage_including_empty_auth_token = terrareg.analytics.AnalyticsEngine.get_global_module_usage_counts(include_empty_auth_token=True)
21 |         total_analytics_token_with_auth_token = sum(module_usage_with_auth_token.values())
22 |         total_analytics_token_including_empty_auth_token = sum(module_usage_including_empty_auth_token.values())
23 |         return {
24 |             'module_provider_count': terrareg.models.ModuleProvider.get_total_count(),
25 |             'module_provider_usage_breakdown_with_auth_token': module_usage_with_auth_token,
26 |             'module_provider_usage_count_with_auth_token': total_analytics_token_with_auth_token,
27 |             'module_provider_usage_including_empty_auth_token': module_usage_including_empty_auth_token,
28 |             'module_provider_usage_count_including_empty_auth_token': total_analytics_token_including_empty_auth_token
29 |         }


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_health.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | 
 4 | 
 5 | class ApiTerraregHealth(ErrorCatchingResource):
 6 |     """Endpoint to return 200 when healthy."""
 7 | 
 8 |     def _get(self):
 9 |         """Return static 200"""
10 |         return {
11 |             "message": "Ok"
12 |         }
13 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_is_authenticated.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.auth_wrapper
 4 | import terrareg.auth
 5 | 
 6 | 
 7 | class ApiTerraregIsAuthenticated(ErrorCatchingResource):
 8 |     """Interface to return whether user is authenticated as an admin."""
 9 | 
10 |     def _get(self):
11 |         """Return information about current user."""
12 |         auth_method = terrareg.auth.AuthFactory().get_current_auth_method()
13 |         return {
14 |             "read_access": auth_method.can_access_read_api(),
15 |             "authenticated": auth_method.is_authenticated(),
16 |             "site_admin": auth_method.is_admin(),
17 |             "namespace_permissions": {
18 |                 namespace.name: permission.value
19 |                 for namespace, permission in auth_method.get_all_namespace_permissions().items()
20 |             }
21 |         }
22 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_provider_analytics_token_versions.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.analytics
 4 | import terrareg.auth_wrapper
 5 | 
 6 | 
 7 | class ApiTerraregModuleProviderAnalyticsTokenVersions(ErrorCatchingResource):
 8 |     """Provide download summary for module provider."""
 9 | 
10 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
11 | 
12 |     def _get(self, namespace, name, provider):
13 |         """Return list of download counts for module provider."""
14 |         _, _, module_provider, error = self.get_module_provider_by_names(namespace, name, provider)
15 |         if error:
16 |             return error
17 |         return terrareg.analytics.AnalyticsEngine.get_module_provider_token_versions(module_provider)


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_provider_delete.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.auth_wrapper
 6 | import terrareg.user_group_namespace_permission_type
 7 | import terrareg.csrf
 8 | 
 9 | 
10 | class ApiTerraregModuleProviderDelete(ErrorCatchingResource):
11 |     """Provide interface to delete module provider."""
12 | 
13 |     method_decorators = [
14 |         terrareg.auth_wrapper.auth_wrapper(
15 |             'check_namespace_access',
16 |             terrareg.user_group_namespace_permission_type.UserGroupNamespacePermissionType.FULL,
17 |             request_kwarg_map={'namespace': 'namespace'}
18 |         )
19 |     ]
20 | 
21 |     def _delete(self, namespace, name, provider):
22 |         """Delete module provider."""
23 |         parser = reqparse.RequestParser()
24 |         parser.add_argument(
25 |             'csrf_token', type=str,
26 |             required=False,
27 |             help='CSRF token',
28 |             location='json',
29 |             default=None
30 |         )
31 | 
32 |         args = parser.parse_args()
33 | 
34 |         terrareg.csrf.check_csrf_token(args.csrf_token)
35 | 
36 |         _, _, module_provider, error = self.get_module_provider_by_names(namespace, name, provider)
37 |         if error:
38 |             return error
39 | 
40 |         module_provider_id = module_provider.id
41 | 
42 |         module_provider.delete()
43 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_provider_integrations.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.auth_wrapper
 4 | 
 5 | 
 6 | class ApiTerraregModuleProviderIntegrations(ErrorCatchingResource):
 7 |     """Interface to provide list of integration URLs"""
 8 | 
 9 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
10 | 
11 |     def _get(self, namespace, name, provider):
12 |         """Return list of integration URLs"""
13 |         _, _ , module_provider, error = self.get_module_provider_by_names(namespace, name, provider)
14 |         if error:
15 |             return error
16 | 
17 |         integrations = module_provider.get_integrations()
18 | 
19 |         return [
20 |             integrations[integration]
21 |             for integration in ['upload', 'import', 'hooks_bitbucket', 'hooks_github', 'hooks_gitlab', 'publish']
22 |             if integration in integrations
23 |         ]
24 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_provider_redirects.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | from terrareg.models import ModuleProviderRedirect
 4 | 
 5 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 6 | import terrareg.auth_wrapper
 7 | import terrareg.user_group_namespace_permission_type
 8 | import terrareg.csrf
 9 | import terrareg.auth_wrapper
10 | 
11 | 
12 | class ApiTerraregModuleProviderRedirects(ErrorCatchingResource):
13 |     """Provide interface to delete module provider redirect."""
14 | 
15 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
16 | 
17 |     def _get(self, namespace, name, provider):
18 |         """Delete module provider."""
19 |         _, _, module_provider, error = self.get_module_provider_by_names(namespace, name, provider)
20 |         if error:
21 |             return error
22 | 
23 |         return [
24 |             {
25 |                 "id": module_provider_redirect.pk,
26 |                 "namespace": module_provider_redirect.namespace.name,
27 |                 "module": module_provider_redirect.module_name,
28 |                 "provider": module_provider_redirect.provider_name
29 |             }
30 |             for module_provider_redirect in ModuleProviderRedirect.get_by_module_provider(module_provider)
31 |         ]
32 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_provider_versions.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse, inputs
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.auth_wrapper
 6 | 
 7 | 
 8 | class ApiTerraregModuleProviderVersions(ErrorCatchingResource):
 9 |     """Interface to obtain module provider versions"""
10 | 
11 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
12 | 
13 |     def _get(self, namespace, name, provider):
14 |         """Return list of module versions for module provider"""
15 |         parser = reqparse.RequestParser()
16 |         parser.add_argument(
17 |             'include-beta', type=inputs.boolean,
18 |             default=False, help='Whether to include beta versions',
19 |             location='args',
20 |             dest='include_beta'
21 |         )
22 |         parser.add_argument(
23 |             'include-unpublished', type=inputs.boolean,
24 |             default=False, help='Whether to include unpublished versions',
25 |             location='args',
26 |             dest='include_unpublished'
27 |         )
28 |         args = parser.parse_args()
29 | 
30 |         namespace, module, module_provider, error = self.get_module_provider_by_names(namespace, name, provider)
31 |         if error:
32 |             return error
33 | 
34 |         return [
35 |             {
36 |                 'version': module_version.version,
37 |                 'published': module_version.published,
38 |                 'beta': module_version.beta
39 |             } for module_version in module_provider.get_versions(
40 |                 include_beta=args.include_beta, include_unpublished=args.include_unpublished)
41 |         ]
42 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_providers.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.models
 6 | import terrareg.auth_wrapper
 7 | 
 8 | 
 9 | class ApiTerraregModuleProviders(ErrorCatchingResource):
10 |     """Interface to obtain list of providers for module."""
11 | 
12 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
13 | 
14 |     def _get(self, namespace, name):
15 |         """Return list of modules in namespace"""
16 | 
17 |         parser = reqparse.RequestParser()
18 |         parser.add_argument(
19 |             'offset', type=int,
20 |             location='args',
21 |             default=0, help='Pagination offset')
22 |         parser.add_argument(
23 |             'limit', type=int,
24 |             location='args',
25 |             default=10, help='Pagination limit'
26 |         )
27 |         args = parser.parse_args()
28 | 
29 |         namespace_obj = terrareg.models.Namespace.get(name=namespace)
30 |         if namespace_obj is None:
31 |             return self._get_404_response()
32 | 
33 |         module_obj = terrareg.models.Module(namespace=namespace_obj, name=name)
34 | 
35 |         module_providers = [
36 |             module_provider
37 |             for module_provider in module_obj.get_providers()
38 |         ]
39 | 
40 |         meta = {
41 |             'limit': args.limit,
42 |             'current_offset': args.offset
43 |         }
44 |         if len(module_providers) > (args.offset + args.limit):
45 |             meta['next_offset'] = (args.offset + args.limit)
46 |         if args.offset > 0:
47 |             meta['prev_offset'] = max(args.offset - args.limit, 0)
48 | 
49 |         return {
50 |             "meta": meta,
51 |             "modules": [
52 |                 module_provider.get_api_outline()
53 |                 if module_provider.get_latest_version() is None else
54 |                 module_provider.get_latest_version().get_api_outline()
55 |                 for module_provider in module_providers[args.offset:args.offset + args.limit]
56 |             ]
57 |         }
58 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_search_filters.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.module_search
 6 | import terrareg.auth_wrapper
 7 | 
 8 | 
 9 | class ApiTerraregModuleSearchFilters(ErrorCatchingResource):
10 |     """
11 |     Return list of filters available for search.
12 |     
13 |     *Deprecation*: The `/v1/terrareg/search_filters` endpoint has been deprecated in favour of `/v1/terrareg/modules/search/filters`
14 | 
15 |     The previous endpoint will be removed in a future major release.
16 |     """
17 | 
18 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
19 | 
20 |     def _get(self):
21 |         """Return list of available filters and filter counts for search query."""
22 |         parser = reqparse.RequestParser()
23 |         parser.add_argument(
24 |             'q', type=str,
25 |             required=True,
26 |             location='args',
27 |             help='The search string.'
28 |         )
29 |         args = parser.parse_args()
30 | 
31 |         return terrareg.module_search.ModuleSearch.get_search_filters(query=args.q)


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_version_delete.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.csrf
 6 | import terrareg.auth_wrapper
 7 | import terrareg.user_group_namespace_permission_type
 8 | 
 9 | 
10 | class ApiTerraregModuleVersionDelete(ErrorCatchingResource):
11 |     """Provide interface to delete module version."""
12 | 
13 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper(
14 |         'check_namespace_access',
15 |         terrareg.user_group_namespace_permission_type.UserGroupNamespacePermissionType.FULL,
16 |         request_kwarg_map={'namespace': 'namespace'})
17 |     ]
18 | 
19 |     def _delete(self, namespace, name, provider, version):
20 |         """Delete module version."""
21 |         parser = reqparse.RequestParser()
22 |         parser.add_argument(
23 |             'csrf_token', type=str,
24 |             required=False,
25 |             help='CSRF token',
26 |             location='json',
27 |             default=None
28 |         )
29 | 
30 |         args = parser.parse_args()
31 | 
32 |         terrareg.csrf.check_csrf_token(args.csrf_token)
33 | 
34 |         _, _, _, module_version, error = self.get_module_version_by_name(namespace, name, provider, version)
35 |         if error:
36 |             return error
37 | 
38 |         module_version_id = module_version.id
39 | 
40 |         module_version.delete()
41 | 
42 |         return {
43 |             'status': 'Success'
44 |         }


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_version_examples.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.auth_wrapper
 4 | 
 5 | 
 6 | class ApiTerraregModuleVersionExamples(ErrorCatchingResource):
 7 |     """Interface to obtain list of examples in module version."""
 8 | 
 9 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
10 | 
11 |     def _get(self, namespace, name, provider, version):
12 |         """Return list of examples."""
13 |         _, _, _, module_version, error = self.get_module_version_by_name(namespace, name, provider, version)
14 |         if error:
15 |             return error
16 | 
17 |         return [
18 |             {
19 |                 'path': example.path,
20 |                 'href': example.get_view_url()
21 |             }
22 |             for example in module_version.get_examples()
23 |         ]
24 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_version_file.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.models
 4 | import terrareg.auth_wrapper
 5 | 
 6 | 
 7 | class ApiTerraregModuleVersionFile(ErrorCatchingResource):
 8 |     """Interface to obtain content of module version file."""
 9 | 
10 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
11 | 
12 |     def _get(self, namespace, name, provider, version, path):
13 |         """Return content of module version file."""
14 |         _, _, _, module_version, error = self.get_module_version_by_name(namespace, name, provider, version)
15 |         if error:
16 |             return error
17 | 
18 |         module_version_file = terrareg.models.ModuleVersionFile.get(module_version=module_version, path=path)
19 | 
20 |         if module_version_file is None:
21 |             return {'message': 'Module version file does not exist.'}, 400
22 | 
23 |         return module_version_file.get_content()
24 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_version_publish.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.auth_wrapper
 4 | 
 5 | 
 6 | class ApiTerraregModuleVersionPublish(ErrorCatchingResource):
 7 |     """Provide interface to publish module version."""
 8 | 
 9 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper(
10 |         'can_publish_module_version',
11 |         request_kwarg_map={'namespace': 'namespace'})
12 |     ]
13 | 
14 |     def _post(self, namespace, name, provider, version):
15 |         """Publish module."""
16 |         _, _, _, module_version, error = self.get_module_version_by_name(namespace, name, provider, version)
17 |         if error:
18 |             return error
19 | 
20 |         module_version.publish()
21 |         return {
22 |             'status': 'Success'
23 |         }
24 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_version_readme_html.py:
--------------------------------------------------------------------------------
 1 | 
 2 | import urllib.parse
 3 | 
 4 | from flask import request
 5 | 
 6 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 7 | import terrareg.auth_wrapper
 8 | 
 9 | 
10 | class ApiTerraregModuleVersionReadmeHtml(ErrorCatchingResource):
11 |     """Provide variable template for module version."""
12 | 
13 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
14 | 
15 |     def _get(self, namespace, name, provider, version):
16 |         """Return variable template."""
17 |         _, _, _, module_version, error = self.get_module_version_by_name(
18 |             namespace, name, provider, version)
19 |         if error:
20 |             return error
21 |         return module_version.get_readme_html(server_hostname=urllib.parse.urlparse(request.base_url).hostname)
22 | 
23 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_version_submodules.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.auth_wrapper
 4 | 
 5 | 
 6 | class ApiTerraregModuleVerisonSubmodules(ErrorCatchingResource):
 7 |     """Interface to obtain list of submodules in module version."""
 8 | 
 9 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
10 | 
11 |     def _get(self, namespace, name, provider, version):
12 |         """Return list of submodules."""
13 |         _, _, _, module_version, error = self.get_module_version_by_name(
14 |             namespace, name, provider, version)
15 |         if error:
16 |             return error
17 | 
18 |         return [
19 |             {
20 |                 'path': submodule.path,
21 |                 'href': submodule.get_view_url()
22 |             }
23 |             for submodule in module_version.get_submodules()
24 |         ]
25 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_module_version_variable_template.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.auth_wrapper
 6 | 
 7 | 
 8 | class ApiTerraregModuleVersionVariableTemplate(ErrorCatchingResource):
 9 |     """Provide variable template for module version."""
10 | 
11 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
12 | 
13 |     def _get_arg_parser(self):
14 |         """Return argument parser for GET method"""
15 |         parser = reqparse.RequestParser()
16 |         parser.add_argument(
17 |             'output',
18 |             type=str,
19 |             location='args',
20 |             default='md',
21 |             dest='output',
22 |             help='Variable/Output description format, either "html" or "md"'
23 |         )
24 |         return parser
25 | 
26 |     def _get(self, namespace, name, provider, version):
27 |         """Return variable template."""
28 |         parser = self._get_arg_parser()
29 |         args = parser.parse_args()
30 | 
31 |         _, _, _, module_version, error = self.get_module_version_by_name(
32 |             namespace, name, provider, version)
33 |         if error:
34 |             return error
35 |         return module_version.get_variable_template(html=(args.output == "html"))
36 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_most_downloaded_module_this_week.py:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 4 | import terrareg.module_search
 5 | import terrareg.auth_wrapper
 6 | 
 7 | 
 8 | class ApiTerraregMostDownloadedModuleProviderThisWeek(ErrorCatchingResource):
 9 |     """Return data for most downloaded module provider this week."""
10 | 
11 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
12 | 
13 |     def _get(self):
14 |         """Return most downloaded module this week"""
15 |         module_provider = terrareg.module_search.ModuleSearch.get_most_downloaded_module_provider_this_Week()
16 |         if not module_provider:
17 |             return {}, 404
18 | 
19 |         return module_provider.get_latest_version().get_api_outline()


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_most_recently_published_module_version.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.module_search
 4 | import terrareg.auth_wrapper
 5 | 
 6 | 
 7 | class ApiTerraregMostRecentlyPublishedModuleVersion(ErrorCatchingResource):
 8 |     """Return data for most recently published module version."""
 9 | 
10 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
11 | 
12 |     def _get(self):
13 |         """Return number of namespaces, modules, module versions and downloads"""
14 |         module_version = terrareg.module_search.ModuleSearch.get_most_recently_published()
15 |         if not module_version:
16 |             return {}, 404
17 |         return module_version.get_api_outline()
18 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_namespace_modules.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.models
 6 | import terrareg.auth_wrapper
 7 | 
 8 | 
 9 | class ApiTerraregNamespaceModules(ErrorCatchingResource):
10 |     """Interface to obtain list of modules in namespace."""
11 | 
12 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
13 | 
14 |     def _get(self, namespace):
15 |         """Return list of modules in namespace"""
16 | 
17 |         parser = reqparse.RequestParser()
18 |         parser.add_argument(
19 |             'offset', type=int,
20 |             location='args',
21 |             default=0, help='Pagination offset')
22 |         parser.add_argument(
23 |             'limit', type=int,
24 |             location='args',
25 |             default=10, help='Pagination limit'
26 |         )
27 |         args = parser.parse_args()
28 | 
29 |         namespace_obj = terrareg.models.Namespace.get(name=namespace)
30 |         if namespace_obj is None:
31 |             return self._get_404_response()
32 | 
33 |         module_providers = [
34 |             module_provider
35 |             for module in namespace_obj.get_all_modules()
36 |             for module_provider in module.get_providers()
37 |         ]
38 | 
39 |         meta = {
40 |             'limit': args.limit,
41 |             'current_offset': args.offset
42 |         }
43 |         if len(module_providers) > (args.offset + args.limit):
44 |             meta['next_offset'] = (args.offset + args.limit)
45 |         if args.offset > 0:
46 |             meta['prev_offset'] = max(args.offset - args.limit, 0)
47 | 
48 |         return {
49 |             "meta": meta,
50 |             "modules": [
51 |                 module_provider.get_api_outline()
52 |                 if module_provider.get_latest_version() is None else
53 |                 module_provider.get_latest_version().get_api_outline()
54 |                 for module_provider in module_providers[args.offset:args.offset + args.limit]
55 |             ]
56 |         }
57 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_provider_integrations.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.auth_wrapper
 4 | 
 5 | 
 6 | class ApiTerraregProviderIntegrations(ErrorCatchingResource):
 7 |     """Interface to provide list of integration URLs"""
 8 | 
 9 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
10 | 
11 |     def _get(self, namespace, provider):
12 |         """Return list of integration URLs"""
13 |         _, provider_obj, error = self.get_provider_by_names(namespace, provider)
14 |         if error:
15 |             return error
16 | 
17 |         integrations = provider_obj.get_integrations()
18 | 
19 |         return [
20 |             integrations[integration]
21 |             for integration in ['import']
22 |             if integration in integrations
23 |         ]
24 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_provider_logos.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.models
 4 | import terrareg.auth_wrapper
 5 | 
 6 | 
 7 | class ApiTerraregProviderLogos(ErrorCatchingResource):
 8 |     """Provide interface to obtain all provider logo details"""
 9 | 
10 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
11 | 
12 |     def _get(self):
13 |         """Return all details about provider logos."""
14 |         return {
15 |             provider_logo.provider: {
16 |                 'source': provider_logo.source,
17 |                 'alt': provider_logo.alt,
18 |                 'tos': provider_logo.tos,
19 |                 'link': provider_logo.link
20 |             }
21 |             for provider_logo in terrareg.models.ProviderLogo.get_all()
22 |         }
23 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_provider_search_filters.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask_restful import reqparse
 3 | 
 4 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 5 | import terrareg.provider_search
 6 | import terrareg.auth_wrapper
 7 | 
 8 | 
 9 | class ApiTerraregProviderSearchFilters(ErrorCatchingResource):
10 |     """
11 |     Return list of filters available for provider search.
12 |     """
13 | 
14 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
15 | 
16 |     def _get(self):
17 |         """Return list of available filters and filter counts for search query."""
18 |         parser = reqparse.RequestParser()
19 |         parser.add_argument(
20 |             'q', type=str,
21 |             required=True,
22 |             location='args',
23 |             help='The search string.'
24 |         )
25 |         args = parser.parse_args()
26 | 
27 |         return terrareg.provider_search.ProviderSearch.get_search_filters(query=args.q)


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_submodule_details.py:
--------------------------------------------------------------------------------
 1 | 
 2 | import urllib.parse
 3 | 
 4 | from flask import request
 5 | 
 6 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 7 | import terrareg.models
 8 | import terrareg.auth_wrapper
 9 | 
10 | 
11 | class ApiTerraregSubmoduleDetails(ErrorCatchingResource):
12 |     """Interface to obtain submodule details."""
13 | 
14 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
15 | 
16 |     def _get(self, namespace, name, provider, version, submodule):
17 |         """Return details of submodule."""
18 |         _, _, _, module_version, error = self.get_module_version_by_name(namespace, name, provider, version)
19 |         if error:
20 |             return error
21 | 
22 |         submodule_obj = terrareg.models.Submodule.get(module_version=module_version, module_path=submodule)
23 | 
24 |         if not submodule_obj:
25 |             return self._get_404_response()
26 | 
27 |         return submodule_obj.get_terrareg_api_details(
28 |             request_domain=urllib.parse.urlparse(request.base_url).hostname)
29 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_submodule_readme_html.py:
--------------------------------------------------------------------------------
 1 | 
 2 | import urllib.parse
 3 | 
 4 | from flask import request
 5 | 
 6 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 7 | import terrareg.models
 8 | import terrareg.auth_wrapper
 9 | 
10 | 
11 | class ApiTerraregSubmoduleReadmeHtml(ErrorCatchingResource):
12 |     """Interface to obtain submodule REAMDE in HTML format."""
13 | 
14 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('can_access_read_api')]
15 | 
16 |     def _get(self, namespace, name, provider, version, submodule):
17 |         """Return HTML formatted README of submodule."""
18 |         _, _, _, module_version, error = self.get_module_version_by_name(namespace, name, provider, version)
19 |         if error:
20 |             return error
21 | 
22 |         submodule_obj = terrareg.models.Submodule.get(module_version=module_version, module_path=submodule)
23 | 
24 |         if not submodule_obj:
25 |             return self._get_404_response()
26 | 
27 |         return submodule_obj.get_readme_html(server_hostname=urllib.parse.urlparse(request.base_url).hostname)
28 | 


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_user_group.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | import terrareg.auth_wrapper
 4 | import terrareg.models
 5 | 
 6 | 
 7 | class ApiTerraregAuthUserGroup(ErrorCatchingResource):
 8 |     """Interface to interact with single user group."""
 9 | 
10 |     method_decorators = [terrareg.auth_wrapper.auth_wrapper('is_admin')]
11 | 
12 |     def _delete(self, user_group):
13 |         """Delete user group."""
14 |         user_group_obj = terrareg.models.UserGroup.get_by_group_name(user_group)
15 |         if not user_group_obj:
16 |             return {'message': 'User group does not exist.'}, 400
17 | 
18 |         user_group_obj.delete()
19 |         return {}, 200


--------------------------------------------------------------------------------
/terrareg/server/api/terrareg_version.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from terrareg.server.error_catching_resource import ErrorCatchingResource
 3 | from terrareg.version import VERSION
 4 | 
 5 | 
 6 | class ApiTerraregVersion(ErrorCatchingResource):
 7 |     """Interface to obtain version of Terrareg."""
 8 | 
 9 |     def _get(self):
10 |         """Return version"""
11 | 
12 |         return {
13 |             "version": VERSION
14 |         }
15 | 


--------------------------------------------------------------------------------
/terrareg/server/api/utils.py:
--------------------------------------------------------------------------------
 1 | 
 2 | from flask import request
 3 | 
 4 | 
 5 | def get_request_domain():
 6 |     arg_domain = request.args.get('domain')
 7 |     if arg_domain:
 8 |         return arg_domain
 9 |     return request.host
10 | 
11 | def get_request_port():
12 |     return request.args.get('port')
13 | 
14 | def get_request_protocol():
15 |     return request.args.get('protocol')
16 | 


--------------------------------------------------------------------------------
/terrareg/static/css/bulma/LICENSE:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright (c) 2023 Jeremy Thomas
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.


--------------------------------------------------------------------------------
/terrareg/static/css/datatables/LICENSE:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright (c) 2008-present, SpryMedia Limited
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.


--------------------------------------------------------------------------------
/terrareg/static/css/datatables/rowGroup.bulma-1.2.0.min.css:
--------------------------------------------------------------------------------
1 | table.dataTable tr.dtrg-group th{background-color:#e0e0e0;text-align:left}table.dataTable tr.dtrg-group.dtrg-level-0 th{font-weight:bold}table.dataTable tr.dtrg-group.dtrg-level-1 th,table.dataTable tr.dtrg-group.dtrg-level-2 th,table.dataTable tr.dtrg-group.dtrg-level-3 th,table.dataTable tr.dtrg-group.dtrg-level-4 th,table.dataTable tr.dtrg-group.dtrg-level-5 th{background-color:#f0f0f0;padding-top:.25em;padding-bottom:.25em;padding-left:2em;font-size:.9em}table.dataTable tr.dtrg-group.dtrg-level-2 th{background-color:#f3f3f3;padding-left:2.5em}table.dataTable tr.dtrg-group.dtrg-level-3 th{background-color:#f3f3f3;padding-left:3em}table.dataTable tr.dtrg-group.dtrg-level-4 th{background-color:#f3f3f3;padding-left:3.5em}table.dataTable tr.dtrg-group.dtrg-level-5 th{background-color:#f3f3f3;padding-left:4em}
2 | 


--------------------------------------------------------------------------------
/terrareg/static/css/initial-setup.css:
--------------------------------------------------------------------------------
1 | .initial-setup-card {
2 |     margin-top: 20px;
3 | }
4 | #setup-cards-container a {
5 |     color: #485fc7;
6 | }


--------------------------------------------------------------------------------
/terrareg/static/css/module_provider_page.css:
--------------------------------------------------------------------------------
 1 | #provider-logo-link {
 2 |     margin-top: 30px;
 3 |     max-height: 150px;
 4 | }
 5 | #module-provider-header-content {
 6 |     background-color: #f9f9f9;
 7 |     padding-top: 30px;
 8 |     padding-bottom: 30px;
 9 | }
10 | #module-tab-readme h1, h2, h3, h4 {
11 |     margin-top: 20px;
12 | }
13 | .module-provider-tab-content-table {
14 |     width: 100%;
15 | }
16 | #yearly-cost {
17 |     margin-top: 20px;
18 |     margin-bottom: 20px;
19 | }
20 | #module-labels {
21 |     padding-top: 5px;
22 |     padding-bottom: 20px;
23 | }
24 | #current-submodule {
25 |     font-weight: bold;
26 | }
27 | .custom-link {
28 |     margin-top: 10px;
29 | }
30 | #publish-button-container {
31 |     margin-bottom: 20px;
32 | }
33 | #settings-module-version-card {
34 |     margin-bottom: 15px;
35 | }
36 | #module-tab-inputs tr {
37 |     border: 1px #eeeeee solid;
38 | }
39 | #module-tab-inputs th {
40 |     padding: 10px 5px 10px 5px;
41 | }
42 | #module-tab-inputs td {
43 |     border-left: 1px #eeeeee solid;
44 |     padding: 10px 5px 10px 5px;
45 | }
46 | #module-tab-inputs tr:nth-child(even) {
47 |     background-color: #fcfcfc;
48 | }


--------------------------------------------------------------------------------
/terrareg/static/css/prism/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT LICENSE
 2 | 
 3 | Copyright (c) 2012 Lea Verou
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.


--------------------------------------------------------------------------------
/terrareg/static/css/prism/prism-hcl-1.29.0.css:
--------------------------------------------------------------------------------
1 | /* PrismJS 1.29.0
2 | https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+bash+batch+docker+hcl+plsql+powershell+python+sql */
3 | code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
4 | 


--------------------------------------------------------------------------------
/terrareg/static/css/provider_page.css:
--------------------------------------------------------------------------------
 1 | #provider-logo-link {
 2 |     margin-top: 30px;
 3 |     max-height: 150px;
 4 | }
 5 | #provider-header-content {
 6 |     background-color: #f9f9f9;
 7 |     padding-top: 30px;
 8 |     padding-bottom: 30px;
 9 | }
10 | 
11 | #provider-labels {
12 |     padding-top: 5px;
13 |     padding-bottom: 20px;
14 | }
15 | .custom-link {
16 |     margin-top: 10px;
17 | }
18 | 
19 | .docs-link-dropdown {
20 |     position: relative !important;
21 | }
22 | 
23 | .provider-docs {
24 |     margin-top: 30px;
25 | }
26 | 
27 | /* Style ul and ol in documentation */
28 | #provider-doc-content > ul {
29 |     list-style: disc;
30 |     margin-bottom: 20px;
31 |     padding-left: 40px;
32 | }
33 | #provider-doc-content > ul > li {
34 |     padding-top: 5px;
35 | }
36 | 
37 | #provider-doc-content > h2 {
38 |     margin-top: 3rem;
39 | }
40 | 


--------------------------------------------------------------------------------
/terrareg/static/css/terrareg.css:
--------------------------------------------------------------------------------
 1 | 
 2 | .result-card-label {
 3 |     margin-left: 8px;
 4 |     padding: 4px
 5 | }
 6 | 
 7 | .default-hidden {
 8 |     display: none !important;
 9 | }
10 | 
11 | .module-provider-card-provider-text {
12 |     vertical-align: middle;
13 | }
14 | .module-provider-card-provider-text > button {
15 |     height: 100%;
16 |     vertical-align: middle;
17 | }
18 | 
19 | #search-results-header {
20 |     padding: 10px;
21 | }
22 | 
23 | table.dataTable tr.dtrg-group th {
24 |     background-color: #EDEDED !important;
25 | }
26 | 
27 | table.dataTable .select {
28 |     height: auto !important;
29 | }
30 | 
31 | #module-tab-usage-builder .dt-buttons {
32 |     padding-top: 10px;
33 |     padding-bottom: 10px;
34 | }
35 | 
36 | .input,.select select,.textarea{
37 |     color: black;
38 | }
39 | 
40 | /* Remove right padding to join first result count span
41 |    with the icon
42 | */
43 | #result-card-label-security-issues-icon {
44 |     padding-right: 3px !important;
45 | }
46 | /* First visible security issue result tag */
47 | .result-card-label-security-issue-result-tag:not(.default-hidden) {
48 |     padding-left: 3px !important;
49 | }
50 | /* All subsequent security issues result tags */
51 | .result-card-label-security-issue-result-tag:not(.default-hidden) ~ .result-card-label-security-issue-result-tag:not(.default-hidden) {
52 |     padding-left: .75em !important;
53 | }
54 | 
55 | #site-warning {
56 |     width: 100%;
57 |     height: 40px;
58 |     background-color: red;
59 |     color: white;
60 |     font-weight: bold;
61 |     text-align: center;
62 |     padding-top: 7px;
63 | }
64 | 


--------------------------------------------------------------------------------
/terrareg/static/images/PB_AWS_logo_RGB_stacked.547f032d90171cdea4dd90c258f47373c5573db5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/static/images/PB_AWS_logo_RGB_stacked.547f032d90171cdea4dd90c258f47373c5573db5.png


--------------------------------------------------------------------------------
/terrareg/static/images/consul.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/static/images/consul.png


--------------------------------------------------------------------------------
/terrareg/static/images/dd_logo_v_rgb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/static/images/dd_logo_v_rgb.png


--------------------------------------------------------------------------------
/terrareg/static/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/static/images/favicon.ico


--------------------------------------------------------------------------------
/terrareg/static/images/gcp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/static/images/gcp.png


--------------------------------------------------------------------------------
/terrareg/static/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/static/images/logo.png


--------------------------------------------------------------------------------
/terrareg/static/images/nomad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/static/images/nomad.png


--------------------------------------------------------------------------------
/terrareg/static/images/null.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/static/images/null.png


--------------------------------------------------------------------------------
/terrareg/static/images/vagrant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/static/images/vagrant.png


--------------------------------------------------------------------------------
/terrareg/static/images/vault.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/terrareg/static/images/vault.png


--------------------------------------------------------------------------------
/terrareg/static/js/cytoscape/LICENSE:
--------------------------------------------------------------------------------
 1 | Copyright (c) 2016-2023, The Cytoscape Consortium.
 2 | 
 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
 4 | this software and associated documentation files (the “Software”), to deal in
 5 | the Software without restriction, including without limitation the rights to
 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 7 | of the Software, and to permit persons to whom the Software is furnished to do
 8 | so, subject to the following conditions:
 9 | 
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 | 
13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.


--------------------------------------------------------------------------------
/terrareg/static/js/datatables/LICENSE:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright (c) 2008-present, SpryMedia Limited
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.


--------------------------------------------------------------------------------
/terrareg/static/js/datatables/full_numbers_no_ellipses.js:
--------------------------------------------------------------------------------
 1 | /**
 2 |  *  Plug-in offers the same functionality as `full_numbers` pagination type 
 3 |  *  (see `pagingType` option) but without ellipses.
 4 |  *
 5 |  *  See [example](http://www.gyrocode.com/articles/jquery-datatables-pagination-without-ellipses) for demonstration.
 6 |  *
 7 |  *  @name Full Numbers - No Ellipses
 8 |  *  @summary Same pagination as 'full_numbers' but without ellipses
 9 |  *  @author [Michael Ryvkin](http://www.gyrocode.com)
10 |  *
11 |  *  @example
12 |  *    $(document).ready(function() {
13 |  *        $('#example').dataTable( {
14 |  *            "pagingType": "full_numbers_no_ellipses"
15 |  *        } );
16 |  *    } );
17 |  */
18 | 
19 | $.fn.DataTable.ext.pager.full_numbers_no_ellipses = function(page, pages){
20 |    var numbers = [];
21 |    var buttons = $.fn.DataTable.ext.pager.numbers_length;
22 |    var half = Math.floor( buttons / 2 );
23 | 
24 |    var _range = function ( len, start ){
25 |       var end;
26 |    
27 |       if ( typeof start === "undefined" ){ 
28 |          start = 0;
29 |          end = len;
30 | 
31 |       } else {
32 |          end = start;
33 |          start = len;
34 |       }
35 | 
36 |       var out = []; 
37 |       for ( var i = start ; i < end; i++ ){ out.push(i); }
38 |    
39 |       return out;
40 |    };
41 |     
42 | 
43 |    if ( pages <= buttons ) {
44 |       numbers = _range( 0, pages );
45 | 
46 |    } else if ( page <= half ) {
47 |       numbers = _range( 0, buttons);
48 | 
49 |    } else if ( page >= pages - 1 - half ) {
50 |       numbers = _range( pages - buttons, pages );
51 | 
52 |    } else {
53 |       numbers = _range( page - half, page + half + 1);
54 |    }
55 | 
56 |    numbers.DT_el = 'span';
57 | 
58 |    return [ 'first', 'previous', numbers, 'next', 'last' ];
59 | };
60 | 


--------------------------------------------------------------------------------
/terrareg/static/js/jquery/LICENSE:
--------------------------------------------------------------------------------
 1 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/
 2 | 
 3 | Permission is hereby granted, free of charge, to any person obtaining
 4 | a copy of this software and associated documentation files (the
 5 | "Software"), to deal in the Software without restriction, including
 6 | without limitation the rights to use, copy, modify, merge, publish,
 7 | distribute, sublicense, and/or sell copies of the Software, and to
 8 | permit persons to whom the Software is furnished to do so, subject to
 9 | the following conditions:
10 | 
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 | 
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


--------------------------------------------------------------------------------
/terrareg/static/js/navigo/LICENSE:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright (c) 2015 Krasimir Tsonev
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.


--------------------------------------------------------------------------------
/terrareg/static/js/prism/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT LICENSE
 2 | 
 3 | Copyright (c) 2012 Lea Verou
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.


--------------------------------------------------------------------------------
/terrareg/templates/audit_history.html:
--------------------------------------------------------------------------------
 1 | {% extends 'template.html' %}
 2 | 
 3 | {% block title %}Audit History{% endblock %}
 4 | 
 5 | {% block header %}
 6 | 
23 | {% endblock %}
24 | 
25 | {% block content %}
26 | 
27 | 
32 | 
33 | 
34 | 35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /terrareg/templates/error.html: -------------------------------------------------------------------------------- 1 | {% extends 'template.html' %} 2 | 3 | {% block title %}Error{% endblock %} 4 | 5 | {% block content %} 6 | 7 | 19 | 20 | 21 |
22 |
23 |

24 | {{ error_title }} 25 |

26 |
27 |

28 | {{ error_description }} 29 |

30 |
31 |
32 | 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /terrareg/templates/module.html: -------------------------------------------------------------------------------- 1 | {% extends 'template.html' %} 2 | 3 | {% block title %}{{ namespace.name }}/{{ module.name }}{% endblock %} 4 | 5 | {% block header %} 6 | 24 | {% endblock %} 25 | 26 | {% block content %} 27 | 28 | 35 | 36 |
37 |
38 | This namespace/module does not exist 39 |
40 |
41 | There are no providers for this module 42 |
43 |
44 | 45 |
46 |
47 |
48 |
49 | 50 | {% endblock %} 51 | -------------------------------------------------------------------------------- /terrareg/terraform_product.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | import terrareg.config 4 | 5 | 6 | class BaseProduct(abc.ABC): 7 | 8 | @abc.abstractmethod 9 | def get_tfswitch_product_arg(self) -> str: 10 | """Return name of tfswitch product argument value""" 11 | ... 12 | 13 | @abc.abstractmethod 14 | def get_executable_name(self) -> str: 15 | """Return executable name for product""" 16 | ... 17 | 18 | class Terraform(BaseProduct): 19 | """Terraform product""" 20 | 21 | def get_tfswitch_product_arg(self) -> str: 22 | """Return name of tfswitch product argument value""" 23 | return "terraform" 24 | 25 | def get_executable_name(self) -> str: 26 | """Return executable name for product""" 27 | return "terraform" 28 | 29 | 30 | class OpenTofu(BaseProduct): 31 | """OpenTofu product""" 32 | 33 | def get_tfswitch_product_arg(self) -> str: 34 | """Return name of tfswitch product argument value""" 35 | return "opentofu" 36 | 37 | def get_executable_name(self) -> str: 38 | """Return executable name for product""" 39 | return "tofu" 40 | 41 | 42 | class ProductFactory: 43 | 44 | @staticmethod 45 | def get_product(): 46 | """Obtain current product""" 47 | product_enum = terrareg.config.Config().PRODUCT 48 | if product_enum is terrareg.config.Product.TERRAFORM: 49 | return Terraform() 50 | elif product_enum is terrareg.config.Product.OPENTOFU: 51 | return OpenTofu() 52 | raise Exception("Could not determine product class") 53 | -------------------------------------------------------------------------------- /terrareg/user_group_namespace_permission_type.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from enum import Enum 4 | 5 | 6 | class UserGroupNamespacePermissionType(Enum): 7 | """User group namespace permission access level type""" 8 | 9 | FULL = 'FULL' 10 | MODIFY = 'MODIFY' 11 | -------------------------------------------------------------------------------- /terrareg/version.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import Optional 3 | import os 4 | 5 | 6 | version_file: str = os.path.join(os.path.dirname(os.path.realpath(__file__)), "version.txt") 7 | VERSION: Optional[str] = None 8 | 9 | if os.path.isfile(version_file): 10 | try: 11 | with open(version_file, "r") as fh: 12 | VERSION = fh.readline().strip() 13 | except Exception as exc: 14 | print("Failed to read version file", exc) 15 | -------------------------------------------------------------------------------- /test/integration/README.md: -------------------------------------------------------------------------------- 1 | # Integration tests 2 | 3 | These test against a 'real' database, testing allow model functionality 4 | 5 | Any direct tests against models are places in here. 6 | 7 | Mock data is inserted into the real database, testing the sqlalchemy code. 8 | -------------------------------------------------------------------------------- /test/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/integration/__init__.py -------------------------------------------------------------------------------- /test/integration/terrareg/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest.mock 3 | 4 | from terrareg.auth import AdminApiKeyAuthMethod 5 | 6 | from test import BaseTest 7 | import terrareg.database 8 | from .test_data import ( 9 | integration_test_data, 10 | integration_git_providers, 11 | integration_provider_categories, 12 | integration_provider_sources 13 | ) 14 | 15 | 16 | class TerraregIntegrationTest(BaseTest): 17 | 18 | _TEST_DATA = integration_test_data 19 | _GIT_PROVIDER_DATA = integration_git_providers 20 | _PROVIDER_CATEGORIES = integration_provider_categories 21 | _PROVIDER_SOURCES = integration_provider_sources 22 | 23 | @staticmethod 24 | def _get_database_path(): 25 | """Return path of database file to use.""" 26 | return 'temp-integration.db' 27 | 28 | @classmethod 29 | def setup_class(cls): 30 | """Setup class method""" 31 | super(TerraregIntegrationTest, cls).setup_class() 32 | 33 | # Mock get_current_auth_method, which is used when 34 | # creating audit events. 35 | cls._get_current_auth_method_mock = unittest.mock.patch( 36 | 'terrareg.auth.AuthFactory.get_current_auth_method', 37 | return_value=AdminApiKeyAuthMethod()) 38 | cls._get_current_auth_method_mock.start() 39 | 40 | @classmethod 41 | def teardown_class(cls): 42 | """Teardown class""" 43 | cls._get_current_auth_method_mock.stop() 44 | 45 | super(TerraregIntegrationTest, cls).teardown_class() 46 | 47 | def _delete_audit_events(self): 48 | """Delete all audit events""" 49 | db = terrareg.database.Database.get() 50 | with db.get_connection() as conn: 51 | conn.execute(db.audit_history.delete()) 52 | -------------------------------------------------------------------------------- /test/integration/terrareg/analytics_engine/test_get_global_module_usage.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from terrareg.analytics import AnalyticsEngine 4 | from . import AnalyticsIntegrationTest 5 | 6 | 7 | class TestGetGlobalModuleUsage(AnalyticsIntegrationTest): 8 | """Test get_global_module_usage function.""" 9 | 10 | def test_get_global_module_usage_counts_with_no_analytics(self): 11 | """Test function with no analytics recorded.""" 12 | assert AnalyticsEngine.get_global_module_usage_counts() == {} 13 | 14 | def test_get_global_module_usage_counts_excluding_no_environment(self): 15 | """Test function with default functionality, excluding stats for analytics without an API token""" 16 | self._import_test_analytics(self._TEST_ANALYTICS_DATA) 17 | 18 | assert AnalyticsEngine.get_global_module_usage_counts() == { 19 | 'testnamespace/publishedmodule/testprovider': 4, 20 | 'testnamespace/publishedmodule/secondprovider': 2, 21 | 'testnamespace/secondmodule/testprovider': 2, 22 | 'secondnamespace/othernamespacemodule/anotherprovider': 1 23 | } 24 | 25 | def test_get_global_module_usage_counts_including_empty_auth_token(self): 26 | """Test function including stats for analytics without an auth token""" 27 | self._import_test_analytics(self._TEST_ANALYTICS_DATA) 28 | 29 | assert AnalyticsEngine.get_global_module_usage_counts(include_empty_auth_token=True) == { 30 | 'testnamespace/publishedmodule/testprovider': 5, 31 | 'testnamespace/publishedmodule/secondprovider': 2, 32 | 'testnamespace/secondmodule/testprovider': 2, 33 | 'secondnamespace/othernamespacemodule/anotherprovider': 1, 34 | 'testnamespace/noanalyticstoken/testprovider': 1 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/integration/terrareg/audit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/integration/terrareg/audit/__init__.py -------------------------------------------------------------------------------- /test/integration/terrareg/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/integration/terrareg/models/__init__.py -------------------------------------------------------------------------------- /test/integration/terrareg/models/provider_source/base_provider_source_tests.py: -------------------------------------------------------------------------------- 1 | 2 | from .test_base_provider_source import TestBaseProviderSource 3 | import terrareg.database 4 | import terrareg.provider_source 5 | 6 | 7 | class BaseProviderSourceTests(TestBaseProviderSource): 8 | """Base tests for child classes of BaseProviderSource""" 9 | 10 | ADDITIONAL_CONFIG = None 11 | 12 | def test_generate_db_config_from_source_config(self): 13 | """Test generate_db_config_from_source_config""" 14 | raise NotImplementedError 15 | 16 | def test_login_button_text(self): 17 | """Test login_button_text property""" 18 | raise NotImplementedError 19 | 20 | def test_get_user_access_token(self): 21 | """Test get_user_access_token""" 22 | raise NotImplementedError 23 | 24 | def test_update_repositories(self): 25 | """Test update_repositories""" 26 | raise NotImplementedError 27 | 28 | def test_refresh_namespace_repositories(self): 29 | """Test refresh_namespace_repositories""" 30 | raise NotImplementedError 31 | 32 | def test_get_new_releases(self): 33 | """Test get_new_releases""" 34 | raise NotImplementedError 35 | 36 | def test_get_release_artifact(self): 37 | """Test get_release_artifact""" 38 | raise NotImplementedError 39 | 40 | def test_get_release_archive(self): 41 | """Test get_release_archive""" 42 | raise NotImplementedError 43 | 44 | def test_get_public_source_url(self): 45 | """Test get_public_source_url""" 46 | raise NotImplementedError 47 | 48 | def test_get_public_artifact_download_url(self): 49 | """Test get_public_artifact_download_url""" 50 | raise NotImplementedError 51 | -------------------------------------------------------------------------------- /test/integration/terrareg/models/test_base_submodule.py: -------------------------------------------------------------------------------- 1 | 2 | from datetime import datetime 3 | import unittest.mock 4 | import pytest 5 | import sqlalchemy 6 | from terrareg.analytics import AnalyticsEngine 7 | from terrareg.database import Database 8 | 9 | from terrareg.models import Example, ExampleFile, Module, Namespace, ModuleProvider, ModuleVersion 10 | import terrareg.errors 11 | from test.integration.terrareg import TerraregIntegrationTest 12 | 13 | class CommonBaseSubmodule(TerraregIntegrationTest): 14 | 15 | SUBMODULE_CLASS = None 16 | 17 | @pytest.mark.parametrize('provider_git_path,module_path,expected_path', [ 18 | # Various methods of provider git path being root 19 | (None, 'path/to/module', 'path/to/module'), 20 | ('', 'path/to/module', 'path/to/module'), 21 | ('/', 'path/to/module', 'path/to/module'), 22 | ('.', 'path/to/module', 'path/to/module'), 23 | ('./', 'path/to/module', 'path/to/module'), 24 | 25 | ('subsubdir', 'path/to/module', 'subsubdir/path/to/module'), 26 | ('./subsubdir', 'path/to/module', 'subsubdir/path/to/module'), 27 | ('./subsubdir/', 'path/to/module', 'subsubdir/path/to/module'), 28 | ('subsubdir/', 'path/to/module', 'subsubdir/path/to/module'), 29 | 30 | ('multiple/directories/in/', 'path/to/module', 'multiple/directories/in/path/to/module'), 31 | ]) 32 | def test_git_path(self, provider_git_path, module_path, expected_path): 33 | provider = ModuleProvider.get(Module(Namespace('moduledetails'), 'git-path'), 'provider') 34 | version = ModuleVersion.get(provider, '1.0.0') 35 | version.update_attributes(git_path=provider_git_path) 36 | submodule = self.SUBMODULE_CLASS.create(version, module_path) 37 | 38 | try: 39 | assert submodule.git_path == expected_path 40 | finally: 41 | submodule.delete() 42 | -------------------------------------------------------------------------------- /test/integration/terrareg/models/test_module.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | from terrareg.models import Module, Namespace 5 | import terrareg.errors 6 | from test.integration.terrareg import TerraregIntegrationTest 7 | 8 | class TestModule(TerraregIntegrationTest): 9 | 10 | @pytest.mark.parametrize('module_name', [ 11 | 'invalid@atsymbol', 12 | 'invalid"doublequote', 13 | "invalid'singlequote", 14 | '-startwithdash', 15 | 'endwithdash-', 16 | '_startwithunderscore', 17 | 'endwithunscore_', 18 | 'a:colon', 19 | 'or;semicolon', 20 | 'who?knows', 21 | '-a', 22 | 'a-', 23 | 'a_', 24 | '_a', 25 | '__', 26 | '--', 27 | '_', 28 | '-', 29 | ]) 30 | def test_invalid_module_names(self, module_name): 31 | """Test invalid module names""" 32 | namespace = Namespace(name='test') 33 | with pytest.raises(terrareg.errors.InvalidModuleNameError): 34 | Module(namespace=namespace, name=module_name) 35 | 36 | @pytest.mark.parametrize('module_name', [ 37 | 'normalname', 38 | 'name2withnumber', 39 | '2startendiwthnumber2', 40 | 'contains4number', 41 | 'with-dash', 42 | 'with_underscore', 43 | 'withAcapital', 44 | 'StartwithCaptital', 45 | 'endwithcapitaL', 46 | # Two letters 47 | 'tl', 48 | # Two numbers 49 | '11', 50 | # Two characters with dash/unserscore 51 | 'a-z', 52 | 'a_z', 53 | ]) 54 | def test_valid_module_names(self, module_name): 55 | """Test valid module names""" 56 | namespace = Namespace(name='test') 57 | Module(namespace=namespace, name=module_name) 58 | -------------------------------------------------------------------------------- /test/integration/terrareg/models/test_submodule.py: -------------------------------------------------------------------------------- 1 | 2 | from terrareg.models import Submodule 3 | from test.integration.terrareg.models.test_base_submodule import CommonBaseSubmodule 4 | 5 | class TestSubmodule(CommonBaseSubmodule): 6 | 7 | SUBMODULE_CLASS = Submodule 8 | -------------------------------------------------------------------------------- /test/integration/terrareg/module_search/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/integration/terrareg/module_search/__init__.py -------------------------------------------------------------------------------- /test/integration/terrareg/provider_search/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/integration/terrareg/provider_search/__init__.py -------------------------------------------------------------------------------- /test/integration/terrareg/server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/integration/terrareg/server/__init__.py -------------------------------------------------------------------------------- /test/integration/terrareg/test_provider_documentation_type.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | import terrareg.provider_documentation_type 5 | 6 | 7 | class TestProviderSourceType: 8 | """Test ProviderDocumentationType""" 9 | 10 | @pytest.mark.parametrize('value, expected_enum', [ 11 | ('overview', terrareg.provider_documentation_type.ProviderDocumentationType.OVERVIEW), 12 | ('provider', terrareg.provider_documentation_type.ProviderDocumentationType.PROVIDER), 13 | ('resources', terrareg.provider_documentation_type.ProviderDocumentationType.RESOURCE), 14 | ('data-sources', terrareg.provider_documentation_type.ProviderDocumentationType.DATA_SOURCE), 15 | ('guides', terrareg.provider_documentation_type.ProviderDocumentationType.GUIDE), 16 | ]) 17 | def test_by_value(self, value, expected_enum): 18 | """Test getting by value""" 19 | assert terrareg.provider_documentation_type.ProviderDocumentationType(value) == expected_enum 20 | 21 | @pytest.mark.parametrize('value, expected_enum', [ 22 | ('OVERVIEW', terrareg.provider_documentation_type.ProviderDocumentationType.OVERVIEW), 23 | ('PROVIDER', terrareg.provider_documentation_type.ProviderDocumentationType.PROVIDER), 24 | ('RESOURCE', terrareg.provider_documentation_type.ProviderDocumentationType.RESOURCE), 25 | ('DATA_SOURCE', terrareg.provider_documentation_type.ProviderDocumentationType.DATA_SOURCE), 26 | ('GUIDE', terrareg.provider_documentation_type.ProviderDocumentationType.GUIDE), 27 | ]) 28 | def test_by_key(self, value, expected_enum): 29 | """Test getting by value""" 30 | assert terrareg.provider_documentation_type.ProviderDocumentationType[value] == expected_enum 31 | -------------------------------------------------------------------------------- /test/integration/terrareg/test_provider_source_type.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | import terrareg.provider_source_type 5 | 6 | 7 | class TestProviderSourceType: 8 | """Test ProviderSourceType""" 9 | 10 | @pytest.mark.parametrize('value, expected_enum', [ 11 | ('github', terrareg.provider_source_type.ProviderSourceType.GITHUB) 12 | ]) 13 | def test_by_value(self, value, expected_enum): 14 | """Test getting by value""" 15 | assert terrareg.provider_source_type.ProviderSourceType(value) == expected_enum 16 | 17 | @pytest.mark.parametrize('value, expected_enum', [ 18 | ('GITHUB', terrareg.provider_source_type.ProviderSourceType.GITHUB) 19 | ]) 20 | def test_by_key(self, value, expected_enum): 21 | """Test getting by value""" 22 | assert terrareg.provider_source_type.ProviderSourceType[value] == expected_enum 23 | -------------------------------------------------------------------------------- /test/integration/terrareg/test_provider_tier.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | from test.integration.terrareg import TerraregIntegrationTest 5 | import terrareg.provider_tier 6 | 7 | 8 | class TestProviderTier(TerraregIntegrationTest): 9 | """Test Registry resource type""" 10 | 11 | @pytest.mark.parametrize('value, expected_enum', [ 12 | ('community', terrareg.provider_tier.ProviderTier.COMMUNITY), 13 | ('official', terrareg.provider_tier.ProviderTier.OFFICIAL), 14 | ]) 15 | def test_by_value(self, value, expected_enum): 16 | """Test getting by value""" 17 | assert terrareg.provider_tier.ProviderTier(value) == expected_enum 18 | 19 | @pytest.mark.parametrize('value, expected_enum', [ 20 | ('COMMUNITY', terrareg.provider_tier.ProviderTier.COMMUNITY), 21 | ('OFFICIAL', terrareg.provider_tier.ProviderTier.OFFICIAL), 22 | ]) 23 | def test_by_key(self, value, expected_enum): 24 | """Test getting by value""" 25 | assert terrareg.provider_tier.ProviderTier[value] == expected_enum 26 | -------------------------------------------------------------------------------- /test/integration/terrareg/test_registry_resource_type.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | from test.integration.terrareg import TerraregIntegrationTest 5 | import terrareg.registry_resource_type 6 | 7 | 8 | class TestRegistryResourceType(TerraregIntegrationTest): 9 | """Test Registry resource type""" 10 | 11 | @pytest.mark.parametrize('value, expected_enum', [ 12 | ('module', terrareg.registry_resource_type.RegistryResourceType.MODULE), 13 | ('provider', terrareg.registry_resource_type.RegistryResourceType.PROVIDER), 14 | ]) 15 | def test_by_value(self, value, expected_enum): 16 | """Test getting by value""" 17 | assert terrareg.registry_resource_type.RegistryResourceType(value) == expected_enum 18 | 19 | @pytest.mark.parametrize('value, expected_enum', [ 20 | ('MODULE', terrareg.registry_resource_type.RegistryResourceType.MODULE), 21 | ('PROVIDER', terrareg.registry_resource_type.RegistryResourceType.PROVIDER), 22 | ]) 23 | def test_by_key(self, value, expected_enum): 24 | """Test getting by value""" 25 | assert terrareg.registry_resource_type.RegistryResourceType[value] == expected_enum 26 | -------------------------------------------------------------------------------- /test/integration/terrareg/test_repository_kind.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | from test.integration.terrareg import TerraregIntegrationTest 5 | import terrareg.repository_kind 6 | 7 | 8 | class TestRepositoryKind(TerraregIntegrationTest): 9 | """Test Registry resource type""" 10 | 11 | @pytest.mark.parametrize('value, expected_enum', [ 12 | ('module', terrareg.repository_kind.RepositoryKind.MODULE), 13 | ('provider', terrareg.repository_kind.RepositoryKind.PROVIDER), 14 | ]) 15 | def test_by_value(self, value, expected_enum): 16 | """Test getting by value""" 17 | assert terrareg.repository_kind.RepositoryKind(value) == expected_enum 18 | 19 | @pytest.mark.parametrize('value, expected_enum', [ 20 | ('MODULE', terrareg.repository_kind.RepositoryKind.MODULE), 21 | ('PROVIDER', terrareg.repository_kind.RepositoryKind.PROVIDER), 22 | ]) 23 | def test_by_key(self, value, expected_enum): 24 | """Test getting by value""" 25 | assert terrareg.repository_kind.RepositoryKind[value] == expected_enum 26 | -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_example.png -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_example_full_modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_example_full_modules.png -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_example_full_resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_example_full_resources.png -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_example_full_resources_full_modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_example_full_resources_full_modules.png -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_root_module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_root_module.png -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_root_module_full_modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_root_module_full_modules.png -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_root_module_full_resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_root_module_full_resources.png -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_root_module_full_resources_full_modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_root_module_full_resources_full_modules.png -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_submodule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_submodule.png -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_submodule_full_modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_submodule_full_modules.png -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_submodule_full_resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_submodule_full_resources.png -------------------------------------------------------------------------------- /test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_submodule_full_resources_full_modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/selenium/test_graph_canvas_images/moduledetails_fullypopulated_testprovider_1.5.0_submodule_full_resources_full_modules.png -------------------------------------------------------------------------------- /test/unit/README.md: -------------------------------------------------------------------------------- 1 | # Integration tests 2 | 3 | These test against a semi-mocked models, testing against fake data supplied. 4 | 5 | This is mainly to test 'user interfaces', i.e. the flask endpoints and flask-restful resources 6 | 7 | Any functions that call the database directly (via sqlalchemy) are mocked. 8 | -------------------------------------------------------------------------------- /test/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/unit/__init__.py -------------------------------------------------------------------------------- /test/unit/terrareg/auth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/unit/terrareg/auth/__init__.py -------------------------------------------------------------------------------- /test/unit/terrareg/auth/base_auth_method_test.py: -------------------------------------------------------------------------------- 1 | 2 | from test.unit.terrareg import TerraregUnitTest 3 | 4 | 5 | class BaseAuthMethodTest(TerraregUnitTest): 6 | """Test common smaller methods of auth methods""" 7 | 8 | def test_is_built_in_admin(self): 9 | """Test is_built_in_admin method""" 10 | raise NotImplementedError 11 | 12 | def test_is_admin(self): 13 | """Test is_admin method""" 14 | raise NotImplementedError 15 | 16 | def test_is_authenticated(self): 17 | """Test is_authenticated method""" 18 | raise NotImplementedError 19 | 20 | def test_is_enabled(self): 21 | """Test is_enabled method""" 22 | raise NotImplementedError 23 | 24 | def test_requires_csrf_tokens(self): 25 | """Test requires_csrf_token method""" 26 | raise NotImplementedError 27 | 28 | def test_can_publish_module_version(self): 29 | """Test can_publish_module_version method""" 30 | raise NotImplementedError 31 | 32 | def test_can_upload_module_version(self): 33 | """Test can_upload_module_version method""" 34 | raise NotImplementedError 35 | 36 | def test_check_auth_state(self): 37 | """Test check_auth_state method""" 38 | raise NotImplementedError 39 | 40 | def test_check_namespace_access(self): 41 | """Test check_namespace_access method""" 42 | raise NotImplementedError 43 | 44 | def test_get_username(self): 45 | """Test check_username method""" 46 | raise NotImplementedError 47 | 48 | def test_can_access_read_api(self): 49 | """Test can_access_read_api method""" 50 | raise NotImplementedError 51 | 52 | def test_can_access_terraform_api(self): 53 | """Test can_access_terraform_api method""" 54 | raise NotImplementedError 55 | 56 | def test_should_record_terraform_analytics(self): 57 | """Test should_record_terraform_analytics method""" 58 | raise NotImplementedError 59 | 60 | def test_get_terraform_auth_token(self): 61 | """Test get_terraform_auth_token method""" 62 | raise NotImplementedError 63 | -------------------------------------------------------------------------------- /test/unit/terrareg/server/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/unit/terrareg/server/api/__init__.py -------------------------------------------------------------------------------- /test/unit/terrareg/server/api/terraform/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/unit/terrareg/server/api/terraform/__init__.py -------------------------------------------------------------------------------- /test/unit/terrareg/server/api/terraform/v2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewJohn/terrareg/2d4f335a3d8ecb2a4f5b998ee2855ac71285f30d/test/unit/terrareg/server/api/terraform/v2/__init__.py -------------------------------------------------------------------------------- /test/unit/terrareg/server/test_api_terrareg_git_providers.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest import mock 3 | 4 | from test.unit.terrareg import TerraregUnitTest, setup_test_data, mock_models 5 | from test import client 6 | import terrareg.models 7 | 8 | 9 | class TestApiTerraregGitProviders(TerraregUnitTest): 10 | """Test TestApiTerraregGitProviders resource.""" 11 | 12 | def test_with_no_git_providers_configured(self, mock_models, client): 13 | """Test endpoint when no git providers are configured.""" 14 | res = client.get('/v1/terrareg/git_providers') 15 | assert res.status_code == 200 16 | assert res.json == [] 17 | 18 | @setup_test_data() 19 | def test_with_git_providers_configured(self, mock_models, client): 20 | """Test endpoint with git providers configured.""" 21 | res = client.get('/v1/terrareg/git_providers') 22 | assert res.status_code == 200 23 | assert res.json == [ 24 | {'id': 1, 'name': 'testgitprovider', 'git_path_template': None}, 25 | {'id': 2, 'name': 'second-git-provider', 'git_path_template': '/modules/{module}/'}, 26 | {'git_path_template': '/modules/{module}/', 'id': 3, 'name': 'third-git-provider'}, 27 | ] 28 | 29 | def test_unauthenticated(self, client, mock_models): 30 | """Test unauthenticated call to API""" 31 | def call_endpoint(): 32 | return client.get('/v1/terrareg/git_providers') 33 | 34 | self._test_unauthenticated_read_api_endpoint_test(call_endpoint) 35 | -------------------------------------------------------------------------------- /test/unit/terrareg/server/test_api_terrareg_health.py: -------------------------------------------------------------------------------- 1 | 2 | from test.unit.terrareg import TerraregUnitTest 3 | from test import client 4 | 5 | 6 | class TestApiTerraregHealth(TerraregUnitTest): 7 | """Test ApiTerraregHealth resource.""" 8 | 9 | def test_with_no_params(self, client): 10 | """Test endpoint without query parameters""" 11 | res = client.get('/v1/terrareg/health') 12 | assert res.status_code == 200 13 | assert res.json == { 14 | 'message': 'Ok' 15 | } 16 | 17 | def test_with_post(self, client): 18 | """Test invalid post request""" 19 | res = client.post('/v1/terrareg/health') 20 | assert res.status_code == 405 21 | -------------------------------------------------------------------------------- /test/unit/terrareg/server/test_prometheus_metrics.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest.mock 3 | 4 | import pytest 5 | 6 | from test.unit.terrareg import TerraregUnitTest 7 | from test import client, app_context, test_request_context 8 | 9 | 10 | class TestPrometheusMetrics(TerraregUnitTest): 11 | """Test global usage stats endpoint""" 12 | 13 | def test_prometheus_metrics( 14 | self, app_context, 15 | test_request_context, 16 | client 17 | ): 18 | """Test update of repository URL.""" 19 | with client, \ 20 | unittest.mock.patch('terrareg.analytics.AnalyticsEngine.get_prometheus_metrics') as mock_get_prometheus_metrics: 21 | 22 | mock_get_prometheus_metrics.return_value = """ 23 | # HELP unittest_output_count Unittest test output 24 | # # TYPE unittest_output_count counter 25 | # unittest_output_count 5 26 | """.strip() 27 | 28 | res = client.get('/metrics') 29 | 30 | assert res.data.decode('utf-8') == """ 31 | # HELP unittest_output_count Unittest test output 32 | # # TYPE unittest_output_count counter 33 | # unittest_output_count 5 34 | """.strip() 35 | assert res.status_code == 200 36 | assert res.headers['Content-Type'] == 'text/plain; version=0.0.4' 37 | 38 | mock_get_prometheus_metrics.assert_called_once() 39 | -------------------------------------------------------------------------------- /test/unit/terrareg/server/test_terraform_well_known.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest import mock 3 | from test.unit.terrareg import TerraregUnitTest 4 | from test import client 5 | 6 | 7 | class TestTerraformWellKnown(TerraregUnitTest): 8 | """Test TerraformWellKnown resource.""" 9 | 10 | def test_with_no_params(self, client): 11 | """Test endpoint without query parameters""" 12 | with mock.patch('terrareg.terraform_idp.TerraformIdp.is_enabled', False): 13 | res = client.get('/.well-known/terraform.json') 14 | assert res.status_code == 200 15 | assert res.json == { 16 | 'modules.v1': '/v1/modules/', 17 | 'providers.v1': '/v1/providers/' 18 | } 19 | 20 | def test_with_terraform_idc(self, client): 21 | """Test endpoint without query parameters""" 22 | with mock.patch('terrareg.terraform_idp.TerraformIdp.is_enabled', True): 23 | res = client.get('/.well-known/terraform.json') 24 | 25 | assert res.status_code == 200 26 | assert res.json == { 27 | 'modules.v1': '/v1/modules/', 28 | 'providers.v1': '/v1/providers/', 29 | 'login.v1': { 30 | 'authz': '/terraform/oauth/authorization', 31 | 'client': 'terraform-cli', 32 | 'grant_types': ['authz_code', 'token'], 33 | 'ports': [10000, 10010], 34 | 'token': '/terraform/oauth/token' 35 | } 36 | } 37 | 38 | def test_with_post(self, client): 39 | """Test invalid post request""" 40 | res = client.post('/.well-known/terraform.json') 41 | assert res.status_code == 405 42 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36 3 | 4 | [testenv] 5 | deps = pytest 6 | commands = pytest 7 | 8 | [pytest] 9 | junit_logging = all 10 | junit_log_passing_tests = true 11 | -------------------------------------------------------------------------------- /traefik/dynamic_conf.yaml: -------------------------------------------------------------------------------- 1 | tls: 2 | certificates: 3 | - certFile: "/etc/certs/local-cert.pem" 4 | keyFile: "/etc/certs/local-key.pem" -------------------------------------------------------------------------------- /traefik/traefik.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | sendAnonymousUsage: false 3 | 4 | api: 5 | dashboard: true 6 | insecure: true 7 | 8 | providers: 9 | docker: 10 | endpoint: tcp://docker-socket-proxy:2375 11 | watch: true 12 | exposedByDefault: false 13 | 14 | file: 15 | filename: /etc/traefik/dynamic_conf.yaml 16 | watch: true 17 | 18 | log: 19 | level: DEBUG 20 | format: common 21 | 22 | entryPoints: 23 | web: 24 | address: ':80' 25 | http: 26 | redirections: 27 | entryPoint: 28 | to: websecure 29 | scheme: https 30 | 31 | websecure: 32 | address: ':443' 33 | --------------------------------------------------------------------------------