├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── new-plugin-request.md
├── diagram.png
├── fabric.svg
├── styles
│ ├── .vale-config
│ │ ├── 1-Hugo.ini
│ │ └── 2-Hugo.ini
│ └── config
│ │ └── vocabularies
│ │ └── fabric-vocab
│ │ └── accept.txt
└── workflows
│ ├── deps.yml
│ ├── docs.yml
│ ├── install_tools
│ └── action.yml
│ ├── main.yml
│ ├── main_common.yml
│ ├── main_full.yml
│ ├── main_partial.yml
│ └── release.yml
├── .gitignore
├── .golangci.yaml
├── .goreleaser-dev.yaml
├── .goreleaser.yaml
├── .markdownlint.yaml
├── .mockery.yaml
├── .vale.ini
├── Dockerfile
├── LICENSE
├── README.md
├── buf.gen.yaml
├── buf.work.yaml
├── cmd
├── data.go
├── fabctx
│ ├── eval_ctx.go
│ ├── eval_ctx_test.go
│ └── fabctx.go
├── install.go
├── internal
│ ├── multilog
│ │ └── handler.go
│ └── telemetry
│ │ └── telemetry.go
├── lint.go
├── render.go
├── root.go
├── usage.go
├── validations.go
└── version.go
├── codegen
├── format.sh
├── gen.sh
├── gen_code.sh
├── gen_docs.sh
├── lint_protobuf.sh
├── rm_gen.sh
├── setup.sh
└── utils.sh
├── docs
├── _index.md
├── cli.md
├── install.md
├── language
│ ├── _index.md
│ ├── configs.md
│ ├── content-blocks.md
│ ├── context.md
│ ├── data-blocks.md
│ ├── documents.md
│ ├── dynamic-blocks.md
│ ├── hcl.md
│ ├── publish-blocks.md
│ ├── references.md
│ ├── section-blocks.md
│ ├── syntax.md
│ └── vscode-fabric-screenshot.png
├── plugins
│ ├── _index.md
│ ├── atlassian
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ └── jira_issues.md
│ ├── builtin
│ │ ├── _index.md
│ │ ├── content-providers
│ │ │ ├── blockquote.md
│ │ │ ├── code.md
│ │ │ ├── frontmatter.md
│ │ │ ├── image.md
│ │ │ ├── list.md
│ │ │ ├── sleep.md
│ │ │ ├── table.md
│ │ │ ├── text.md
│ │ │ ├── title.md
│ │ │ └── toc.md
│ │ ├── data-sources
│ │ │ ├── csv.md
│ │ │ ├── http.md
│ │ │ ├── json.md
│ │ │ ├── rss.md
│ │ │ ├── sleep.md
│ │ │ ├── txt.md
│ │ │ └── yaml.md
│ │ └── publishers
│ │ │ ├── hub.md
│ │ │ └── local_file.md
│ ├── content-providers.md
│ ├── crowdstrike
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ ├── falcon_cspm_ioms.md
│ │ │ ├── falcon_detection_details.md
│ │ │ ├── falcon_discover_host_details.md
│ │ │ ├── falcon_intel_indicators.md
│ │ │ └── falcon_vulnerabilities.md
│ ├── data-sources.md
│ ├── elastic
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ ├── elastic_security_cases.md
│ │ │ └── elasticsearch.md
│ ├── github
│ │ ├── _index.md
│ │ ├── data-sources
│ │ │ └── github_issues.md
│ │ └── publishers
│ │ │ └── github_gist.md
│ ├── graphql
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ └── graphql.md
│ ├── hackerone
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ └── hackerone_reports.md
│ ├── iris
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ ├── iris_alerts.md
│ │ │ └── iris_cases.md
│ ├── microsoft
│ │ ├── _index.md
│ │ ├── content-providers
│ │ │ └── azure_openai_text.md
│ │ └── data-sources
│ │ │ ├── microsoft_graph.md
│ │ │ ├── microsoft_security.md
│ │ │ ├── microsoft_security_query.md
│ │ │ └── microsoft_sentinel_incidents.md
│ ├── misp
│ │ ├── _index.md
│ │ ├── data-sources
│ │ │ └── misp_events.md
│ │ └── publishers
│ │ │ └── misp_event_reports.md
│ ├── nist_nvd
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ └── nist_nvd_cves.md
│ ├── openai
│ │ ├── _index.md
│ │ └── content-providers
│ │ │ └── openai_text.md
│ ├── opencti
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ └── opencti.md
│ ├── plugins.json
│ ├── postgresql
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ └── postgresql.md
│ ├── publishers.md
│ ├── snyk
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ └── snyk_issues.md
│ ├── splunk
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ └── splunk_search.md
│ ├── sqlite
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ └── sqlite.md
│ ├── stixview
│ │ ├── _index.md
│ │ └── content-providers
│ │ │ └── stixview.md
│ ├── terraform
│ │ ├── _index.md
│ │ └── data-sources
│ │ │ └── terraform_state_local.md
│ └── virustotal
│ │ ├── _index.md
│ │ └── data-sources
│ │ └── virustotal_api_usage.md
└── tutorial.md
├── engine
├── depends_on_ref_test.go
├── dynamic_test.go
├── engine.go
├── engine_test.go
├── engine_vars_test.go
├── fuzz_test.go
├── helpers_test.go
├── is_included_test.go
├── is_included_vars_test.go
├── options.go
├── tags_test.go
└── testdata
│ └── a.json
├── eval
├── content.go
├── document.go
├── dynamic.go
├── evaluator.go
├── plugin_action.go
├── plugin_content_action.go
├── plugin_data_action.go
├── plugin_publish_action.go
├── plugins.go
├── section.go
└── vars.go
├── examples
├── plugins
│ ├── README.md
│ └── basic
│ │ ├── cmd
│ │ └── main.go
│ │ ├── content_greeting.go
│ │ ├── data_random_numbers.go
│ │ └── plugin.go
└── templates
│ ├── async
│ └── example.fabric
│ ├── atlassian
│ └── example.fabric
│ ├── azureopenai
│ └── example.fabric
│ ├── basic_hello
│ └── hello.fabric
│ ├── crowdstrike
│ ├── data_falcon_cspm_ioms.fabric
│ ├── data_falcon_detection_details.fabric
│ ├── data_falcon_discover_host_details.fabric
│ ├── data_falcon_intel_indicators.fabric
│ └── data_falcon_vulnerabilities.fabric
│ ├── frontmatter
│ └── example.fabric
│ ├── github
│ └── publish_gist.fabric
│ ├── hackerone
│ └── example.fabric
│ ├── hub
│ └── example.fabric
│ ├── iris
│ └── example.fabric
│ ├── microsoft
│ └── graph_data_source.fabric
│ ├── misp
│ ├── misp_event_reports.fabric
│ └── misp_events.fabric
│ ├── nist_nvd
│ └── example.fabric
│ ├── openai
│ ├── data.csv
│ └── example.fabric
│ ├── publish
│ └── example.fabric
│ ├── sections
│ └── example.fabric
│ ├── stixview
│ └── example.fabric
│ └── virustotal
│ └── example.fabric
├── gen.go
├── go.mod
├── go.sum
├── internal
├── atlassian
│ ├── client
│ │ ├── client.go
│ │ ├── client_test.go
│ │ └── dto.go
│ ├── cmd
│ │ └── main.go
│ ├── data_jira_issues.go
│ ├── data_jira_issues_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── builtin
│ ├── content_blockquote.go
│ ├── content_blockquote_test.go
│ ├── content_code.go
│ ├── content_code_test.go
│ ├── content_frontmatter.go
│ ├── content_frontmatter_test.go
│ ├── content_helpers.go
│ ├── content_image.go
│ ├── content_image_test.go
│ ├── content_list.go
│ ├── content_list_test.go
│ ├── content_sleep.go
│ ├── content_sleep_test.go
│ ├── content_table.go
│ ├── content_table_test.go
│ ├── content_text.go
│ ├── content_text_test.go
│ ├── content_title.go
│ ├── content_title_test.go
│ ├── content_toc.go
│ ├── content_toc_test.go
│ ├── data_csv.go
│ ├── data_csv_test.go
│ ├── data_http.go
│ ├── data_http_test.go
│ ├── data_json.go
│ ├── data_json_test.go
│ ├── data_rss.go
│ ├── data_rss_test.go
│ ├── data_sleep.go
│ ├── data_sleep_test.go
│ ├── data_txt.go
│ ├── data_txt_test.go
│ ├── data_yaml.go
│ ├── data_yaml_test.go
│ ├── hubapi
│ │ ├── client.go
│ │ ├── client_test.go
│ │ └── dto.go
│ ├── plugin.go
│ ├── plugin_test.go
│ ├── publish_hub.go
│ ├── publish_hub_test.go
│ ├── publish_local_file.go
│ ├── publish_local_file_test.go
│ ├── testdata
│ │ ├── csv
│ │ │ ├── comma.csv
│ │ │ ├── empty.csv
│ │ │ ├── invalid.csv
│ │ │ └── semicolon.csv
│ │ ├── json
│ │ │ ├── a.json
│ │ │ ├── dir
│ │ │ │ ├── b.json
│ │ │ │ └── c.json
│ │ │ └── invalid.txt
│ │ ├── rss
│ │ │ ├── basic.atom
│ │ │ └── basic.rss
│ │ ├── txt
│ │ │ ├── data.txt
│ │ │ └── empty.txt
│ │ └── yaml
│ │ │ ├── a.yaml
│ │ │ ├── dir
│ │ │ ├── b.yaml
│ │ │ └── c.yaml
│ │ │ └── invalid.txt
│ └── utils
│ │ └── parsers.go
├── crowdstrike
│ ├── cmd
│ │ └── main.go
│ ├── data_falcon_cspm_ioms.go
│ ├── data_falcon_cspm_ioms_test.go
│ ├── data_falcon_detection_details.go
│ ├── data_falcon_detection_details_test.go
│ ├── data_falcon_discover_host_details.go
│ ├── data_falcon_discover_host_details_test.go
│ ├── data_falcon_intel_indicators.go
│ ├── data_falcon_intel_indicators_test.go
│ ├── data_falcon_vulnerabilities.go
│ ├── data_falcon_vulnerabilities_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── elastic
│ ├── cmd
│ │ └── main.go
│ ├── data_elastic_security_cases.go
│ ├── data_elastic_security_cases_test.go
│ ├── data_elasticsearch.go
│ ├── data_elasticsearch_test.go
│ ├── kbclient
│ │ ├── client.go
│ │ └── client_test.go
│ ├── plugin.go
│ ├── plugin_test.go
│ └── testdata
│ │ └── data.json
├── github
│ ├── cmd
│ │ └── main.go
│ ├── data_github_issues.go
│ ├── data_github_issues_test.go
│ ├── plugin.go
│ ├── plugin_test.go
│ ├── publish_github_gist.go
│ └── publish_github_gist_test.go
├── graphql
│ ├── cmd
│ │ └── main.go
│ ├── data_graphql.go
│ ├── data_graphql_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── hackerone
│ ├── client
│ │ ├── client.go
│ │ └── client_test.go
│ ├── cmd
│ │ └── main.go
│ ├── data_hackerone_reports.go
│ ├── data_hackerone_reports_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── iris
│ ├── client
│ │ ├── client.go
│ │ ├── client_test.go
│ │ └── dto.go
│ ├── cmd
│ │ └── main.go
│ ├── data_iris_alerts.go
│ ├── data_iris_alerts_test.go
│ ├── data_iris_cases.go
│ ├── data_iris_cases_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── microsoft
│ ├── client
│ │ ├── azure_client.go
│ │ ├── azure_client_test.go
│ │ ├── graph_client.go
│ │ ├── microsoftonline_client.go
│ │ └── security_client.go
│ ├── cmd
│ │ └── main.go
│ ├── content_azure_openai_text.go
│ ├── content_azure_openai_text_test.go
│ ├── data_microsoft_graph.go
│ ├── data_microsoft_graph_test.go
│ ├── data_microsoft_security.go
│ ├── data_microsoft_security_query.go
│ ├── data_microsoft_security_query_test.go
│ ├── data_microsoft_security_test.go
│ ├── data_microsoft_sentinel_incidents.go
│ ├── data_microsoft_sentinel_incidents_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── misp
│ ├── client
│ │ ├── misp_client.go
│ │ └── misp_models.go
│ ├── cmd
│ │ └── main.go
│ ├── data_misp_events.go
│ ├── data_misp_events_test.go
│ ├── plugin.go
│ ├── plugin_test.go
│ ├── publish_misp_event_reports.go
│ └── publish_misp_event_reports_test.go
├── nistnvd
│ ├── client
│ │ ├── client.go
│ │ └── client_test.go
│ ├── cmd
│ │ └── main.go
│ ├── data_nist_nvd_cves.go
│ ├── data_nist_nvd_cves_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── openai
│ ├── client
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── options.go
│ │ └── types.go
│ ├── cmd
│ │ └── main.go
│ ├── content_openai_text.go
│ ├── content_openai_text_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── opencti
│ ├── cmd
│ │ └── main.go
│ ├── data_opencti.go
│ ├── data_opencti_test.go
│ ├── opencti.graphql
│ ├── plugin.go
│ └── plugin_test.go
├── plugin_validity_test.go
├── postgresql
│ ├── cmd
│ │ └── main.go
│ ├── data_postgresql.go
│ ├── data_postgresql_test.go
│ ├── plugin.go
│ ├── plugin_test.go
│ └── testdata
│ │ └── data.sql
├── snyk
│ ├── client
│ │ ├── client.go
│ │ └── client_test.go
│ ├── cmd
│ │ └── main.go
│ ├── data_snyk_issues.go
│ ├── data_snyk_issues_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── splunk
│ ├── client
│ │ ├── client.go
│ │ ├── client_test.go
│ │ └── dto.go
│ ├── cmd
│ │ └── main.go
│ ├── data_splunk_search.go
│ ├── data_splunk_search_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── sqlite
│ ├── cmd
│ │ └── main.go
│ ├── data_sqlite.go
│ ├── data_sqlite_test.go
│ ├── helpers_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── stixview
│ ├── cmd
│ │ └── main.go
│ ├── content_stixview.go
│ ├── content_stixview_test.go
│ ├── plugin.go
│ ├── plugin_test.go
│ └── stixview.gohtml
├── terraform
│ ├── cmd
│ │ └── main.go
│ ├── data_terraform_state_local.go
│ ├── data_terraform_state_local_test.go
│ ├── plugin.go
│ ├── plugin_test.go
│ └── testdata
│ │ └── terraform.tfstate
└── virustotal
│ ├── client
│ ├── client.go
│ ├── client_test.go
│ └── dto.go
│ ├── cmd
│ └── main.go
│ ├── data_virustotal_api_usage.go
│ ├── data_virustotal_api_usage_test.go
│ ├── plugin.go
│ └── plugin_test.go
├── justfile
├── main.go
├── mocks
├── internalpkg
│ ├── atlassian
│ │ └── client
│ │ │ └── client.go
│ ├── builtin
│ │ └── hubapi
│ │ │ └── client.go
│ ├── crowdstrike
│ │ ├── client.go
│ │ ├── cspm_registration_client.go
│ │ ├── detects_client.go
│ │ ├── discover_client.go
│ │ ├── intel_client.go
│ │ └── spot_vulnerabilities_client.go
│ ├── elastic
│ │ └── kbclient
│ │ │ └── client.go
│ ├── github
│ │ ├── client.go
│ │ ├── gist_client.go
│ │ └── issues_client.go
│ ├── hackerone
│ │ └── client
│ │ │ └── client.go
│ ├── iris
│ │ └── client
│ │ │ └── client.go
│ ├── microsoft
│ │ ├── azure_client.go
│ │ ├── azure_open_ai_client.go
│ │ ├── microsoft_graph_client.go
│ │ └── microsoft_security_client.go
│ ├── misp
│ │ └── client.go
│ ├── nistnvd
│ │ └── client
│ │ │ └── client.go
│ ├── openai
│ │ └── client
│ │ │ └── client.go
│ ├── snyk
│ │ └── client
│ │ │ └── client.go
│ ├── splunk
│ │ └── client
│ │ │ └── client.go
│ └── virustotal
│ │ └── client
│ │ └── client.go
└── parser
│ └── definitions
│ └── fabric_block.go
├── parser
├── defined_blocks.go
├── defined_blocks_test.go
├── definitions
│ ├── config.go
│ ├── config_empty.go
│ ├── config_ptr.go
│ ├── definitions.go
│ ├── document.go
│ ├── dynamic.go
│ ├── global_config.go
│ ├── meta.go
│ ├── parsed_document.go
│ ├── parsed_plugin.go
│ ├── plugin.go
│ ├── section.go
│ ├── validators.go
│ └── vars.go
├── evaluation
│ ├── block_invocation.go
│ └── evaluation.go
├── parse_document.go
├── parse_dynamic.go
├── parse_plugin.go
├── parse_section.go
├── parse_title.go
├── parse_vars.go
├── parser.go
├── parser_test.go
└── traversal.go
├── pkg
├── circularRefDetector
│ ├── circularRefs.go
│ └── circularRefs_test.go
├── diagnostics
│ ├── diagnostics.go
│ ├── diagtest
│ │ ├── diagtest.go
│ │ └── diagtest_test.go
│ ├── extra.go
│ ├── gojq.go
│ ├── path.go
│ ├── printer.go
│ ├── refine.go
│ ├── repeated.go
│ └── traceback.go
├── encapsulator
│ ├── codec.go
│ ├── decoder.go
│ ├── encapsulator_test.go
│ ├── encoder.go
│ ├── typed_capsule_ops.go
│ ├── typed_capusle_opts_test.go
│ └── util.go
├── jsontools
│ └── jsontools.go
├── parexec
│ ├── limiter.go
│ ├── limiter_test.go
│ ├── parexec.go
│ ├── parexecRace_test.go
│ └── parexec_test.go
├── sloghclog
│ ├── sloghclog.go
│ └── sloghclog_test.go
└── utils
│ ├── golang.go
│ ├── hcl.go
│ ├── maps.go
│ ├── maps_test.go
│ ├── slices.go
│ ├── slices_test.go
│ ├── slogutil
│ └── slogutil.go
│ ├── string.go
│ └── string_test.go
├── plugin
├── ast
│ ├── astsrc
│ │ └── astsrc.go
│ ├── builder.go
│ ├── nodes
│ │ ├── content_node.go
│ │ ├── custom_nodes.go
│ │ └── metadata.go
│ └── v1
│ │ ├── ast.pb.go
│ │ ├── ast_decoder.go
│ │ ├── ast_encoder.go
│ │ ├── ast_test.go
│ │ ├── bubble_error.go
│ │ ├── enum_codec.go
│ │ ├── interfaces.go
│ │ └── testdata
│ │ └── fuzz
│ │ └── FuzzEncoder
│ │ └── 6b5e47038908d6af
├── content.go
├── content_provider.go
├── data_source.go
├── dataspec
│ ├── attr.go
│ ├── attr_spec.go
│ ├── attr_spec_doc_comment.gotmpl
│ ├── attr_spec_test.go
│ ├── block.go
│ ├── block_spec.go
│ ├── constraint
│ │ ├── constraint.go
│ │ └── constraint_test.go
│ ├── dataspec.go
│ ├── decode.go
│ ├── deferred
│ │ ├── deferred_eval.go
│ │ └── jq.go
│ ├── genutil.go
│ └── root_spec.go
├── errors.go
├── logging.go
├── pluginapi
│ └── v1
│ │ ├── client.go
│ │ ├── content.pb.go
│ │ ├── content_decoder.go
│ │ ├── content_encoder.go
│ │ ├── cty.pb.go
│ │ ├── cty_codec_test.go
│ │ ├── cty_decoder.go
│ │ ├── cty_encoder.go
│ │ ├── data.pb.go
│ │ ├── data_decoder.go
│ │ ├── data_encoder.go
│ │ ├── dataspec.pb.go
│ │ ├── dataspec_decoder.go
│ │ ├── dataspec_encoder.go
│ │ ├── hcl.pb.go
│ │ ├── hcl_decoder.go
│ │ ├── hcl_encoder.go
│ │ ├── is_not_plugin.go
│ │ ├── is_plugin.go
│ │ ├── plugin.go
│ │ ├── plugin.pb.go
│ │ ├── plugin_grpc.pb.go
│ │ ├── schema.pb.go
│ │ ├── schema_decoder.go
│ │ ├── schema_encoder.go
│ │ └── server.go
├── plugindata
│ ├── data.go
│ └── data_encapsulated.go
├── plugintest
│ ├── blocktest.go
│ └── plugintest.go
├── publisher.go
├── resolver
│ ├── checksum.go
│ ├── checksum_test.go
│ ├── lockfile.go
│ ├── lockfile_test.go
│ ├── mock_source_test.go
│ ├── name.go
│ ├── name_test.go
│ ├── options.go
│ ├── resolver.go
│ ├── resolver_test.go
│ ├── source.go
│ ├── source_local.go
│ ├── source_local_test.go
│ ├── source_remote.go
│ ├── source_remote_test.go
│ ├── version.go
│ └── version_test.go
├── runner
│ ├── loader.go
│ └── runner.go
├── schema.go
└── tracing.go
├── print
├── htmlprint
│ ├── document.gotempl
│ └── printer.go
├── logging.go
├── mdprint
│ └── printer.go
├── pdfprint
│ └── printer.go
├── printer.go
└── tracing.go
├── proto
├── ast
│ └── v1
│ │ └── ast.proto
├── buf.yaml
└── pluginapi
│ └── v1
│ ├── content.proto
│ ├── cty.proto
│ ├── data.proto
│ ├── dataspec.proto
│ ├── hcl.proto
│ ├── plugin.proto
│ └── schema.proto
└── tools
├── docgen
├── content-provider.md.gotempl
├── data-source.md.gotempl
├── main.go
├── plugin.md.gotempl
└── publisher.md.gotempl
└── pluginmeta
├── main.go
├── metadata.go
└── releaser_config.go
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.fabric linguist-language=hcl
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Description
11 | Provide a clear and concise description of the bug you are experiencing.
12 |
13 | ### Environment
14 | Fabric version: [Specify the version of `fabric` CLI where the bug was observed]
15 | Operating System: [Specify the operating system and version]
16 | Terminal/Shell: [Specify the terminal or shell environment]
17 |
18 | ### Steps to Reproduce
19 | Clearly outline the steps to reproduce the bug. Include any relevant commands, inputs, or configurations that lead to the issue. If possible, provide a minimal code example or a step-by-step guide.
20 |
21 | 1. [Step 1]
22 | 2. [Step 2]
23 | 3. [Step 3]
24 | 4. ...
25 |
26 | ### Expected Behavior
27 | Describe what you expected to happen when performing the steps outlined above. This will help us understand the intended functionality and identify deviations from it.
28 |
29 | ### Actual Behavior
30 | Describe what actually happened when performing the steps outlined above. Include any error messages or unexpected outcomes that occurred during the process.
31 |
32 | ### Additional Context
33 | Include any additional context that might be helpful in resolving the bug. This could include screenshots, configurations, or any recent changes made to the environment.
34 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/new-plugin-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: New plugin request
3 | about: Create a request for a new plugin
4 | title: ''
5 | labels: plugins
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Description
11 | Please provide a brief description of the functionality you would like to see implemented in the new plugin.
12 |
13 | ### Use Case
14 | Describe a real-world scenario or use case where this new plugin would be beneficial. This will help us understand the practical value and potential impact of the proposed plugin.
15 |
16 | ### Requirements
17 | List any specific requirements or features you expect from the new plugin. For example, input/output formats, compatibility with certain systems, or integration with other tools.
18 |
19 | ### Additional Information
20 | Include any other relevant information, such as links to documentation, examples, or external resources that may help in understanding the requirements for the new plugin.
21 |
--------------------------------------------------------------------------------
/.github/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blackstork-io/fabric/122a20956a140c0413d68c061e56e9825c199082/.github/diagram.png
--------------------------------------------------------------------------------
/.github/styles/.vale-config/1-Hugo.ini:
--------------------------------------------------------------------------------
1 | [*.md]
2 | # Exclude `{{< ... >}}`, `{{% ... %}}`, [Who]({{< ... >}})
3 | TokenIgnores = ({{[%<] .* [%>]}}.*?{{[%<] ?/.* [%>]}}), \
4 | (\[.+\]\({{< .+ >}}\)), \
5 | ({{[%<] .+ [%>]}})
6 |
7 | # Exclude `{{< myshortcode `This is some HTML, ... >}}`
8 | BlockIgnores = (?sm)^({{[%<] [^{]*? [%>]}})\n$, \
9 | (?s) *({{< highlight [^>]* ?>}}.*?{{< ?/ ?highlight >}}), \
10 | ({{[%<] .+ [%>]}})
11 |
--------------------------------------------------------------------------------
/.github/styles/.vale-config/2-Hugo.ini:
--------------------------------------------------------------------------------
1 | [*.md]
2 | # Exclude `{{< ... >}}`, `{{% ... %}}`, [Who]({{< ... >}})
3 | TokenIgnores = ({{[%<] .* [%>]}}.*?{{[%<] ?/.* [%>]}}), \
4 | (\[.+\]\({{< .+ >}}\)), \
5 | ({{[%<] .+ [%>]}})
6 |
7 | # Exclude `{{< myshortcode `This is some HTML, ... >}}`
8 | BlockIgnores = (?sm)^({{[%<] [^{]*? [%>]}})\n$, \
9 | (?s) *({{< highlight [^>]* ?>}}.*?{{< ?/ ?highlight >}}), \
10 | ({{[%<] .+ [%>]}})
11 |
--------------------------------------------------------------------------------
/.github/styles/config/vocabularies/fabric-vocab/accept.txt:
--------------------------------------------------------------------------------
1 | Fabric
2 | GitHub
3 | boolean
4 | namespace
5 | Sonoma
6 | API
7 | LLM
8 | Splunk
9 | Cyber
10 | CTI
11 | ASCII
12 | stdout
13 |
--------------------------------------------------------------------------------
/.github/workflows/deps.yml:
--------------------------------------------------------------------------------
1 | # https://github.com/actions/go-dependency-submission
2 | name: dependency-submission
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | main:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: actions/setup-go@v4
17 | with:
18 | go-version: "1.22.x"
19 | - uses: actions/go-dependency-submission@v2
20 | with:
21 | go-mod-path: go.mod
22 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: docs
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths:
8 | - '**.md'
9 | pull_request:
10 | paths:
11 | - '**.md'
12 | workflow_dispatch:
13 |
14 | concurrency:
15 | group: docs-${{ github.head_ref || github.run_id }}
16 | cancel-in-progress: true
17 |
18 | jobs:
19 | # https://github.com/igorshubovych/markdownlint-cli
20 | markdownlint-cli:
21 | if: github.event_name != 'pull_request' || !github.event.pull_request.draft
22 | runs-on: ubuntu-latest
23 | steps:
24 | - uses: actions/checkout@v4
25 | - uses: nosborn/github-action-markdown-cli@v3.3.0
26 | with:
27 | files: '**/*.md'
28 | config_file: .markdownlint.yaml
29 | dot: true
30 |
31 | # https://github.com/errata-ai/vale-action
32 | vale:
33 | if: github.event_name != 'pull_request' || !github.event.pull_request.draft
34 | runs-on: ubuntu-latest
35 | steps:
36 | - uses: actions/checkout@v4
37 | - uses: errata-ai/vale-action@reviewdog
38 | with:
39 | fail_on_error: true
40 | vale_flags: "--glob='**/*.md'"
41 |
42 |
--------------------------------------------------------------------------------
/.github/workflows/install_tools/action.yml:
--------------------------------------------------------------------------------
1 | name: Install code generation tools
2 |
3 | description: |
4 | Installs code generation tools, linters, etc
5 | Expects go to be installed
6 |
7 | runs:
8 | using: "composite"
9 | steps:
10 | - name: Get installed go version
11 | run: echo "GOVERSION=$(go env GOVERSION)" >> "$GITHUB_ENV"
12 | shell: bash
13 | - name: Restore cached codegen tools
14 | id: cache-tools
15 | uses: actions/cache/restore@v4
16 | with:
17 | key: codegen-tools-${{ runner.os }}-${{ env.GOVERSION }}-${{ hashFiles('./codegen/setup.sh') }}
18 | path: ~/go/bin/
19 | - name: Setup tools
20 | if: steps.cache-tools.outputs.cache-hit != 'true'
21 | run: ./codegen/setup.sh
22 | shell: bash
23 |
24 | - name: Cache codegen tools
25 | if: steps.cache-tools.outputs.cache-hit != 'true'
26 | uses: actions/cache/save@v4
27 | with:
28 | key: ${{ steps.cache-tools.outputs.cache-primary-key }}
29 | path: ~/go/bin/
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: main
2 | # PR checks
3 |
4 | on:
5 | push:
6 | branches:
7 | - main
8 | pull_request:
9 | types:
10 | - opened
11 | - synchronize
12 | - reopened
13 | - ready_for_review
14 | pull_request_review:
15 | types: [submitted]
16 |
17 | workflow_dispatch:
18 |
19 | concurrency:
20 | group: main-${{ github.head_ref || github.sha }}
21 | cancel-in-progress: true
22 |
23 | jobs:
24 | partial_checks:
25 | if: github.event_name == 'pull_request' && !github.event.pull_request.draft
26 | uses: ./.github/workflows/main_partial.yml
27 |
28 | full_checks:
29 | if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request_review' && github.event.review.state == 'approved'
30 | uses: ./.github/workflows/main_full.yml
31 |
32 | common_checks:
33 | if: github.event_name != 'pull_request' || !github.event.pull_request.draft
34 |
35 | permissions:
36 | # For golangci-lint
37 | contents: read
38 | pull-requests: read
39 | checks: write
40 | uses: ./.github/workflows/main_common.yml
41 |
--------------------------------------------------------------------------------
/.github/workflows/main_full.yml:
--------------------------------------------------------------------------------
1 | name: Full PR checks
2 |
3 | on:
4 | workflow_call:
5 |
6 | jobs:
7 | tests:
8 | strategy:
9 | fail-fast: false
10 | matrix:
11 | go-version: ["stable", "oldstable"]
12 | os: [ubuntu-latest, macos-latest, windows-latest]
13 | runs-on: ${{ matrix.os }}
14 | steps:
15 | - run: git config --global core.autocrlf input
16 | - uses: actions/checkout@v4
17 | - uses: actions/setup-go@v5
18 | with:
19 | go-version: ${{ matrix.go-version }}
20 | # Run integration tests (testcontainers) on ubuntu
21 | - run: go test -race -v -timeout 2m ${{ !startsWith(matrix.os, 'ubuntu') && '-short' || '' }} ./...
22 |
23 | builds:
24 | strategy:
25 | matrix:
26 | go-version: ["stable", "oldstable"]
27 | os: [ubuntu-latest, macos-latest, windows-latest]
28 | runs-on: ${{ matrix.os }}
29 | steps:
30 | - run: git config --global core.autocrlf input
31 | - uses: actions/checkout@v4
32 | - uses: actions/setup-go@v5
33 | with:
34 | go-version: ${{ matrix.go-version }}
35 | - name: Build fabric and plugins
36 | uses: goreleaser/goreleaser-action@v6
37 | with:
38 | distribution: goreleaser
39 | version: '~> v2'
40 | args: build --snapshot --clean --single-target
41 |
--------------------------------------------------------------------------------
/.github/workflows/main_partial.yml:
--------------------------------------------------------------------------------
1 | name: Partial PR checks
2 |
3 | on:
4 | workflow_call:
5 |
6 | jobs:
7 | tests:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 | - uses: actions/setup-go@v5
12 | with:
13 | go-version: "stable"
14 | - run: go test -race -v -timeout 10s -short ./...
15 |
16 | builds:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v4
20 | - uses: actions/setup-go@v5
21 | with:
22 | go-version: "stable"
23 | - name: Build fabric and plugins
24 | uses: goreleaser/goreleaser-action@v6
25 | with:
26 | distribution: goreleaser
27 | version: '~> v2'
28 | args: build --snapshot --clean --single-target
29 |
30 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | release:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v4
17 | with:
18 | fetch-depth: 0
19 | - name: Set up Go
20 | uses: actions/setup-go@v5
21 | with:
22 | go-version: stable
23 | - name: Login to Docker Hub
24 | uses: docker/login-action@v3
25 | with:
26 | # TODO: setup creds
27 | username: "${{ secrets.DOCKERHUB_USERNAME }}"
28 | password: "${{ secrets.DOCKERHUB_TOKEN }}"
29 | - name: Run GoReleaser
30 | uses: goreleaser/goreleaser-action@v6
31 | with:
32 | distribution: goreleaser
33 | version: '~> v2'
34 | args: release --clean
35 | env:
36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37 | CONTAINER_REPO_NAME: "blackstorkio/fabric"
38 | TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Editor directories
5 | .vscode/
6 | # Binaries for programs and plugins
7 | bin/*
8 | *.exe
9 | *.exe~
10 | *.dll
11 | *.so
12 | *.dylib
13 | .DS_Store
14 |
15 | # Vale will install modules in `.vale/style` and those should not be in version control
16 | .github/styles/*
17 | !.github/styles/config
18 |
19 | # Test binary, built with `go test -c`
20 | *.test
21 |
22 | # Output of the go coverage tool, specifically when used with LiteIDE
23 | *.out
24 |
25 | # Dependency directories (remove the comment below to include it)
26 | vendor/
27 |
28 | # Go workspace file
29 | go.work
30 |
31 | dist/
32 | .tmp
33 | .fabric
34 | .fabric-lock.json
35 |
--------------------------------------------------------------------------------
/.markdownlint.yaml:
--------------------------------------------------------------------------------
1 | # https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml
2 | default: true # enable all by default
3 | MD007: # unordered list indentation
4 | indent: 2
5 | MD013: false # do not validate line length
6 | MD014: false # allow $ before command output
7 | MD024: false # allow duplicate headings, for example, "Example"
8 | MD029: # ordered list prefix, use all ones
9 | style: "one"
10 | MD033: false # allow inline HTML
11 | MD041: false # the first line is not always H1
12 | MD025: false # because it thinks that is there is frontmatter, there should be no H1
13 |
--------------------------------------------------------------------------------
/.vale.ini:
--------------------------------------------------------------------------------
1 | MinAlertLevel = suggestion
2 | Packages = Microsoft, Hugo
3 | StylesPath = .github/styles
4 | Vocab = fabric-vocab
5 |
6 | [*.md]
7 | BasedOnStyles = Vale, Microsoft
8 |
9 | # Style.Rule = {YES, NO, suggestion, warning, error} to
10 |
11 | # Disable a requirement of no spaces around dashes
12 | Microsoft.Dashes = NO
13 |
14 | # Allow CLI in the headings
15 | Microsoft.HeadingAcronyms = NO
16 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | FROM alpine:3.19
4 | ENTRYPOINT [ "/fabric" ]
5 | CMD [ "--help" ]
6 | COPY fabric /fabric
--------------------------------------------------------------------------------
/buf.gen.yaml:
--------------------------------------------------------------------------------
1 | version: v1
2 | managed:
3 | enabled: true
4 | go_package_prefix:
5 | default: github.com/blackstork-io/fabric/plugin
6 | plugins:
7 | - plugin: buf.build/protocolbuffers/go
8 | out: .
9 | opt: module=github.com/blackstork-io/fabric
10 | - plugin: buf.build/grpc/go
11 | out: .
12 | opt: module=github.com/blackstork-io/fabric
--------------------------------------------------------------------------------
/buf.work.yaml:
--------------------------------------------------------------------------------
1 | version: v1
2 | directories:
3 | - proto/
4 |
--------------------------------------------------------------------------------
/cmd/fabctx/eval_ctx_test.go:
--------------------------------------------------------------------------------
1 | package fabctx
2 |
3 | import (
4 | "os"
5 | "path"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/zclconf/go-cty/cty"
10 | )
11 |
12 | func Test_EnvVars(t *testing.T) {
13 | assert := assert.New(t)
14 | t.Setenv("TEST_KEY", "test_value")
15 | evalCtx := newEvalContext()
16 | env := evalCtx.Variables["env"]
17 | assert.NotNil(env)
18 | assert.True(cty.Map(cty.String).Equals(env.Type()))
19 | envMap := env.AsValueMap()
20 | assert.True(envMap["NON_EXISTENT_KEY"].IsNull())
21 | assert.False(envMap["TEST_KEY"].IsNull())
22 | assert.Equal("test_value", envMap["TEST_KEY"].AsString())
23 | }
24 |
25 | func TestFromFileFunc(t *testing.T) {
26 | const fileContents = "test file contents"
27 | assert := assert.New(t)
28 | tmp := t.TempDir()
29 | tmpPath := path.Join(tmp, "test")
30 | os.WriteFile(tmpPath, []byte(fileContents), 0o600)
31 | val, err := fromFileFunc.Call([]cty.Value{cty.StringVal(tmpPath)})
32 | assert.NoError(err)
33 | assert.Equal(fileContents, val.AsString())
34 | }
35 |
36 | func TestFuncsPresent(t *testing.T) {
37 | assert := assert.New(t)
38 | evalCtx := newEvalContext()
39 | assert.Contains(evalCtx.Functions, "from_file")
40 | assert.Contains(evalCtx.Functions, "join")
41 | }
42 |
--------------------------------------------------------------------------------
/cmd/install.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "log/slog"
5 |
6 | "github.com/spf13/cobra"
7 |
8 | "github.com/blackstork-io/fabric/engine"
9 | "github.com/blackstork-io/fabric/internal/builtin"
10 | "github.com/blackstork-io/fabric/pkg/diagnostics"
11 | )
12 |
13 | var installUpgrade bool
14 |
15 | func init() {
16 | rootCmd.AddCommand(installCmd)
17 | installCmd.Flags().BoolVarP(&installUpgrade, "upgrade", "u", false, "Upgrade plugin versions")
18 | }
19 |
20 | var installCmd = &cobra.Command{
21 | Use: "install",
22 | Short: "Install plugins",
23 | Long: "Install Fabric plugins",
24 | RunE: func(cmd *cobra.Command, args []string) (err error) {
25 | ctx := cmd.Context()
26 | var diags diagnostics.Diag
27 | eng := engine.New(
28 | engine.WithLogger(slog.Default()),
29 | engine.WithTracer(tracer),
30 | engine.WithBuiltIn(builtin.Plugin(version, slog.Default(), tracer)),
31 | )
32 | defer func() {
33 | err = exitCommand(eng, cmd, diags)
34 | }()
35 | diag := eng.ParseDir(ctx, cliArgs.sourceDir)
36 | if diags.Extend(diag) {
37 | return
38 | }
39 | diag = eng.LoadPluginResolver(ctx, true)
40 | if diags.Extend(diag) {
41 | return
42 | }
43 | diag = eng.Install(ctx, installUpgrade)
44 | if diags.Extend(diag) {
45 | return
46 | }
47 | return
48 | },
49 | }
50 |
--------------------------------------------------------------------------------
/cmd/internal/multilog/handler.go:
--------------------------------------------------------------------------------
1 | package multilog
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "log/slog"
7 | )
8 |
9 | // Handler is a log handler that forwards log records to multiple handlers.
10 | // It is useful when you want to log to multiple destinations, e.g. console and opentelemetry.
11 | type Handler struct {
12 | Level slog.Level
13 | Handlers []slog.Handler
14 | }
15 |
16 | // Enabled returns true if the log level is enabled.
17 | func (multi Handler) Enabled(ctx context.Context, level slog.Level) bool {
18 | return multi.Level <= level
19 | }
20 |
21 | // Handle forwards the log record to all handlers.
22 | func (multi Handler) Handle(ctx context.Context, record slog.Record) error {
23 | var errs []error
24 | for _, handler := range multi.Handlers {
25 | err := handler.Handle(ctx, record)
26 | if err != nil {
27 | errs = append(errs, err)
28 | }
29 | }
30 | return errors.Join(errs...)
31 | }
32 |
33 | // WithAttrs returns a new Handler with the given attributes.
34 | func (multi Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
35 | handlers := make([]slog.Handler, len(multi.Handlers))
36 | for i, handler := range multi.Handlers {
37 | handlers[i] = handler.WithAttrs(attrs)
38 | }
39 | multi.Handlers = handlers
40 | return multi
41 | }
42 |
43 | // WithGroup returns a new Handler with the given group name.
44 | func (multi Handler) WithGroup(name string) slog.Handler {
45 | handlers := make([]slog.Handler, len(multi.Handlers))
46 | for i, handler := range multi.Handlers {
47 | handlers[i] = handler.WithGroup(name)
48 | }
49 | multi.Handlers = handlers
50 | return multi
51 | }
52 |
--------------------------------------------------------------------------------
/cmd/lint.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "log/slog"
5 |
6 | "github.com/spf13/cobra"
7 |
8 | "github.com/blackstork-io/fabric/engine"
9 | "github.com/blackstork-io/fabric/internal/builtin"
10 | "github.com/blackstork-io/fabric/pkg/diagnostics"
11 | )
12 |
13 | var fullLint bool
14 |
15 | func init() {
16 | lintCmd.Flags().BoolVar(&fullLint, "full", false, "Lint plugin bodies (requires plugins to be installed)")
17 | rootCmd.AddCommand(lintCmd)
18 | }
19 |
20 | var lintCmd = &cobra.Command{
21 | Use: "lint",
22 | Short: "Evaluate *.fabric files for syntax mistakes",
23 | Long: `Doesn't call plugins, only checks the *.fabric templates for correctness`,
24 | RunE: func(cmd *cobra.Command, _ []string) (err error) {
25 | ctx := cmd.Context()
26 | var diags diagnostics.Diag
27 | eng := engine.New(
28 | engine.WithLogger(slog.Default()),
29 | engine.WithTracer(tracer),
30 | engine.WithBuiltIn(builtin.Plugin(version, slog.Default(), tracer)),
31 | )
32 | defer func() {
33 | err = exitCommand(eng, cmd, diags)
34 | }()
35 | diag := eng.ParseDir(ctx, cliArgs.sourceDir)
36 | if diags.Extend(diag) {
37 | return
38 | }
39 | if fullLint {
40 | if diags.Extend(eng.LoadPluginResolver(ctx, false)) {
41 | return
42 | }
43 | if diags.Extend(eng.LoadPluginRunner(ctx)) {
44 | return
45 | }
46 | }
47 | diag = eng.Lint(ctx, fullLint)
48 | diags.Extend(diag)
49 | return
50 | },
51 | }
52 |
--------------------------------------------------------------------------------
/cmd/validations.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "log/slog"
7 | "os"
8 | "slices"
9 | "strings"
10 |
11 | "github.com/blackstork-io/fabric/pkg/utils"
12 | )
13 |
14 | type logLevels struct {
15 | Names []string
16 | Vals []slog.Level
17 | }
18 |
19 | func (ll *logLevels) Find(name string) (level slog.Level, err error) {
20 | nameKey := strings.ToLower(strings.TrimSpace(rawArgs.logLevel))
21 | idx := slices.Index(ll.Names, nameKey)
22 | if idx == -1 {
23 | err = fmt.Errorf("unknown log level '%s'", name)
24 | return
25 | }
26 | return ll.Vals[idx], nil
27 | }
28 |
29 | func (ll *logLevels) String() string {
30 | return utils.JoinSurround(", ", "'", ll.Names...)
31 | }
32 |
33 | var validLogLevels = logLevels{
34 | Names: []string{
35 | "debug",
36 | "info",
37 | "warn",
38 | "error",
39 | },
40 | Vals: []slog.Level{
41 | slog.LevelDebug,
42 | slog.LevelInfo,
43 | slog.LevelWarn,
44 | slog.LevelError,
45 | },
46 | }
47 |
48 | func validateDir(what, dir string) error {
49 | info, err := os.Stat(dir)
50 | switch {
51 | case err == nil:
52 | case errors.Is(err, os.ErrNotExist):
53 | return fmt.Errorf("failed to open %s: path '%s' doesn't exist", what, dir)
54 | case errors.Is(err, os.ErrPermission):
55 | return fmt.Errorf("failed to open %s: permission to access path '%s' denied", what, dir)
56 | default:
57 | return fmt.Errorf("failed to open %s: path '%s': %w", what, dir, err)
58 | }
59 | if !info.IsDir() {
60 | return fmt.Errorf("failed to open %s: path '%s' is not a directory", what, dir)
61 | }
62 | return nil
63 | }
64 |
--------------------------------------------------------------------------------
/cmd/version.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "runtime/debug"
6 | "slices"
7 | "strings"
8 | )
9 |
10 | // Overridden by goreleaser.
11 | var (
12 | version = ""
13 | builtBy = "golang"
14 | )
15 |
16 | func init() {
17 | if builtBy != "goreleaser" {
18 | version = fmt.Sprintf(
19 | "%s+builtBy.%s",
20 | versionFromBuildInfo(),
21 | builtBy,
22 | )
23 | }
24 | // Version needs to be set here to the command instead of where rootCmd is defined
25 | // because the version is set after the rootCmd is defined. Else, the version
26 | // will be empty and the command will not show the version.
27 | rootCmd.Version = version
28 | }
29 |
30 | func versionFromBuildInfo() (result string) {
31 | result = "v0.0.0-dev"
32 | info, ok := debug.ReadBuildInfo()
33 | if !ok || info == nil {
34 | return
35 | }
36 | if info.Main.Version != "(devel)" {
37 | result = strings.ToLower(info.Main.Version)
38 | if !strings.HasPrefix(result, "v") {
39 | result = "v" + result
40 | }
41 | return
42 | }
43 | var meta []string
44 | // It's a dev version not built by goreleaser, add extra info
45 | dirtyIdx := slices.IndexFunc(info.Settings, func(s debug.BuildSetting) bool {
46 | return s.Key == "vcs.modified"
47 | })
48 | if dirtyIdx != -1 && info.Settings[dirtyIdx].Value == "true" {
49 | meta = append(meta, "dirty")
50 | }
51 |
52 | shaIdx := slices.IndexFunc(info.Settings, func(s debug.BuildSetting) bool {
53 | return s.Key == "vcs.revision"
54 | })
55 | if shaIdx != -1 {
56 | meta = append(meta, "rev", info.Settings[shaIdx].Value)
57 | }
58 | if len(meta) != 0 {
59 | result += "+" + strings.Join(meta, ".")
60 | }
61 | return
62 | }
63 |
--------------------------------------------------------------------------------
/codegen/format.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Formats the code
3 | set -e
4 | cd "$(dirname "${BASH_SOURCE[0]:-$0}")"
5 | source ./setup.sh
6 | cd ..
7 |
8 | gofumpt -w .
9 | gci write --skip-generated -s standard -s default -s "prefix(github.com/blackstork-io/fabric)" .
10 |
11 |
--------------------------------------------------------------------------------
/codegen/gen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Runs codegen and docgen tools
3 | set -e
4 | cd "$(dirname "${BASH_SOURCE[0]:-$0}")"
5 |
6 | ./gen_code.sh
7 | ./gen_docs.sh
8 |
--------------------------------------------------------------------------------
/codegen/gen_code.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Runs codegen tools
3 | set -e
4 | cd "$(dirname "${BASH_SOURCE[0]:-$0}")"
5 | source ./setup.sh
6 | cd ..
7 |
8 | buf generate
9 | mockery
10 | go mod tidy
--------------------------------------------------------------------------------
/codegen/gen_docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Runs codegen tools
3 | set -e
4 | cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.."
5 |
6 | go run ./tools/docgen --version $(git tag -l --sort=-creatordate | head -n1) --output ./docs/plugins
--------------------------------------------------------------------------------
/codegen/lint_protobuf.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Runs protobuf linter
3 | set -e
4 | cd "$(dirname "${BASH_SOURCE[0]:-$0}")"
5 | source ./setup.sh
6 | cd ..
7 |
8 | if is_ci; then
9 | buf lint --error-format=github-actions
10 | else
11 | buf lint
12 | fi
--------------------------------------------------------------------------------
/codegen/rm_gen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Removes all generated files
3 |
4 | cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.."
5 |
6 | grep -R --with-filename --files-with-matches --no-messages --include "*.go" -E -e '^// Code generated .* DO NOT EDIT.$' . | xargs rm
7 | find ./mocks -type d -empty -exec rmdir {} +
8 |
9 | find ./docs/plugins -mindepth 1 \( -maxdepth 1 -type d -o -not -name "*.md" \) -exec rm -rf {} +
10 |
--------------------------------------------------------------------------------
/codegen/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Make codegen tools available
3 | set -e
4 | source "$(dirname "${BASH_SOURCE[0]:-$0}")/utils.sh"
5 | # Codegen tools
6 | install_tool mockery github.com/vektra/mockery/v2 "2.52.2"
7 | install_tool buf github.com/bufbuild/buf/cmd/buf "1.32.2"
8 | # Formatting tools
9 | install_tool gci github.com/daixiang0/gci "0.13.4"
10 | install_tool gofumpt mvdan.cc/gofumpt "0.6.0"
11 | wait
12 |
--------------------------------------------------------------------------------
/codegen/utils.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Collection of tools
3 | function is_ci() {
4 | [ -n "$CI" ] && [ -n "$GITHUB_ACTIONS" ]
5 | }
6 |
7 | function install_tool() {
8 | local binary="$1"
9 | local path="$2"
10 | local version="$3"
11 |
12 | if $binary --version 2> /dev/null | grep -q "$version"; then
13 | # binary is already installed and has the correct version
14 | return
15 | fi
16 | if is_ci; then
17 | go install $path@v$version &
18 | return
19 | fi
20 | # avoid installing the binary into global scope
21 | # (perhaps developer has another version of the binary or does not want to install it)
22 | eval "function $binary() { go run \"$path@v$version\" \"\$@\"; }"
23 | }
--------------------------------------------------------------------------------
/docs/language/vscode-fabric-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blackstork-io/fabric/122a20956a140c0413d68c061e56e9825c199082/docs/language/vscode-fabric-screenshot.png
--------------------------------------------------------------------------------
/docs/plugins/atlassian/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/atlassian
3 | weight: 20
4 | plugin:
5 | name: blackstork/atlassian
6 | description: "The `atlassian` plugin for Atlassian Cloud."
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/atlassian/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/atlassian" "atlassian" "v0.4.2" >}}
15 |
16 | ## Description
17 | The `atlassian` plugin for Atlassian Cloud.
18 |
19 | ## Installation
20 |
21 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
22 |
23 | ```hcl
24 | fabric {
25 | plugin_versions = {
26 | "blackstork/atlassian" = ">= v0.4.2"
27 | }
28 | }
29 | ```
30 |
31 |
32 | ## Data sources
33 |
34 | {{< plugin-resources "atlassian" "data-source" >}}
35 |
--------------------------------------------------------------------------------
/docs/plugins/builtin/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Built-in
3 | weight: 10
4 | plugin:
5 | name: blackstork/builtin
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/builtin" "builtin" "v0.4.2" >}}
15 |
16 | `fabric` binary includes a set of built-in data sources and content providers, available out-of-the-box.
17 |
18 |
19 | ## Data sources
20 |
21 | {{< plugin-resources "builtin" "data-source" >}}
22 |
23 | ## Content providers
24 |
25 | {{< plugin-resources "builtin" "content-provider" >}}
26 | ## Publishers
27 |
28 | {{< plugin-resources "builtin" "publisher" >}}
--------------------------------------------------------------------------------
/docs/plugins/builtin/content-providers/blockquote.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`blockquote` content provider"
3 | plugin:
4 | name: blackstork/builtin
5 | description: "Formats text as a block quote"
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
9 | resource:
10 | type: content-provider
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "blockquote" "content provider" >}}
17 |
18 | ## Description
19 | Formats text as a block quote
20 |
21 | The content provider is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.
22 |
23 |
24 | #### Configuration
25 |
26 | The content provider doesn't support any configuration arguments.
27 |
28 | #### Usage
29 |
30 | The content provider supports the following execution arguments:
31 |
32 | ```hcl
33 | content blockquote {
34 | # Required string.
35 | #
36 | # For example:
37 | value = "Text to be formatted as a quote"
38 | }
39 | ```
40 |
41 |
--------------------------------------------------------------------------------
/docs/plugins/builtin/content-providers/code.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`code` content provider"
3 | plugin:
4 | name: blackstork/builtin
5 | description: "Formats text as code snippet"
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
9 | resource:
10 | type: content-provider
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "code" "content provider" >}}
17 |
18 | ## Description
19 | Formats text as code snippet
20 |
21 | The content provider is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.
22 |
23 |
24 | #### Configuration
25 |
26 | The content provider doesn't support any configuration arguments.
27 |
28 | #### Usage
29 |
30 | The content provider supports the following execution arguments:
31 |
32 | ```hcl
33 | content code {
34 | # Required string.
35 | #
36 | # For example:
37 | value = "Text to be formatted as a code block"
38 |
39 | # Specifiy the language for syntax highlighting
40 | #
41 | # Optional string.
42 | #
43 | # For example:
44 | # language = "python3"
45 | #
46 | # Default value:
47 | language = ""
48 | }
49 | ```
50 |
51 |
--------------------------------------------------------------------------------
/docs/plugins/builtin/content-providers/frontmatter.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`frontmatter` content provider"
3 | plugin:
4 | name: blackstork/builtin
5 | description: "Produces the frontmatter"
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
9 | resource:
10 | type: content-provider
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "frontmatter" "content provider" >}}
17 |
18 | ## Description
19 | Produces the frontmatter.
20 |
21 | The content provider is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.
22 |
23 |
24 | #### Configuration
25 |
26 | The content provider doesn't support any configuration arguments.
27 |
28 | #### Usage
29 |
30 | The content provider supports the following execution arguments:
31 |
32 | ```hcl
33 | content frontmatter {
34 | # Format of the frontmatter.
35 | #
36 | # Optional string.
37 | # Must be one of: "yaml", "toml", "json"
38 | # Default value:
39 | format = "yaml"
40 |
41 | # Arbitrary key-value map to be put in the frontmatter.
42 | #
43 | # Required jq queriable.
44 | # Must be non-empty
45 | #
46 | # For example:
47 | content = {
48 | key = "arbitrary value"
49 | key2 = {
50 | "can be nested" = 42
51 | }
52 | }
53 | }
54 | ```
55 |
56 |
--------------------------------------------------------------------------------
/docs/plugins/builtin/content-providers/image.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`image` content provider"
3 | plugin:
4 | name: blackstork/builtin
5 | description: "Returns an image tag"
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
9 | resource:
10 | type: content-provider
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "image" "content provider" >}}
17 |
18 | ## Description
19 | Returns an image tag
20 |
21 | The content provider is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.
22 |
23 |
24 | #### Configuration
25 |
26 | The content provider doesn't support any configuration arguments.
27 |
28 | #### Usage
29 |
30 | The content provider supports the following execution arguments:
31 |
32 | ```hcl
33 | content image {
34 | # Required string.
35 | # Must be non-empty
36 | #
37 | # For example:
38 | src = "https://example.com/img.png"
39 |
40 | # Optional string.
41 | #
42 | # For example:
43 | # alt = "Text description of the image"
44 | #
45 | # Default value:
46 | alt = null
47 | }
48 | ```
49 |
50 |
--------------------------------------------------------------------------------
/docs/plugins/builtin/content-providers/list.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`list` content provider"
3 | plugin:
4 | name: blackstork/builtin
5 | description: "Produces a list of items"
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
9 | resource:
10 | type: content-provider
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "list" "content provider" >}}
17 |
18 | ## Description
19 | Produces a list of items
20 |
21 | The content provider is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.
22 |
23 |
24 | #### Configuration
25 |
26 | The content provider doesn't support any configuration arguments.
27 |
28 | #### Usage
29 |
30 | The content provider supports the following execution arguments:
31 |
32 | ```hcl
33 | content list {
34 | # Go template for the item of the list
35 | #
36 | # Optional string.
37 | #
38 | # For example:
39 | # item_template = "[{{.Title}}]({{.URL}})"
40 | #
41 | # Default value:
42 | item_template = "{{.}}"
43 |
44 | # Optional string.
45 | # Must be one of: "unordered", "ordered", "tasklist"
46 | # Default value:
47 | format = "unordered"
48 |
49 | # List of items to render.
50 | #
51 | # Required list of jq queriable.
52 | # Must be non-empty
53 | #
54 | # For example:
55 | items = ["First item", "Second item", "Third item"]
56 | }
57 | ```
58 |
59 |
--------------------------------------------------------------------------------
/docs/plugins/builtin/content-providers/sleep.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`sleep` content provider"
3 | plugin:
4 | name: blackstork/builtin
5 | description: "Sleeps for the specified duration. Useful for testing and debugging"
6 | tags: ["debug"]
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
9 | resource:
10 | type: content-provider
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "sleep" "content provider" >}}
17 |
18 | ## Description
19 |
20 | Sleeps for the specified duration. Useful for testing and debugging.
21 |
22 |
23 | The content provider is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.
24 |
25 |
26 | #### Configuration
27 |
28 | The content provider doesn't support any configuration arguments.
29 |
30 | #### Usage
31 |
32 | The content provider supports the following execution arguments:
33 |
34 | ```hcl
35 | content sleep {
36 | # Duration to sleep
37 | #
38 | # Optional string.
39 | # Must be non-empty
40 | # Default value:
41 | duration = "1s"
42 | }
43 | ```
44 |
45 |
--------------------------------------------------------------------------------
/docs/plugins/builtin/content-providers/text.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`text` content provider"
3 | plugin:
4 | name: blackstork/builtin
5 | description: "Renders text"
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
9 | resource:
10 | type: content-provider
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "text" "content provider" >}}
17 |
18 | ## Description
19 | Renders text
20 |
21 | The content provider is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.
22 |
23 |
24 | #### Configuration
25 |
26 | The content provider doesn't support any configuration arguments.
27 |
28 | #### Usage
29 |
30 | The content provider supports the following execution arguments:
31 |
32 | ```hcl
33 | content text {
34 | # A string to render. Can use go template syntax.
35 | #
36 | # Required string.
37 | #
38 | # For example:
39 | value = "Hello world!"
40 | }
41 | ```
42 |
43 |
--------------------------------------------------------------------------------
/docs/plugins/builtin/content-providers/title.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`title` content provider"
3 | plugin:
4 | name: blackstork/builtin
5 | description: "Produces a title"
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
9 | resource:
10 | type: content-provider
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "title" "content provider" >}}
17 |
18 | ## Description
19 |
20 | Produces a title.
21 |
22 | The title size after calculations must be in an interval [0; 5] inclusive, where 0
23 | corresponds to the largest size (`
`) and 5 corresponds to (``)
24 |
25 |
26 | The content provider is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.
27 |
28 |
29 | #### Configuration
30 |
31 | The content provider doesn't support any configuration arguments.
32 |
33 | #### Usage
34 |
35 | The content provider supports the following execution arguments:
36 |
37 | ```hcl
38 | content title {
39 | # Title content
40 | #
41 | # Required string.
42 | #
43 | # For example:
44 | value = "Vulnerability Report"
45 |
46 | # Sets the absolute size of the title.
47 | # If `null` – absoulute title size is determined from the document structure
48 | #
49 | # Optional integer.
50 | # Default value:
51 | absolute_size = null
52 |
53 | # Adjusts the absolute size of the title.
54 | # The value (which may be negative) is added to the `absolute_size` to produce the final title size
55 | #
56 | # Optional integer.
57 | # Default value:
58 | relative_size = 0
59 | }
60 | ```
61 |
62 |
--------------------------------------------------------------------------------
/docs/plugins/builtin/data-sources/sleep.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`sleep` data source"
3 | plugin:
4 | name: blackstork/builtin
5 | description: "Sleeps for the specified duration. Useful for testing and debugging"
6 | tags: ["debug"]
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
9 | resource:
10 | type: data-source
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "sleep" "data source" >}}
17 |
18 | ## Description
19 |
20 | Sleeps for the specified duration. Useful for testing and debugging.
21 |
22 |
23 | The data source is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.
24 |
25 | ## Configuration
26 |
27 | The data source doesn't support any configuration arguments.
28 |
29 | ## Usage
30 |
31 | The data source supports the following execution arguments:
32 |
33 | ```hcl
34 | data sleep {
35 | # Duration to sleep
36 | #
37 | # Optional string.
38 | # Must be non-empty
39 | # Default value:
40 | duration = "1s"
41 | }
42 | ```
--------------------------------------------------------------------------------
/docs/plugins/builtin/publishers/hub.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`hub` publisher"
3 | plugin:
4 | name: blackstork/builtin
5 | description: "Publish documents to Blackstork Hub."
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
9 | resource:
10 | type: publisher
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "hub" "publisher" >}}
17 |
18 | The publisher is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.
19 |
20 | #### Formats
21 |
22 | The publisher supports the following document formats:
23 |
24 | - `unknown`
25 |
26 | To set the output format, specify it inside `publish` block with `format` argument.
27 |
28 |
29 | #### Configuration
30 |
31 | The publisher supports the following configuration arguments:
32 |
33 | ```hcl
34 | config publish hub {
35 | # API url.
36 | #
37 | # Required string.
38 | # Must be non-empty
39 | #
40 | # For example:
41 | api_url = "some string"
42 |
43 | # API url.
44 | #
45 | # Required string.
46 | # Must be non-empty
47 | #
48 | # For example:
49 | api_token = "some string"
50 | }
51 |
52 | ```
53 |
54 | #### Usage
55 |
56 | The publisher supports the following execution arguments:
57 |
58 | ```hcl
59 | # In addition to the arguments listed, `publish` block accepts `format` argument.
60 |
61 | publish hub {
62 | # Hub Document title override. By default uses title configured in the document.
63 | #
64 | # Optional string.
65 | # Must be non-empty
66 | # Default value:
67 | title = null
68 | }
69 |
70 | ```
71 |
72 |
--------------------------------------------------------------------------------
/docs/plugins/builtin/publishers/local_file.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`local_file` publisher"
3 | plugin:
4 | name: blackstork/builtin
5 | description: "Publishes content to local file"
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/builtin/"
9 | resource:
10 | type: publisher
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/builtin" "builtin" "v0.4.2" "local_file" "publisher" >}}
17 |
18 | The publisher is built-in, which means it's a part of `fabric` binary. It's available out-of-the-box, no installation required.
19 |
20 | #### Formats
21 |
22 | The publisher supports the following document formats:
23 |
24 | - `md`
25 | - `html`
26 | - `pdf`
27 |
28 | To set the output format, specify it inside `publish` block with `format` argument.
29 |
30 |
31 | #### Configuration
32 |
33 | The publisher doesn't support any configuration arguments.
34 |
35 | #### Usage
36 |
37 | The publisher supports the following execution arguments:
38 |
39 | ```hcl
40 | # In addition to the arguments listed, `publish` block accepts `format` argument.
41 |
42 | publish local_file {
43 | # Path to the file
44 | #
45 | # Required string.
46 | #
47 | # For example:
48 | path = "dist/output.md"
49 | }
50 |
51 | ```
52 |
53 |
--------------------------------------------------------------------------------
/docs/plugins/content-providers.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Content Providers
3 | description: Explore a wide array of content providers through Fabric plugins. These powerful integrations enable the rendering of document content locally or via external APIs, covering various types including text, tables, graphs, code, and more. Enhance your document generation capabilities with Fabric's versatile content provider plugins.
4 | type: docs
5 | weight: 20
6 | ---
7 |
8 | # Content providers
9 |
10 | Content providers are Fabric content renderers. The providers may generate content locally or use an
11 | external service (like OpenAI API) for content generation.
12 |
13 | ## Available content providers
14 |
15 | {{< plugin-resources "" "content-provider" >}}
16 |
--------------------------------------------------------------------------------
/docs/plugins/crowdstrike/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/crowdstrike
3 | weight: 20
4 | plugin:
5 | name: blackstork/crowdstrike
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/crowdstrike/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/crowdstrike" "crowdstrike" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/crowdstrike" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "crowdstrike" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/data-sources.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Data Sources
3 | description: Discover a diverse range of data sources within Fabric plugins. These integrations empower you to effortlessly load data from files, external services, APIs, and data storage systems. Simplify your data retrieval process and enhance your document generation workflow with Fabric's versatile data sources.
4 | type: docs
5 | weight: 10
6 | ---
7 |
8 | # Data sources
9 |
10 | Data sources are Fabric integrations responsible for loading data from local and external sources -
11 | files, external services, APIs, or data storage solutions.
12 |
13 | ## Available data sources
14 |
15 | {{< plugin-resources "" "data-source" >}}
16 |
--------------------------------------------------------------------------------
/docs/plugins/elastic/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/elastic
3 | weight: 20
4 | plugin:
5 | name: blackstork/elastic
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/elastic/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/elastic" "elastic" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/elastic" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "elastic" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/github/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/github
3 | weight: 20
4 | plugin:
5 | name: blackstork/github
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/github/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/github" "github" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/github" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "github" "data-source" >}}
32 |
33 | ## Publishers
34 |
35 | {{< plugin-resources "github" "publisher" >}}
--------------------------------------------------------------------------------
/docs/plugins/graphql/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/graphql
3 | weight: 20
4 | plugin:
5 | name: blackstork/graphql
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/graphql/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/graphql" "graphql" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/graphql" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "graphql" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/graphql/data-sources/graphql.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`graphql` data source"
3 | plugin:
4 | name: blackstork/graphql
5 | description: ""
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/graphql/"
9 | resource:
10 | type: data-source
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/graphql" "graphql" "v0.4.2" "graphql" "data source" >}}
17 |
18 | ## Installation
19 |
20 | To use `graphql` data source, you must install the plugin `blackstork/graphql`.
21 |
22 | To install the plugin, add the full plugin name to the `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), as shown below:
23 |
24 | ```hcl
25 | fabric {
26 | plugin_versions = {
27 | "blackstork/graphql" = ">= v0.4.2"
28 | }
29 | }
30 | ```
31 |
32 | Note the version constraint set for the plugin.
33 |
34 | ## Configuration
35 |
36 | The data source supports the following configuration arguments:
37 |
38 | ```hcl
39 | config data graphql {
40 | # Required string.
41 | #
42 | # For example:
43 | url = "some string"
44 |
45 | # Optional string.
46 | # Default value:
47 | auth_token = null
48 | }
49 | ```
50 |
51 | ## Usage
52 |
53 | The data source supports the following execution arguments:
54 |
55 | ```hcl
56 | data graphql {
57 | # Required string.
58 | #
59 | # For example:
60 | query = "some string"
61 | }
62 | ```
--------------------------------------------------------------------------------
/docs/plugins/hackerone/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/hackerone
3 | weight: 20
4 | plugin:
5 | name: blackstork/hackerone
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/hackerone/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/hackerone" "hackerone" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/hackerone" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "hackerone" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/iris/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/iris
3 | weight: 20
4 | plugin:
5 | name: blackstork/iris
6 | description: "The `iris` plugin for Iris Incident Response platform."
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/iris/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/iris" "iris" "v0.4.2" >}}
15 |
16 | ## Description
17 | The `iris` plugin for Iris Incident Response platform.
18 |
19 | ## Installation
20 |
21 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
22 |
23 | ```hcl
24 | fabric {
25 | plugin_versions = {
26 | "blackstork/iris" = ">= v0.4.2"
27 | }
28 | }
29 | ```
30 |
31 |
32 | ## Data sources
33 |
34 | {{< plugin-resources "iris" "data-source" >}}
35 |
--------------------------------------------------------------------------------
/docs/plugins/microsoft/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/microsoft
3 | weight: 20
4 | plugin:
5 | name: blackstork/microsoft
6 | description: "Plugin for Microsoft services."
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/microsoft/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/microsoft" "microsoft" "v0.4.2" >}}
15 |
16 | ## Description
17 | Plugin for Microsoft services.
18 |
19 | ## Installation
20 |
21 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
22 |
23 | ```hcl
24 | fabric {
25 | plugin_versions = {
26 | "blackstork/microsoft" = ">= v0.4.2"
27 | }
28 | }
29 | ```
30 |
31 |
32 | ## Data sources
33 |
34 | {{< plugin-resources "microsoft" "data-source" >}}
35 |
36 | ## Content providers
37 |
38 | {{< plugin-resources "microsoft" "content-provider" >}}
--------------------------------------------------------------------------------
/docs/plugins/misp/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/misp
3 | weight: 20
4 | plugin:
5 | name: blackstork/misp
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/misp/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/misp" "misp" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/misp" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "misp" "data-source" >}}
32 |
33 | ## Publishers
34 |
35 | {{< plugin-resources "misp" "publisher" >}}
--------------------------------------------------------------------------------
/docs/plugins/nist_nvd/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/nist_nvd
3 | weight: 20
4 | plugin:
5 | name: blackstork/nist_nvd
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/nistnvd/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/nist_nvd" "nist_nvd" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/nist_nvd" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "nist_nvd" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/openai/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/openai
3 | weight: 20
4 | plugin:
5 | name: blackstork/openai
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/openai/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/openai" "openai" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/openai" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 |
30 | ## Content providers
31 |
32 | {{< plugin-resources "openai" "content-provider" >}}
--------------------------------------------------------------------------------
/docs/plugins/opencti/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/opencti
3 | weight: 20
4 | plugin:
5 | name: blackstork/opencti
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/opencti/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/opencti" "opencti" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/opencti" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "opencti" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/opencti/data-sources/opencti.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`opencti` data source"
3 | plugin:
4 | name: blackstork/opencti
5 | description: ""
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/opencti/"
9 | resource:
10 | type: data-source
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/opencti" "opencti" "v0.4.2" "opencti" "data source" >}}
17 |
18 | ## Installation
19 |
20 | To use `opencti` data source, you must install the plugin `blackstork/opencti`.
21 |
22 | To install the plugin, add the full plugin name to the `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), as shown below:
23 |
24 | ```hcl
25 | fabric {
26 | plugin_versions = {
27 | "blackstork/opencti" = ">= v0.4.2"
28 | }
29 | }
30 | ```
31 |
32 | Note the version constraint set for the plugin.
33 |
34 | ## Configuration
35 |
36 | The data source supports the following configuration arguments:
37 |
38 | ```hcl
39 | config data opencti {
40 | # Required string.
41 | #
42 | # For example:
43 | graphql_url = "some string"
44 |
45 | # Optional string.
46 | # Default value:
47 | auth_token = null
48 | }
49 | ```
50 |
51 | ## Usage
52 |
53 | The data source supports the following execution arguments:
54 |
55 | ```hcl
56 | data opencti {
57 | # Required string.
58 | #
59 | # For example:
60 | graphql_query = "some string"
61 | }
62 | ```
--------------------------------------------------------------------------------
/docs/plugins/postgresql/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/postgresql
3 | weight: 20
4 | plugin:
5 | name: blackstork/postgresql
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/postgresql/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/postgresql" "postgresql" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/postgresql" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "postgresql" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/postgresql/data-sources/postgresql.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`postgresql` data source"
3 | plugin:
4 | name: blackstork/postgresql
5 | description: ""
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/postgresql/"
9 | resource:
10 | type: data-source
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/postgresql" "postgresql" "v0.4.2" "postgresql" "data source" >}}
17 |
18 | ## Installation
19 |
20 | To use `postgresql` data source, you must install the plugin `blackstork/postgresql`.
21 |
22 | To install the plugin, add the full plugin name to the `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), as shown below:
23 |
24 | ```hcl
25 | fabric {
26 | plugin_versions = {
27 | "blackstork/postgresql" = ">= v0.4.2"
28 | }
29 | }
30 | ```
31 |
32 | Note the version constraint set for the plugin.
33 |
34 | ## Configuration
35 |
36 | The data source supports the following configuration arguments:
37 |
38 | ```hcl
39 | config data postgresql {
40 | # Required string.
41 | # Must be non-empty
42 | #
43 | # For example:
44 | database_url = "some string"
45 | }
46 | ```
47 |
48 | ## Usage
49 |
50 | The data source supports the following execution arguments:
51 |
52 | ```hcl
53 | data postgresql {
54 | # Required string.
55 | # Must be non-empty
56 | #
57 | # For example:
58 | sql_query = "some string"
59 |
60 | # Optional list of any single type.
61 | # Default value:
62 | sql_args = null
63 | }
64 | ```
--------------------------------------------------------------------------------
/docs/plugins/publishers.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Publishers
3 | description: A wide range of publishers served by Fabric plugins allow users to publish the documents for storage or dissemination to local and external destinations.
4 | type: docs
5 | weight: 30
6 | ---
7 |
8 | # Publishers
9 |
10 | Publishers are Fabric outgoing integrations, responsible for delivering produced documents to local
11 | and external destinations, for storage or dissemination.
12 |
13 | ## Available publishers
14 |
15 | {{< plugin-resources "" "publisher" >}}
16 |
--------------------------------------------------------------------------------
/docs/plugins/snyk/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/snyk
3 | weight: 20
4 | plugin:
5 | name: blackstork/snyk
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/snyk/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/snyk" "snyk" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/snyk" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "snyk" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/splunk/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/splunk
3 | weight: 20
4 | plugin:
5 | name: blackstork/splunk
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/splunk/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/splunk" "splunk" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/splunk" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "splunk" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/sqlite/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/sqlite
3 | weight: 20
4 | plugin:
5 | name: blackstork/sqlite
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/sqlite/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/sqlite" "sqlite" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/sqlite" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "sqlite" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/sqlite/data-sources/sqlite.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`sqlite` data source"
3 | plugin:
4 | name: blackstork/sqlite
5 | description: ""
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/sqlite/"
9 | resource:
10 | type: data-source
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/sqlite" "sqlite" "v0.4.2" "sqlite" "data source" >}}
17 |
18 | ## Installation
19 |
20 | To use `sqlite` data source, you must install the plugin `blackstork/sqlite`.
21 |
22 | To install the plugin, add the full plugin name to the `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), as shown below:
23 |
24 | ```hcl
25 | fabric {
26 | plugin_versions = {
27 | "blackstork/sqlite" = ">= v0.4.2"
28 | }
29 | }
30 | ```
31 |
32 | Note the version constraint set for the plugin.
33 |
34 | ## Configuration
35 |
36 | The data source supports the following configuration arguments:
37 |
38 | ```hcl
39 | config data sqlite {
40 | # Required string.
41 | # Must be non-empty
42 | #
43 | # For example:
44 | database_uri = "some string"
45 | }
46 | ```
47 |
48 | ## Usage
49 |
50 | The data source supports the following execution arguments:
51 |
52 | ```hcl
53 | data sqlite {
54 | # SQL query to execute
55 | #
56 | # Required string.
57 | #
58 | # For example:
59 | sql_query = "some string"
60 |
61 | # A tuple (or list) of strings, numbers, or booleans to be used as arguments in the SQL query
62 | #
63 | # Optional any type.
64 | #
65 | # For example:
66 | # sql_args = ["example argument", 2, false]
67 | #
68 | # Default value:
69 | sql_args = null
70 | }
71 | ```
--------------------------------------------------------------------------------
/docs/plugins/stixview/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/stixview
3 | weight: 20
4 | plugin:
5 | name: blackstork/stixview
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/stixview/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/stixview" "stixview" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/stixview" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 |
30 | ## Content providers
31 |
32 | {{< plugin-resources "stixview" "content-provider" >}}
--------------------------------------------------------------------------------
/docs/plugins/terraform/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/terraform
3 | weight: 20
4 | plugin:
5 | name: blackstork/terraform
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/terraform/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/terraform" "terraform" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/terraform" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "terraform" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/terraform/data-sources/terraform_state_local.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`terraform_state_local` data source"
3 | plugin:
4 | name: blackstork/terraform
5 | description: ""
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/terraform/"
9 | resource:
10 | type: data-source
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/terraform" "terraform" "v0.4.2" "terraform_state_local" "data source" >}}
17 |
18 | ## Installation
19 |
20 | To use `terraform_state_local` data source, you must install the plugin `blackstork/terraform`.
21 |
22 | To install the plugin, add the full plugin name to the `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), as shown below:
23 |
24 | ```hcl
25 | fabric {
26 | plugin_versions = {
27 | "blackstork/terraform" = ">= v0.4.2"
28 | }
29 | }
30 | ```
31 |
32 | Note the version constraint set for the plugin.
33 |
34 | ## Configuration
35 |
36 | The data source doesn't support any configuration arguments.
37 |
38 | ## Usage
39 |
40 | The data source supports the following execution arguments:
41 |
42 | ```hcl
43 | data terraform_state_local {
44 | # Required string.
45 | #
46 | # For example:
47 | path = "some string"
48 | }
49 | ```
--------------------------------------------------------------------------------
/docs/plugins/virustotal/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: blackstork/virustotal
3 | weight: 20
4 | plugin:
5 | name: blackstork/virustotal
6 | description: ""
7 | tags: []
8 | version: "v0.4.2"
9 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/virustotal/"
10 | type: docs
11 | hideInMenu: true
12 | ---
13 |
14 | {{< plugin-header "blackstork/virustotal" "virustotal" "v0.4.2" >}}
15 |
16 | ## Installation
17 |
18 | To install the plugin, add it to `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), with a version constraint restricting which available versions of the plugin the codebase is compatible with:
19 |
20 | ```hcl
21 | fabric {
22 | plugin_versions = {
23 | "blackstork/virustotal" = ">= v0.4.2"
24 | }
25 | }
26 | ```
27 |
28 |
29 | ## Data sources
30 |
31 | {{< plugin-resources "virustotal" "data-source" >}}
32 |
--------------------------------------------------------------------------------
/docs/plugins/virustotal/data-sources/virustotal_api_usage.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "`virustotal_api_usage` data source"
3 | plugin:
4 | name: blackstork/virustotal
5 | description: ""
6 | tags: []
7 | version: "v0.4.2"
8 | source_github: "https://github.com/blackstork-io/fabric/tree/main/internal/virustotal/"
9 | resource:
10 | type: data-source
11 | type: docs
12 | ---
13 |
14 | {{< breadcrumbs 2 >}}
15 |
16 | {{< plugin-resource-header "blackstork/virustotal" "virustotal" "v0.4.2" "virustotal_api_usage" "data source" >}}
17 |
18 | ## Installation
19 |
20 | To use `virustotal_api_usage` data source, you must install the plugin `blackstork/virustotal`.
21 |
22 | To install the plugin, add the full plugin name to the `plugin_versions` map in the Fabric global configuration block (see [Global configuration]({{< ref "configs.md#global-configuration" >}}) for more details), as shown below:
23 |
24 | ```hcl
25 | fabric {
26 | plugin_versions = {
27 | "blackstork/virustotal" = ">= v0.4.2"
28 | }
29 | }
30 | ```
31 |
32 | Note the version constraint set for the plugin.
33 |
34 | ## Configuration
35 |
36 | The data source supports the following configuration arguments:
37 |
38 | ```hcl
39 | config data virustotal_api_usage {
40 | # Required string.
41 | # Must be non-empty
42 | #
43 | # For example:
44 | api_key = "some string"
45 | }
46 | ```
47 |
48 | ## Usage
49 |
50 | The data source supports the following execution arguments:
51 |
52 | ```hcl
53 | data virustotal_api_usage {
54 | # Optional string.
55 | # Default value:
56 | user_id = null
57 |
58 | # Optional string.
59 | # Default value:
60 | group_id = null
61 |
62 | # Optional string.
63 | # Default value:
64 | start_date = null
65 |
66 | # Optional string.
67 | # Default value:
68 | end_date = null
69 | }
70 | ```
--------------------------------------------------------------------------------
/engine/testdata/a.json:
--------------------------------------------------------------------------------
1 | {
2 | "property_for": "a.json"
3 | }
--------------------------------------------------------------------------------
/eval/plugin_action.go:
--------------------------------------------------------------------------------
1 | package eval
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/parser/definitions"
5 | "github.com/blackstork-io/fabric/plugin/dataspec"
6 | )
7 |
8 | type PluginAction struct {
9 | Source *definitions.Plugin
10 | PluginName string
11 | BlockName string
12 | Meta *definitions.MetaBlock
13 | Config *dataspec.Block
14 | Args *dataspec.Block
15 | IsIncluded *dataspec.Attr
16 | }
17 |
--------------------------------------------------------------------------------
/eval/plugins.go:
--------------------------------------------------------------------------------
1 | package eval
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/plugin"
5 | )
6 |
7 | type DataSources interface {
8 | DataSource(name string) (*plugin.DataSource, bool)
9 | }
10 |
11 | type ContentProviders interface {
12 | ContentProvider(name string) (*plugin.ContentProvider, bool)
13 | }
14 |
15 | type Publishers interface {
16 | Publisher(name string) (*plugin.Publisher, bool)
17 | }
18 |
19 | type Plugins interface {
20 | DataSources
21 | ContentProviders
22 | Publishers
23 | }
24 |
--------------------------------------------------------------------------------
/examples/plugins/README.md:
--------------------------------------------------------------------------------
1 | # Plugin Examples
2 |
3 | ## Directories
4 |
5 | * [/examples/plugins/basic](./basic) - Example on how to make a simple `fabric` plugin using Go.
--------------------------------------------------------------------------------
/examples/plugins/basic/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/examples/plugins/basic"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | func main() {
9 | // Serve the plugin using the pluginapiv1
10 | pluginapiv1.Serve(
11 | // Pass the plugin schema to the Serve function
12 | basic.Plugin("0.0.1"),
13 | )
14 | // Thats it! The plugin is now ready to be used
15 | // Build it and put it in the plugin directory to test it out
16 | // Binary should be placed like this in the plugin directory
17 | //
18 | // /plugins
19 | // /example
20 | // basic@0.0.1
21 | }
22 |
--------------------------------------------------------------------------------
/examples/plugins/basic/content_greeting.go:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/zclconf/go-cty/cty"
8 |
9 | "github.com/blackstork-io/fabric/pkg/diagnostics"
10 | "github.com/blackstork-io/fabric/plugin"
11 | "github.com/blackstork-io/fabric/plugin/dataspec"
12 | "github.com/blackstork-io/fabric/plugin/dataspec/constraint"
13 | )
14 |
15 | // makeGreetingContentProvider creates a new content provider that prints out a greeting message
16 | func makeGreetingContentProvider() *plugin.ContentProvider {
17 | return &plugin.ContentProvider{
18 | // Config is optional, in this case we don't need it
19 | // We only define the schema for the arguments
20 | Args: &dataspec.RootSpec{
21 | Attrs: []*dataspec.AttrSpec{{
22 | Name: "name",
23 | Constraints: constraint.RequiredMeaningful,
24 | Doc: `Name of the user`,
25 | ExampleVal: cty.StringVal("John"),
26 | Type: cty.String,
27 | }},
28 | },
29 | // Optional: We can also define the schema for the config
30 | ContentFunc: renderGreetingMessage,
31 | }
32 | }
33 |
34 | func renderGreetingMessage(ctx context.Context, params *plugin.ProvideContentParams) (*plugin.ContentResult, diagnostics.Diag) {
35 | // We specified that the "name" attribute is RequiredMeaningful, so we can safely assume
36 | // that it exists, non-null and non-empty, with whitespace trimmed
37 | name := params.Args.GetAttrVal("name").AsString()
38 | return &plugin.ContentResult{
39 | Content: plugin.NewElementFromMarkdown(fmt.Sprintf("Hello, %s!", name)),
40 | }, nil
41 | }
42 |
--------------------------------------------------------------------------------
/examples/plugins/basic/plugin.go:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import "github.com/blackstork-io/fabric/plugin"
4 |
5 | // Plugin returns the schema and version for the plugin
6 | // This is the entry point for the plugin
7 | func Plugin(version string) *plugin.Schema {
8 | return &plugin.Schema{
9 | // Name is the name of the plugin
10 | Name: "example/basic",
11 | // Version is the version of the plugin
12 | // It must be a valid semver
13 | Version: version,
14 | // DataSources is a map of data sources that the plugin provides
15 | // The key is the name of the data source and the value is the data source schema
16 | DataSources: plugin.DataSources{
17 | "basic_random_numbers": makeRandomNumbersDataSource(),
18 | },
19 | // ContentProviders is a map of content providers that the plugin provides
20 | // The key is the name of the content provider and the value is the content provider schema
21 | ContentProviders: plugin.ContentProviders{
22 | "basic_greeting": makeGreetingContentProvider(),
23 | },
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/templates/async/example.fabric:
--------------------------------------------------------------------------------
1 | document "example" {
2 | title = "Document title"
3 |
4 | data sleep data_a {
5 | duration = "2s"
6 | }
7 | data sleep data_b {
8 | duration = "1s"
9 | }
10 |
11 | data sleep data_c {
12 | duration = "1s"
13 | }
14 |
15 | data sleep data_d {
16 | duration = "3s"
17 | }
18 |
19 | content title {
20 | value = "A"
21 | }
22 |
23 | content sleep content_a {
24 | duration = "2s"
25 | }
26 |
27 | content title {
28 | value = "B"
29 | }
30 |
31 | content sleep content_b {
32 | duration = "1.5s"
33 | }
34 |
35 | content title {
36 | value = "C"
37 | }
38 |
39 | content sleep content_c {
40 | duration = "3.5s"
41 | depends_on = [
42 | "content.sleep.content_a",
43 | "content.sleep.content_b",
44 | ]
45 | }
46 |
47 | content title {
48 | value = "D"
49 | }
50 |
51 | content sleep content_d {
52 | duration = "3s"
53 | }
54 |
55 | section {
56 | title = "Section 1"
57 |
58 | content sleep content_e {
59 | depends_on = ["content.sleep.content_c"]
60 | duration = "0.5s"
61 | }
62 |
63 | section {
64 | title = "Section 1.1"
65 |
66 | content sleep content_f {
67 | duration = "1s"
68 | }
69 |
70 | content sleep content_g {
71 | duration = "1s"
72 | }
73 | }
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/examples/templates/atlassian/example.fabric:
--------------------------------------------------------------------------------
1 | fabric {
2 | plugin_versions = {
3 | "blackstork/atlassian" = ">= 0.5 < 1.0 || 0.5.0-rev0"
4 | }
5 | }
6 |
7 | config data jira_issues {
8 | domain = env.JIRA_DOMAIN
9 | account_email = env.JIRA_ACCOUNT_EMAIL
10 | api_token = env.JIRA_API_TOKEN
11 | }
12 |
13 | document "example" {
14 | title = "Using atlassian plugin"
15 | data jira_issues "my_issues" {
16 | expand = "names"
17 | fields = ["*all"]
18 | jql = "project = TEST"
19 | size = 5
20 | }
21 | content title {
22 | value = "My Jira Issues"
23 | }
24 | content list {
25 | item_template = "{{.key}}: {{.fields.summary}}"
26 | items = query_jq(".data.jira_issues.my_issues")
27 | }
28 | }
--------------------------------------------------------------------------------
/examples/templates/azureopenai/example.fabric:
--------------------------------------------------------------------------------
1 |
2 | fabric {
3 | plugin_versions = {
4 | "blackstork/microsoft" = ">= 0.4 < 1.0 || 0.4.0-rev0"
5 | }
6 | }
7 |
8 | document "example" {
9 | meta {
10 | name = "example_document"
11 | }
12 |
13 | title = "Document title"
14 |
15 | section {
16 | title = "Section 2"
17 |
18 | section {
19 | title = "Subsection 2"
20 |
21 | content text {
22 | value = "Text value 4"
23 | }
24 | }
25 | }
26 |
27 | content azure_openai_text {
28 | config {
29 | api_key = env.AZURE_OPENAI_KEY
30 | resource_endpoint = env.AZURE_OPENAI_ENDPOINT
31 | deployment_name = env.AZURE_OPENAI_DEPLOYMENT
32 | api_version = "2024-02-01"
33 | }
34 | prompt = "How are you today?"
35 | max_tokens = 10
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/examples/templates/basic_hello/hello.fabric:
--------------------------------------------------------------------------------
1 | document "hello" {
2 | title = "Welcome"
3 | content text {
4 | value = "Hello from fabric"
5 | }
6 | }
--------------------------------------------------------------------------------
/examples/templates/crowdstrike/data_falcon_cspm_ioms.fabric:
--------------------------------------------------------------------------------
1 | fabric {
2 | plugin_versions = {
3 | "blackstork/crowdstrike" = ">= 0.4 < 1.0 || 0.4.0-rev0"
4 | }
5 | }
6 |
7 | document "cspm_ioms" {
8 | meta {
9 | name = "example_document"
10 | }
11 |
12 | data falcon_cspm_ioms "cspm" {
13 | config {
14 | client_id = ""
15 | client_secret = ""
16 | client_cloud = "eu-1"
17 | }
18 | limit = 100
19 | }
20 |
21 | title = "List of CSPM IOMS"
22 |
23 | content table {
24 | rows = query_jq(".data.falcon_cspm_ioms.cspm")
25 | columns = [
26 | {
27 | "header" = "Account Id"
28 | "value" = "{{.row.value.account_id}}"
29 | },
30 | {
31 | "header" = "Cloud Provider"
32 | "value" = "{{.row.value.cloud_provider}}"
33 | }
34 | ]
35 | }
36 |
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/examples/templates/crowdstrike/data_falcon_detection_details.fabric:
--------------------------------------------------------------------------------
1 | document "detection_details" {
2 | meta {
3 | name = "example_document"
4 | }
5 |
6 | data falcon_detection_details "details" {
7 | config {
8 | client_id = "90f13a00b72b4306906c9580a24ae0d7"
9 | client_secret = "7X1M6lI4PtU9v5ObmJ8HCSB2jnLqzfacpGx30NWD"
10 | client_cloud = "eu-1"
11 | }
12 | limit = 100
13 | }
14 |
15 | title = "List of Detections"
16 |
17 | content table {
18 | rows = query_jq(".data.falcon_detection_details.details")
19 | columns = [
20 | {
21 | "header" = "CID"
22 | "value" = "{{.row.value.cid}}"
23 | },
24 | {
25 | "header" = "Status"
26 | "value" = "{{.row.value.status}}"
27 | }
28 | ]
29 | }
30 |
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/examples/templates/crowdstrike/data_falcon_discover_host_details.fabric:
--------------------------------------------------------------------------------
1 | document "falcon_discover_host_details" {
2 | meta {
3 | name = "example_document"
4 | }
5 |
6 | data falcon_discover_host_details "fdhd" {
7 | config {
8 | client_id = ""
9 | client_secret = ""
10 | client_cloud = "eu-1"
11 | }
12 | limit = 100
13 | }
14 |
15 | title = "List of discover host details"
16 |
17 | content table {
18 | rows = query_jq(".data.falcon_discover_host_details.fdhd")
19 | columns = [
20 | {
21 | "header" = "Cid"
22 | "value" = "{{.row.value.cid}}"
23 | },
24 | {
25 | "header" = "City"
26 | "value" = "{{.row.value.city}}"
27 | }
28 | ]
29 | }
30 |
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/examples/templates/crowdstrike/data_falcon_intel_indicators.fabric:
--------------------------------------------------------------------------------
1 | fabric {
2 | plugin_versions = {
3 | "blackstork/crowdstrike" = ">= 0.4 < 1.0 || 0.4.0-rev0"
4 | }
5 | }
6 |
7 | document "intel_indicators" {
8 | meta {
9 | name = "example_document"
10 | }
11 |
12 | data falcon_intel_indicators "indicators" {
13 | config {
14 | client_id = ""
15 | client_secret = ""
16 | client_cloud = "eu-1"
17 | }
18 | limit = 100
19 | }
20 |
21 | title = "List of Intel Indicators"
22 |
23 | content table {
24 | rows = query_jq(".data.falcon_intel_indicators.indicators")
25 | columns = [
26 | {
27 | "header" = "Id"
28 | "value" = "{{.row.value.id}}"
29 | },
30 | {
31 | "header" = "Indicator"
32 | "value" = "{{.row.value.indicator}}"
33 | }
34 | ]
35 | }
36 |
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/examples/templates/crowdstrike/data_falcon_vulnerabilities.fabric:
--------------------------------------------------------------------------------
1 | fabric {
2 | plugin_versions = {
3 | "blackstork/crowdstrike" = ">= 0.4 < 1.0 || 0.4.0-rev0"
4 | }
5 | }
6 |
7 | document "vulnerabilities" {
8 | meta {
9 | name = "example_document"
10 | }
11 |
12 | data falcon_vulnerabilities "vulnerabilities" {
13 | config {
14 | client_id = ""
15 | client_secret = ""
16 | client_cloud = "eu-1"
17 | }
18 | limit = 100
19 | }
20 |
21 | title = "List of Falcon vulnerabilities"
22 |
23 | content table {
24 | rows = query_jq(".data.falcon_vulnerabilities.vulnerabilities")
25 | columns = [
26 | {
27 | "header" = "Id"
28 | "value" = "{{.row.value.id}}"
29 | },
30 | {
31 | "header" = "Status"
32 | "value" = "{{.row.value.status}}"
33 | }
34 | ]
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/examples/templates/frontmatter/example.fabric:
--------------------------------------------------------------------------------
1 |
2 | document "example" {
3 | title = "Using FrontMatter content provider"
4 |
5 | content frontmatter {
6 | format = "yaml"
7 | content = {
8 | some = "value"
9 | other = "value2"
10 | }
11 | }
12 |
13 | content title {
14 | value = "Sub title 1"
15 | }
16 |
17 | content text {
18 | value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor dolore magna."
19 | }
20 |
21 | content title {
22 | value = "Sub title 2"
23 | }
24 |
25 | content text {
26 | value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor dolore magna."
27 | }
28 |
29 |
30 | }
--------------------------------------------------------------------------------
/examples/templates/hackerone/example.fabric:
--------------------------------------------------------------------------------
1 | fabric {
2 | plugin_versions = {
3 | "blackstork/hackerone" = ">= 0.4 < 1.0 || 0.4.0-rev0"
4 | }
5 | }
6 |
7 | config data hackerone_reports {
8 | api_username = env.HACKERONE_API_USERNAME
9 | api_token = env.HACKERONE_API_TOKEN
10 | }
11 |
12 | document "example" {
13 | title = "Using hackerone plugin"
14 | data hackerone_reports "my_reports" {
15 | program = [env.HACKERONE_PROGRAM]
16 | }
17 | content title {
18 | value = "My HackerOne Reports"
19 | }
20 | content list {
21 | query = "[.data.hackerone_reports.my_reports[].attributes.title]"
22 | item_template = "{{.}}"
23 | }
24 | }
--------------------------------------------------------------------------------
/examples/templates/hub/example.fabric:
--------------------------------------------------------------------------------
1 | document "example" {
2 |
3 | title = "Example Report, {{ now | date \"Jan 02\" }}"
4 |
5 | publish hub {
6 | config {
7 | api_url = env.HUB_API_URL
8 | api_token = env.HUB_API_TOKEN
9 | }
10 | }
11 |
12 | content title {
13 | value = "TOC"
14 | }
15 |
16 | content toc {}
17 |
18 | content title {
19 | value = "Subtitle 0"
20 | }
21 |
22 | section {
23 | title = "Section #1"
24 |
25 | content title {
26 | value = "Section TOC"
27 | }
28 |
29 | content toc {
30 | start_level = 1
31 | end_level = 4
32 | ordered = true
33 | }
34 |
35 | content text {
36 | value = "Text value 1"
37 | }
38 |
39 | content title {
40 | value = "Subtitle 1"
41 | }
42 |
43 | content text {
44 | value = "Text value 2"
45 | }
46 |
47 | section {
48 | title = "Subsection 1"
49 |
50 | content text {
51 | value = "Text value 3"
52 | }
53 | }
54 |
55 | content title {
56 | value = "Subtitle 2"
57 | }
58 | }
59 |
60 | section {
61 | title = "Section 2"
62 |
63 | section {
64 | title = "Subsection 2"
65 |
66 | content text {
67 | value = "Text value 4"
68 | }
69 | }
70 | }
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/examples/templates/iris/example.fabric:
--------------------------------------------------------------------------------
1 | fabric {
2 | plugin_versions = {
3 | "blackstork/iris" = ">= 0.5 < 1.0 || 0.5.0-rev0"
4 | }
5 | }
6 |
7 | config data iris_cases {
8 | api_url = env.IRIS_API_URL
9 | api_key = env.IRIS_API_KEY
10 | # insecure = true
11 | }
12 |
13 | document "example" {
14 | title = "Using iris plugin"
15 | data iris_cases "my_cases" {
16 | size = 2
17 | }
18 | content title {
19 | value = "My Iris Cases"
20 | }
21 | content list {
22 | item_template = "{{.name}}"
23 | items = query_jq(".data.iris_cases.my_cases")
24 | }
25 | }
--------------------------------------------------------------------------------
/examples/templates/microsoft/graph_data_source.fabric:
--------------------------------------------------------------------------------
1 | fabric {
2 | plugin_versions = {
3 | "blackstork/microsoft" = ">= 0.4 < 1.0 || 0.4.0-rev0"
4 | }
5 | }
6 |
7 | document "example" {
8 | meta {
9 | name = "example_document"
10 | }
11 |
12 | data microsoft_graph "mygraph" {
13 | config {
14 | client_id = ""
15 | client_secret = ""
16 | tenant_id = ""
17 | # private_key_file = ""
18 | }
19 | api_version = "v1.0"
20 | endpoint = "/security/incidents"
21 | query_params = {
22 | "$top" = "10"
23 | }
24 | }
25 |
26 | title = "List of Security Incidents"
27 |
28 | content table {
29 | rows = query_jq(".data.microsoft_graph.mygraph.value")
30 | columns = [
31 | {
32 | "header" = "Severity"
33 | "value" = "{{.row.value.severity}}"
34 | },
35 | {
36 | "header" = "Display Name"
37 | "value" = "{{.row.value.displayName}}"
38 | }
39 | ]
40 | }
41 |
42 |
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/examples/templates/misp/misp_event_reports.fabric:
--------------------------------------------------------------------------------
1 | document "misp_event_reports" {
2 | meta {
3 | name = "example_document"
4 | }
5 |
6 | title = "Publish"
7 |
8 | publish misp_event_reports "myreport" {
9 | format = "md"
10 | event_id = "1"
11 | name = "doc.md"
12 | distribution = "0"
13 | config {
14 | api_key = ""
15 | base_url = "https://localhost"
16 | skip_ssl = true
17 | }
18 | }
19 |
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/examples/templates/misp/misp_events.fabric:
--------------------------------------------------------------------------------
1 | fabric {
2 | plugin_versions = {
3 | "blackstork/misp" = ">= 0.4 < 1.0 || 0.4.0-rev0"
4 | }
5 | }
6 |
7 | document "misp_events" {
8 | meta {
9 | name = "example_document"
10 | }
11 |
12 | data misp_events "events" {
13 | value = ""
14 | config {
15 | api_key = ""
16 | base_url = "https://localhost"
17 | skip_ssl = true
18 | }
19 |
20 | limit = 100
21 | }
22 |
23 | title = "List of Events"
24 |
25 | content table {
26 | rows = query_jq(".data.misp_events.events.response")
27 | columns = [
28 | {
29 | "header" = "Id"
30 | "value" = "{{.row.value.Event.id}}"
31 | },
32 | {
33 | "header" = "Date"
34 | "value" = "{{.row.value.Event.date}}"
35 | }
36 | ]
37 | }
38 |
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/examples/templates/nist_nvd/example.fabric:
--------------------------------------------------------------------------------
1 | # Example of working with NIST NVD CVES
2 |
3 | fabric {
4 | plugin_versions = {
5 | "blackstork/nist_nvd" = ">= 0.4.1 < 1.0 || 0.4.1-rev0"
6 | "blackstork/openai" = ">= 0.4.1 < 1.0 || 0.4.1-rev0"
7 | }
8 | }
9 |
10 | document "example" {
11 | title = "CVE-2024-29018 NIST NVD CVE vulnerability"
12 |
13 | data nist_nvd_cves "cves" {
14 | cve_id = "CVE-2024-29018"
15 | }
16 |
17 | section {
18 | title = "Description"
19 | content openai_text {
20 | config {
21 | api_key = env.OPENAI_API_KEY
22 | }
23 | query = ".data.nist_nvd_cves.cves"
24 | prompt = "Short description of NIST CVE vulnerability."
25 | }
26 | }
27 | section {
28 | title = "What to do?"
29 | content openai_text {
30 | config {
31 | api_key = env.OPENAI_API_KEY
32 | }
33 | query = ".data.nist_nvd_cves.cves"
34 | prompt = "Step by step guide how to mitigate NIST CVE vulnerability."
35 | }
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/examples/templates/openai/data.csv:
--------------------------------------------------------------------------------
1 | id,active,name,age,height
2 | b8fa4bb0-6dd4-45ba-96e0-9a182b2b932e,true,Stacey,26,1.98
3 | b0086c49-bcd8-4aae-9f88-4f46b128e709,false,Myriam,33,1.81
4 | a12d2a8c-eebc-42b3-be52-1ab0a2969a81,true,Oralee,31,2.23
--------------------------------------------------------------------------------
/examples/templates/openai/example.fabric:
--------------------------------------------------------------------------------
1 | fabric {
2 | plugin_versions = {
3 | "blackstork/openai" = ">= 0.4 < 1.0 || 0.4.0-rev0"
4 | }
5 | }
6 |
7 | document "example" {
8 | title = "Testing plugins"
9 |
10 | data csv "csv_file" {
11 | path = "./data.csv"
12 | }
13 | content title {
14 | value = "Values from the CSV file"
15 | }
16 | content table {
17 | query = ".data.csv.csv_file"
18 | columns = [
19 | {
20 | "header" = "ID"
21 | "value" = "ID:{{.id}}"
22 | },
23 | {
24 | "header" = "Active"
25 | "value" = "{{.active}}"
26 | },
27 | {
28 | "header" = "Name"
29 | "value" = "{{.name}}"
30 | },
31 | {
32 | "header" = "Age"
33 | "value" = "{{.age}}"
34 | },
35 | {
36 | "header" = "Height"
37 | "value" = "{{.height}}"
38 | }
39 | ]
40 | }
41 | content openai_text {
42 | config {
43 | api_key = env.OPENAI_API_KEY
44 | }
45 | query = ".data.csv.csv_file"
46 | model = "gpt-3.5-turbo"
47 | prompt = "Decribe each user in a sentence"
48 | }
49 | }
--------------------------------------------------------------------------------
/examples/templates/sections/example.fabric:
--------------------------------------------------------------------------------
1 | document "example" {
2 | title = "Document title"
3 |
4 | content title {
5 | value = "TOC"
6 | }
7 |
8 | content toc {}
9 |
10 | content title {
11 | value = "Subtitle 0"
12 | }
13 |
14 | section {
15 | title = "Section #1"
16 |
17 | content title {
18 | value = "Section TOC"
19 | }
20 |
21 | content toc {
22 | start_level = 1
23 | end_level = 4
24 | ordered = true
25 | }
26 |
27 | content text {
28 | value = "Text value 1"
29 | }
30 |
31 | content title {
32 | value = "Subtitle 1"
33 | }
34 |
35 | content text {
36 | value = "Text value 2"
37 | }
38 |
39 | section {
40 | title = "Subsection 1"
41 |
42 | content text {
43 | value = "Text value 3"
44 | }
45 | }
46 |
47 | content title {
48 | value = "Subtitle 2"
49 | }
50 | }
51 |
52 | section {
53 | title = "Section 2"
54 |
55 | section {
56 | title = "Subsection 2"
57 |
58 | content text {
59 | value = "Text value 4"
60 | }
61 | }
62 | }
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/examples/templates/stixview/example.fabric:
--------------------------------------------------------------------------------
1 | fabric {
2 | plugin_versions = {
3 | "blackstork/stixview" = ">= 0.4 < 1.0 || 0.4.0-rev0"
4 | }
5 | }
6 |
7 | document "example" {
8 | title = "Using stixview plugin"
9 | content stixview {
10 | gist_id = "6a0fbb0f6e7faf063c748b23f9c7dc62"
11 | height = 500
12 | width = 500
13 | }
14 | }
--------------------------------------------------------------------------------
/examples/templates/virustotal/example.fabric:
--------------------------------------------------------------------------------
1 | fabric {
2 | plugin_versions = {
3 | "blackstork/virustotal" = ">= 0.4 < 1.0 || 0.4.0-rev0"
4 | }
5 | }
6 |
7 | config data virustotal_api_usage {
8 | api_key = env.VIRUSTOTAL_API_KEY
9 | }
10 |
11 | document "example" {
12 | title = "Using virustotal plugin"
13 | data virustotal_api_usage "my_usage" {
14 | user_id = env.VIRUSTOTAL_USER_ID
15 | start_date = "20240201"
16 | end_date = "20240203"
17 | }
18 | content text {
19 | value = "{{.data.virustotal_api_usage.my_usage.daily}}"
20 | }
21 | }
--------------------------------------------------------------------------------
/gen.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | //go:generate go run github.com/vektra/mockery/v2@v2.52.2
4 | //go:generate go run github.com/bufbuild/buf/cmd/buf@v1.32.2 generate
5 |
--------------------------------------------------------------------------------
/internal/atlassian/client/dto.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | func String(s string) *string {
8 | return &s
9 | }
10 |
11 | func Int(i int) *int {
12 | return &i
13 | }
14 |
15 | type Error struct {
16 | ErrorMessages []string `json:"errorMessages"`
17 | }
18 |
19 | func (err *Error) Error() string {
20 | return strings.Join(err.ErrorMessages, " ")
21 | }
22 |
23 | type SearchIssuesReq struct {
24 | Expand *string `json:"expand,omitempty"`
25 | Fields []string `json:"fields,omitempty"`
26 | JQL *string `json:"jql,omitempty"`
27 | Properties []string `json:"properties,omitempty"`
28 | NextPageToken *string `json:"nextPageToken,omitempty"`
29 | MaxResults *int `json:"maxResults,omitempty"`
30 | }
31 |
32 | type SearchIssuesRes struct {
33 | NextPageToken *string `json:"nextPageToken,omitempty"`
34 | Issues []any `json:"issues"`
35 | }
36 |
--------------------------------------------------------------------------------
/internal/atlassian/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/atlassian"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | atlassian.Plugin(version, atlassian.DefaultClientLoader),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/atlassian/plugin_test.go:
--------------------------------------------------------------------------------
1 | package atlassian
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/atlassian", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["jira_issues"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/builtin/content_blockquote.go:
--------------------------------------------------------------------------------
1 | package builtin
2 |
3 | import (
4 | "context"
5 | "strings"
6 |
7 | "github.com/hashicorp/hcl/v2"
8 | "github.com/zclconf/go-cty/cty"
9 |
10 | "github.com/blackstork-io/fabric/pkg/diagnostics"
11 | "github.com/blackstork-io/fabric/plugin"
12 | "github.com/blackstork-io/fabric/plugin/dataspec"
13 | "github.com/blackstork-io/fabric/plugin/dataspec/constraint"
14 | )
15 |
16 | func makeBlockQuoteContentProvider() *plugin.ContentProvider {
17 | return &plugin.ContentProvider{
18 | ContentFunc: genBlockQuoteContent,
19 | Args: &dataspec.RootSpec{
20 | Attrs: []*dataspec.AttrSpec{{
21 | Name: "value",
22 | Type: cty.String,
23 | ExampleVal: cty.StringVal("Text to be formatted as a quote"),
24 | Constraints: constraint.RequiredNonNull,
25 | }},
26 | },
27 | Doc: "Formats text as a block quote",
28 | }
29 | }
30 |
31 | func genBlockQuoteContent(ctx context.Context, params *plugin.ProvideContentParams) (*plugin.ContentResult, diagnostics.Diag) {
32 | value := params.Args.GetAttrVal("value")
33 | text, err := genTextContentText(value.AsString(), params.DataContext)
34 | if err != nil {
35 | return nil, diagnostics.Diag{{
36 | Severity: hcl.DiagError,
37 | Summary: "Failed to render blockquote",
38 | Detail: err.Error(),
39 | }}
40 | }
41 | text = "> " + strings.ReplaceAll(text, "\n", "\n> ")
42 | return &plugin.ContentResult{
43 | Content: plugin.NewElementFromMarkdown(text),
44 | }, nil
45 | }
46 |
--------------------------------------------------------------------------------
/internal/builtin/content_code.go:
--------------------------------------------------------------------------------
1 | package builtin
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/hashicorp/hcl/v2"
8 | "github.com/zclconf/go-cty/cty"
9 |
10 | "github.com/blackstork-io/fabric/pkg/diagnostics"
11 | "github.com/blackstork-io/fabric/plugin"
12 | "github.com/blackstork-io/fabric/plugin/dataspec"
13 | "github.com/blackstork-io/fabric/plugin/dataspec/constraint"
14 | )
15 |
16 | func makeCodeContentProvider() *plugin.ContentProvider {
17 | return &plugin.ContentProvider{
18 | ContentFunc: genCodeContent,
19 | Args: &dataspec.RootSpec{
20 | Attrs: []*dataspec.AttrSpec{
21 | {
22 | Name: "value",
23 | Type: cty.String,
24 | Constraints: constraint.RequiredNonNull,
25 | ExampleVal: cty.StringVal("Text to be formatted as a code block"),
26 | },
27 | {
28 | Name: "language",
29 | Type: cty.String,
30 | ExampleVal: cty.StringVal("python3"),
31 | DefaultVal: cty.StringVal(""),
32 | Doc: `Specifiy the language for syntax highlighting`,
33 | },
34 | },
35 | },
36 | Doc: "Formats text as code snippet",
37 | }
38 | }
39 |
40 | func genCodeContent(ctx context.Context, params *plugin.ProvideContentParams) (*plugin.ContentResult, diagnostics.Diag) {
41 | value := params.Args.GetAttrVal("value")
42 | lang := params.Args.GetAttrVal("language")
43 | text, err := genTextContentText(value.AsString(), params.DataContext)
44 | if err != nil {
45 | return nil, diagnostics.Diag{{
46 | Severity: hcl.DiagError,
47 | Summary: "Failed to render code",
48 | Detail: err.Error(),
49 | }}
50 | }
51 | text = fmt.Sprintf("```%s\n%s\n```", lang.AsString(), text)
52 | return &plugin.ContentResult{
53 | Content: plugin.NewElementFromMarkdown(text),
54 | }, nil
55 | }
56 |
--------------------------------------------------------------------------------
/internal/builtin/content_sleep.go:
--------------------------------------------------------------------------------
1 | package builtin
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log/slog"
7 | "time"
8 |
9 | "github.com/hashicorp/hcl/v2"
10 | "github.com/zclconf/go-cty/cty"
11 |
12 | "github.com/blackstork-io/fabric/pkg/diagnostics"
13 | "github.com/blackstork-io/fabric/plugin"
14 | "github.com/blackstork-io/fabric/plugin/dataspec"
15 | "github.com/blackstork-io/fabric/plugin/dataspec/constraint"
16 | )
17 |
18 | func makeSleepContentProvider(logger *slog.Logger) *plugin.ContentProvider {
19 | logger = logger.With("content_provider", "sleep")
20 |
21 | return &plugin.ContentProvider{
22 | Doc: `
23 | Sleeps for the specified duration. Useful for testing and debugging.
24 | `,
25 | Tags: []string{"debug"},
26 | Args: &dataspec.RootSpec{
27 | Attrs: []*dataspec.AttrSpec{
28 | {
29 | Name: "duration",
30 | Type: cty.String,
31 | Doc: "Duration to sleep",
32 | Constraints: constraint.Meaningful,
33 | DefaultVal: cty.StringVal("1s"),
34 | },
35 | },
36 | },
37 | ContentFunc: func(ctx context.Context, params *plugin.ProvideContentParams) (*plugin.ContentResult, diagnostics.Diag) {
38 | duration, err := time.ParseDuration(params.Args.GetAttrVal("duration").AsString())
39 | if err != nil {
40 | return nil, diagnostics.Diag{
41 | {
42 | Severity: hcl.DiagError,
43 | Summary: "Invalid duration",
44 | Detail: err.Error(),
45 | },
46 | }
47 | }
48 |
49 | logger.WarnContext(ctx, "Sleeping", "duration", duration)
50 | time.Sleep(duration)
51 |
52 | return &plugin.ContentResult{
53 | Content: plugin.NewElementFromMarkdown(
54 | fmt.Sprintf("Slept for %s.", duration),
55 | ),
56 | }, nil
57 | },
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/internal/builtin/hubapi/dto.go:
--------------------------------------------------------------------------------
1 | package hubapi
2 |
3 | import (
4 | "encoding/json"
5 | "strings"
6 | "time"
7 | )
8 |
9 | type request struct {
10 | Params any `json:"params"`
11 | }
12 |
13 | type response struct {
14 | Data json.RawMessage `json:"data,omitempty"`
15 | Error *Error `json:"error,omitempty"`
16 | }
17 |
18 | type Error struct {
19 | Details []*ErrorDetail `json:"details"`
20 | }
21 |
22 | type ErrorDetail struct {
23 | Message string `json:"message"`
24 | }
25 |
26 | func (err *Error) Error() string {
27 | messages := make([]string, len(err.Details))
28 | for i, detail := range err.Details {
29 | messages[i] = detail.Message
30 | }
31 | return strings.Join(messages, "; ")
32 | }
33 |
34 | type Document struct {
35 | ID string `json:"id"`
36 | Title string `json:"title"`
37 | ContentID *string `json:"content_id"`
38 | CreatedAt time.Time `json:"created_at"`
39 | UpdatedAt time.Time `json:"updated_at"`
40 | }
41 |
42 | type DocumentParams struct {
43 | Title string `json:"title"`
44 | }
45 |
46 | type DocumentContent struct {
47 | ID string `json:"id"`
48 | CreatedAt time.Time `json:"created_at"`
49 | }
50 |
--------------------------------------------------------------------------------
/internal/builtin/plugin.go:
--------------------------------------------------------------------------------
1 | package builtin
2 |
3 | import (
4 | "log/slog"
5 |
6 | "go.opentelemetry.io/otel/trace"
7 |
8 | "github.com/blackstork-io/fabric/plugin"
9 | )
10 |
11 | const Name = "blackstork/builtin"
12 |
13 | func Plugin(version string, logger *slog.Logger, tracer trace.Tracer) *plugin.Schema {
14 | return &plugin.Schema{
15 | Name: Name,
16 | Version: version,
17 | DataSources: plugin.DataSources{
18 | "csv": makeCSVDataSource(),
19 | "txt": makeTXTDataSource(),
20 | "rss": makeRSSDataSource(),
21 | "json": makeJSONDataSource(),
22 | "yaml": makeYAMLDataSource(),
23 | "http": makeHTTPDataSource(version),
24 | "sleep": makeSleepDataSource(logger),
25 | },
26 | ContentProviders: plugin.ContentProviders{
27 | "toc": makeTOCContentProvider(),
28 | "text": makeTextContentProvider(),
29 | "title": makeTitleContentProvider(),
30 | "code": makeCodeContentProvider(),
31 | "blockquote": makeBlockQuoteContentProvider(),
32 | "image": makeImageContentProvider(),
33 | "list": makeListContentProvider(),
34 | "table": makeTableContentProvider(),
35 | "frontmatter": makeFrontMatterContentProvider(),
36 | "sleep": makeSleepContentProvider(logger),
37 | },
38 | Publishers: plugin.Publishers{
39 | "local_file": makeLocalFilePublisher(logger, tracer),
40 | "hub": makeHubPublisher(version, defaultHubClientLoader, logger, tracer),
41 | },
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/internal/builtin/plugin_test.go:
--------------------------------------------------------------------------------
1 | package builtin
2 |
3 | import (
4 | "io"
5 | "log/slog"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestPluginSchema(t *testing.T) {
12 | schema := Plugin("1.2.3", slog.New(slog.NewTextHandler(io.Discard, nil)), nil)
13 | assert.Equal(t, "blackstork/builtin", schema.Name)
14 | assert.Equal(t, "1.2.3", schema.Version)
15 | assert.NotNil(t, schema.DataSources["csv"])
16 | assert.NotNil(t, schema.DataSources["txt"])
17 | assert.NotNil(t, schema.DataSources["json"])
18 | assert.NotNil(t, schema.DataSources["rss"])
19 | // Content Providers
20 | assert.NotNil(t, schema.ContentProviders["toc"])
21 | assert.NotNil(t, schema.ContentProviders["text"])
22 | assert.NotNil(t, schema.ContentProviders["title"])
23 | assert.NotNil(t, schema.ContentProviders["code"])
24 | assert.NotNil(t, schema.ContentProviders["blockquote"])
25 | assert.NotNil(t, schema.ContentProviders["image"])
26 | assert.NotNil(t, schema.ContentProviders["list"])
27 | assert.NotNil(t, schema.ContentProviders["table"])
28 | assert.NotNil(t, schema.ContentProviders["frontmatter"])
29 | // Publishers
30 | assert.NotNil(t, schema.Publishers["local_file"])
31 | assert.NotNil(t, schema.Publishers["hub"])
32 | }
33 |
--------------------------------------------------------------------------------
/internal/builtin/testdata/csv/comma.csv:
--------------------------------------------------------------------------------
1 | id,active,name,age,height
2 | b8fa4bb0-6dd4-45ba-96e0-9a182b2b932e,true,Stacey,26,1.98
3 | b0086c49-bcd8-4aae-9f88-4f46b128e709,false,Myriam,33,1.81
4 | a12d2a8c-eebc-42b3-be52-1ab0a2969a81,true,Oralee,31,2.23
--------------------------------------------------------------------------------
/internal/builtin/testdata/csv/empty.csv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blackstork-io/fabric/122a20956a140c0413d68c061e56e9825c199082/internal/builtin/testdata/csv/empty.csv
--------------------------------------------------------------------------------
/internal/builtin/testdata/csv/invalid.csv:
--------------------------------------------------------------------------------
1 | id,name,age,height
2 | b8fa4bb0-6dd4-45ba-96e0-9a182b2b932e Stacey,26,1.98
3 | b0086c49-bcd8-4aae-9f88-4f46b128e709,Myriam,33,1.81,
4 | a12d2a8c-eebc-42b3-be52-1ab0a2969a81,Oralee,31,2.23
--------------------------------------------------------------------------------
/internal/builtin/testdata/csv/semicolon.csv:
--------------------------------------------------------------------------------
1 | id;active;name;age;height
2 | b8fa4bb0-6dd4-45ba-96e0-9a182b2b932e;true;Stacey;26;1.98
3 | b0086c49-bcd8-4aae-9f88-4f46b128e709;false;Myriam;33;1.81
4 | a12d2a8c-eebc-42b3-be52-1ab0a2969a81;true;Oralee;31;2.23
--------------------------------------------------------------------------------
/internal/builtin/testdata/json/a.json:
--------------------------------------------------------------------------------
1 | {
2 | "property_for": "a.json"
3 | }
--------------------------------------------------------------------------------
/internal/builtin/testdata/json/dir/b.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "property_for": "dir/b.json"
5 | },
6 | {
7 | "id": 2,
8 | "property_for": "dir/b.json"
9 | }
10 | ]
--------------------------------------------------------------------------------
/internal/builtin/testdata/json/dir/c.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 3,
4 | "property_for": "dir/c.json"
5 | },
6 | {
7 | "id": 4,
8 | "property_for": "dir/c.json"
9 | }
10 | ]
--------------------------------------------------------------------------------
/internal/builtin/testdata/json/invalid.txt:
--------------------------------------------------------------------------------
1 | {invalid}
--------------------------------------------------------------------------------
/internal/builtin/testdata/rss/basic.atom:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Example Feed
7 | A subtitle.
8 |
9 |
10 | urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6
11 | 2003-12-13T18:30:02Z
12 |
13 |
14 |
15 | Atom-Powered Robots Run Amok
16 |
17 |
18 |
19 | urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a
20 | 2003-11-09T17:23:02Z
21 | 2003-12-13T18:30:02Z
22 | Some text.
23 |
24 |
25 |
This is the entry content.
26 |
27 |
28 |
29 | John Doe
30 | johndoe@example.com
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/internal/builtin/testdata/rss/basic.rss:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | RSS Title
5 | This is an example of an RSS feed
6 | http://www.example.com/main.html
7 | 2020 Example.com All rights reserved
8 | Wed, 9 Sep 2010 00:01:00 +0000
9 | Sun, 6 Sep 2009 16:20:12 +0000
10 | 1800
11 |
12 | -
13 | Example entry 2
14 | Here is some text containing an interesting description.
15 | {address}/content/2
16 | 4824db5b-6278-48bd-9657-46a66de3dc1a
17 | Tue, 8 Sep 2009 22:00:00 +0000
18 |
19 | -
20 | Example entry 1
21 | Here is some text containing an interesting description.
22 | {address}/content/1
23 | 7bd204c6-1655-4c27-aeee-53f933c5395f
24 | Sun, 6 Sep 2009 16:20:23 +0000
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/internal/builtin/testdata/txt/data.txt:
--------------------------------------------------------------------------------
1 | data_content
--------------------------------------------------------------------------------
/internal/builtin/testdata/txt/empty.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blackstork-io/fabric/122a20956a140c0413d68c061e56e9825c199082/internal/builtin/testdata/txt/empty.txt
--------------------------------------------------------------------------------
/internal/builtin/testdata/yaml/a.yaml:
--------------------------------------------------------------------------------
1 | property_for: a.yaml
2 |
--------------------------------------------------------------------------------
/internal/builtin/testdata/yaml/dir/b.yaml:
--------------------------------------------------------------------------------
1 | - id: 1
2 | property_for: dir/b.yaml
3 | - id: 2
4 | property_for: dir/b.yaml
5 |
--------------------------------------------------------------------------------
/internal/builtin/testdata/yaml/dir/c.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | - id: 3
3 | property_for: dir/c.yaml
4 | - id: 4
5 | property_for: dir/c.yaml
6 |
--------------------------------------------------------------------------------
/internal/builtin/testdata/yaml/invalid.txt:
--------------------------------------------------------------------------------
1 | foo:
2 | bar
3 |
--------------------------------------------------------------------------------
/internal/builtin/utils/parsers.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "context"
5 | "encoding/csv"
6 | "encoding/json"
7 | "io"
8 |
9 | "github.com/blackstork-io/fabric/plugin/plugindata"
10 | )
11 |
12 | func ParseCSVContent(ctx context.Context, reader *csv.Reader) (plugindata.List, error) {
13 | rowMaps := make(plugindata.List, 0)
14 | headers, err := reader.Read()
15 | if err == io.EOF {
16 | return rowMaps, nil
17 | } else if err != nil {
18 | return nil, err
19 | }
20 |
21 | for {
22 | select {
23 | case <-ctx.Done(): // stop reading if the context is canceled
24 | return nil, ctx.Err()
25 | default:
26 | row, err := reader.Read()
27 | if err == io.EOF {
28 | return rowMaps, nil
29 | } else if err != nil {
30 | return nil, err
31 | }
32 | rowMap := make(plugindata.Map, len(headers))
33 | for j, header := range headers {
34 | if header == "" {
35 | continue
36 | }
37 | if j >= len(row) {
38 | rowMap[header] = nil
39 | continue
40 | }
41 | if row[j] == "true" {
42 | rowMap[header] = plugindata.Bool(true)
43 | } else if row[j] == "false" {
44 | rowMap[header] = plugindata.Bool(false)
45 | } else {
46 | n := json.Number(row[j])
47 | if f, err := n.Float64(); err == nil {
48 | rowMap[header] = plugindata.Number(f)
49 | } else {
50 | rowMap[header] = plugindata.String(row[j])
51 | }
52 | }
53 | }
54 | rowMaps = append(rowMaps, rowMap)
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/internal/crowdstrike/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/crowdstrike"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | crowdstrike.Plugin(version, nil),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/crowdstrike/plugin_test.go:
--------------------------------------------------------------------------------
1 | package crowdstrike
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/crowdstrike", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | }
14 |
--------------------------------------------------------------------------------
/internal/elastic/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/elastic"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | elastic.Plugin(version, elastic.DefaultKibanaClientLoader),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/elastic/plugin_test.go:
--------------------------------------------------------------------------------
1 | package elastic
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/elastic", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["elasticsearch"])
14 | assert.NotNil(t, schema.DataSources["elastic_security_cases"])
15 | }
16 |
--------------------------------------------------------------------------------
/internal/elastic/testdata/data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "54f7a815-eac5-4f7c-a339-5fefd0f54967",
4 | "type": "foo",
5 | "active": false,
6 | "age": 39,
7 | "name": "Davidson"
8 | },
9 | {
10 | "id": "0c68e63d-daaa-4a62-92e6-e855bd144fb6",
11 | "type": "bar",
12 | "active": false,
13 | "age": 20,
14 | "name": "Thompson"
15 | },
16 | {
17 | "id": "a117a5e6-23d0-4daa-be3c-a70900ca4163",
18 | "type": "foo",
19 | "active": true,
20 | "age": 21,
21 | "name": "Armstrong"
22 | }
23 | ]
--------------------------------------------------------------------------------
/internal/github/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/github"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | github.Plugin(version, nil),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/github/plugin.go:
--------------------------------------------------------------------------------
1 | package github
2 |
3 | import (
4 | "context"
5 |
6 | gh "github.com/google/go-github/v58/github"
7 |
8 | "github.com/blackstork-io/fabric/plugin"
9 | )
10 |
11 | var DefaultClientLoader = func(token string) Client {
12 | return &ClientAdapter{gh.NewClient(nil).WithAuthToken(token)}
13 | }
14 |
15 | const (
16 | minPage = 1
17 | pageSize = 30
18 | )
19 |
20 | type ClientLoaderFn func(token string) Client
21 |
22 | type Client interface {
23 | Issues() IssuesClient
24 | Gists() GistClient
25 | }
26 |
27 | type ClientAdapter struct {
28 | gh *gh.Client
29 | }
30 |
31 | func (c *ClientAdapter) Issues() IssuesClient {
32 | return c.gh.Issues
33 | }
34 |
35 | func (c *ClientAdapter) Gists() GistClient {
36 | return c.gh.Gists
37 | }
38 |
39 | type IssuesClient interface {
40 | ListByRepo(ctx context.Context, owner, repo string, opts *gh.IssueListByRepoOptions) ([]*gh.Issue, *gh.Response, error)
41 | }
42 |
43 | type GistClient interface {
44 | Create(ctx context.Context, gist *gh.Gist) (*gh.Gist, *gh.Response, error)
45 | Get(ctx context.Context, id string) (*gh.Gist, *gh.Response, error)
46 | Edit(ctx context.Context, id string, gist *gh.Gist) (*gh.Gist, *gh.Response, error)
47 | }
48 |
49 | func Plugin(version string, clientLoader ClientLoaderFn) *plugin.Schema {
50 | if clientLoader == nil {
51 | clientLoader = DefaultClientLoader
52 | }
53 | return &plugin.Schema{
54 | Name: "blackstork/github",
55 | Version: version,
56 | DataSources: plugin.DataSources{
57 | "github_issues": makeGithubIssuesDataSchema(clientLoader),
58 | },
59 | Publishers: plugin.Publishers{
60 | "github_gist": makeGithubGistPublisher(clientLoader),
61 | },
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/internal/github/plugin_test.go:
--------------------------------------------------------------------------------
1 | package github
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/github", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["github_issues"])
14 | assert.NotNil(t, schema.Publishers["github_gist"])
15 | }
16 |
--------------------------------------------------------------------------------
/internal/graphql/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/graphql"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | graphql.Plugin(version),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/graphql/plugin.go:
--------------------------------------------------------------------------------
1 | package graphql
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/plugin"
5 | )
6 |
7 | func Plugin(version string) *plugin.Schema {
8 | return &plugin.Schema{
9 | Name: "blackstork/graphql",
10 | Version: version,
11 | DataSources: plugin.DataSources{
12 | "graphql": makeGraphQLDataSource(),
13 | },
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/internal/graphql/plugin_test.go:
--------------------------------------------------------------------------------
1 | package graphql
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3")
11 | assert.Equal(t, "blackstork/graphql", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["graphql"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/hackerone/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/hackerone"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | hackerone.Plugin(version, hackerone.DefaultClientLoader),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/hackerone/plugin.go:
--------------------------------------------------------------------------------
1 | package hackerone
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/blackstork-io/fabric/internal/hackerone/client"
7 | "github.com/blackstork-io/fabric/plugin"
8 | "github.com/blackstork-io/fabric/plugin/dataspec"
9 | )
10 |
11 | const (
12 | minPage = 1
13 | pageSize = 25
14 | )
15 |
16 | type ClientLoadFn func(user, token string) client.Client
17 |
18 | var DefaultClientLoader ClientLoadFn = client.New
19 |
20 | func Plugin(version string, loader ClientLoadFn) *plugin.Schema {
21 | if loader == nil {
22 | loader = DefaultClientLoader
23 | }
24 | return &plugin.Schema{
25 | Name: "blackstork/hackerone",
26 | Version: version,
27 | DataSources: plugin.DataSources{
28 | "hackerone_reports": makeHackerOneReportsDataSchema(loader),
29 | },
30 | }
31 | }
32 |
33 | func makeClient(loader ClientLoadFn, cfg *dataspec.Block) (client.Client, error) {
34 | if cfg == nil {
35 | return nil, fmt.Errorf("configuration is required")
36 | }
37 | user := cfg.GetAttrVal("api_username")
38 | if user.IsNull() || user.AsString() == "" {
39 | return nil, fmt.Errorf("api_username is required in configuration")
40 | }
41 | token := cfg.GetAttrVal("api_token")
42 | if token.IsNull() || token.AsString() == "" {
43 | return nil, fmt.Errorf("api_token is required in configuration")
44 | }
45 | return loader(user.AsString(), token.AsString()), nil
46 | }
47 |
--------------------------------------------------------------------------------
/internal/hackerone/plugin_test.go:
--------------------------------------------------------------------------------
1 | package hackerone
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/hackerone", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["hackerone_reports"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/iris/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/iris"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | iris.Plugin(version, iris.DefaultClientLoader),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/iris/plugin.go:
--------------------------------------------------------------------------------
1 | package iris
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 |
7 | "github.com/hashicorp/hcl/v2"
8 |
9 | "github.com/blackstork-io/fabric/internal/iris/client"
10 | "github.com/blackstork-io/fabric/pkg/diagnostics"
11 | "github.com/blackstork-io/fabric/plugin"
12 | "github.com/blackstork-io/fabric/plugin/dataspec"
13 | )
14 |
15 | type ClientLoadFn func(url, apiKey string, insecure bool) client.Client
16 |
17 | var DefaultClientLoader ClientLoadFn = client.New
18 |
19 | func Plugin(version string, loader ClientLoadFn) *plugin.Schema {
20 | if loader == nil {
21 | loader = DefaultClientLoader
22 | }
23 | return &plugin.Schema{
24 | Name: "blackstork/iris",
25 | Doc: "The `iris` plugin for Iris Incident Response platform.",
26 | Version: version,
27 | DataSources: plugin.DataSources{
28 | "iris_cases": makeIrisCasesDataSource(loader),
29 | "iris_alerts": makeIrisAlertsDataSource(loader),
30 | },
31 | }
32 | }
33 |
34 | func parseConfig(cfg *dataspec.Block, loader ClientLoadFn) (client.Client, error) {
35 | if cfg == nil {
36 | return nil, fmt.Errorf("configuration is required")
37 | }
38 | apiURL := cfg.GetAttrVal("api_url").AsString()
39 | apiKey := cfg.GetAttrVal("api_key").AsString()
40 | insecure := cfg.GetAttrVal("insecure").True()
41 | return loader(apiURL, apiKey, insecure), nil
42 | }
43 |
44 | func handleClientError(err error) diagnostics.Diag {
45 | var clientErr *client.Error
46 | if errors.As(err, &clientErr) {
47 | return diagnostics.Diag{{
48 | Severity: hcl.DiagError,
49 | Summary: "Failed to call Iris API",
50 | Detail: clientErr.Message,
51 | }}
52 | }
53 | return diagnostics.Diag{{
54 | Severity: hcl.DiagError,
55 | Summary: "Unknown error while calling Iris API",
56 | }}
57 | }
58 |
--------------------------------------------------------------------------------
/internal/iris/plugin_test.go:
--------------------------------------------------------------------------------
1 | package iris
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/iris", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["iris_cases"])
14 | assert.NotNil(t, schema.DataSources["iris_alerts"])
15 | }
16 |
--------------------------------------------------------------------------------
/internal/microsoft/client/azure_client_test.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | "net/http/httptest"
7 | "net/url"
8 | "testing"
9 |
10 | "github.com/stretchr/testify/suite"
11 | )
12 |
13 | type ClientTestSuite struct {
14 | suite.Suite
15 | ctx context.Context
16 | cancel context.CancelFunc
17 | }
18 |
19 | func (s *ClientTestSuite) SetupTest() {
20 | s.ctx, s.cancel = context.WithCancel(context.Background())
21 | }
22 |
23 | func (s *ClientTestSuite) TearDownTest() {
24 | s.cancel()
25 | }
26 |
27 | func TestClientTestSuite(t *testing.T) {
28 | suite.Run(t, new(ClientTestSuite))
29 | }
30 |
31 | func (s *ClientTestSuite) mock(fn http.HandlerFunc, token string) (azureClient, *httptest.Server) {
32 | srv := httptest.NewServer(fn)
33 | cli := azureClient{
34 | accessToken: token,
35 | baseURL: srv.URL,
36 | client: &http.Client{},
37 | }
38 | return cli, srv
39 | }
40 |
41 | func (s *ClientTestSuite) TestPrepare() {
42 | client, srv := s.mock(func(w http.ResponseWriter, r *http.Request) {
43 | s.Equal("Bearer test_token", r.Header.Get("Authorization"))
44 | s.Equal("2023-11-01", r.URL.Query().Get("api-version"))
45 | }, "test_token")
46 | defer srv.Close()
47 | client.QueryObjects(s.ctx, "/tmp", url.Values{}, 1)
48 | }
49 |
50 | func (s *ClientTestSuite) TestGetAllReportsError() {
51 | client, srv := s.mock(func(w http.ResponseWriter, r *http.Request) {
52 | w.WriteHeader(http.StatusUnauthorized)
53 | }, "test_token")
54 | defer srv.Close()
55 | _, err := client.QueryObjects(s.ctx, "/tmp", url.Values{}, 1)
56 | s.Error(err)
57 | }
58 |
59 | func (s *ClientTestSuite) TestBaseURL() {
60 | cli := NewAzureClient("dummy-access-token")
61 | s.Equal(baseURLAzure, cli.baseURL)
62 | }
63 |
--------------------------------------------------------------------------------
/internal/microsoft/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/microsoft"
5 | "github.com/blackstork-io/fabric/internal/microsoft/client"
6 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
7 | )
8 |
9 | var version string
10 |
11 | func main() {
12 | pluginapiv1.Serve(
13 | microsoft.Plugin(
14 | version,
15 | microsoft.MakeDefaultAzureClientLoader(client.AcquireAzureToken),
16 | microsoft.MakeAzureOpenAIClientLoader(),
17 | microsoft.MakeDefaultMicrosoftGraphClientLoader(client.AcquireAzureToken),
18 | microsoft.MakeDefaultMicrosoftSecurityClientLoader(client.AcquireAzureToken),
19 | ),
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/internal/misp/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/misp"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | misp.Plugin(version, nil),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/misp/plugin_test.go:
--------------------------------------------------------------------------------
1 | package misp
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/misp", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | }
14 |
--------------------------------------------------------------------------------
/internal/nistnvd/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/nistnvd"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | nistnvd.Plugin(version, nistnvd.DefaultClientLoader),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/nistnvd/plugin.go:
--------------------------------------------------------------------------------
1 | package nistnvd
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/nistnvd/client"
5 | "github.com/blackstork-io/fabric/plugin"
6 | )
7 |
8 | type ClientLoadFn func(apiKey *string) client.Client
9 |
10 | var DefaultClientLoader ClientLoadFn = client.New
11 |
12 | func Plugin(version string, loader ClientLoadFn) *plugin.Schema {
13 | return &plugin.Schema{
14 | Name: "blackstork/nist_nvd",
15 | Version: version,
16 | DataSources: plugin.DataSources{
17 | "nist_nvd_cves": makeNistNvdCvesDataSource(loader),
18 | },
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/internal/nistnvd/plugin_test.go:
--------------------------------------------------------------------------------
1 | package nistnvd
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/nist_nvd", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["nist_nvd_cves"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/openai/client/options.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | type Option func(*client)
4 |
5 | func WithBaseURL(baseURL string) Option {
6 | return func(c *client) {
7 | c.baseURL = baseURL
8 | }
9 | }
10 |
11 | func WithOrgID(orgID string) Option {
12 | return func(c *client) {
13 | c.orgID = orgID
14 | }
15 | }
16 |
17 | func WithAPIKey(apiKey string) Option {
18 | return func(c *client) {
19 | c.apiKey = apiKey
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/internal/openai/client/types.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import "fmt"
4 |
5 | type ChatCompletionParams struct {
6 | Model string `json:"model"`
7 | Messages []ChatCompletionMessage `json:"messages"`
8 | }
9 |
10 | type ChatCompletionMessage struct {
11 | Role string `json:"role"`
12 | Content string `json:"content"`
13 | }
14 |
15 | type ChatCompletionResult struct {
16 | Choices []ChatCompletionChoice `json:"choices"`
17 | }
18 |
19 | type ChatCompletionChoice struct {
20 | FinishedReason string `json:"finish_reason"`
21 | Index int `json:"index"`
22 | Message ChatCompletionMessage `json:"message"`
23 | }
24 |
25 | type Error struct {
26 | Type string `json:"type"`
27 | Message string `json:"message"`
28 | }
29 |
30 | func (e Error) Error() string {
31 | return fmt.Sprintf("openai[%s]: %s", e.Type, e.Message)
32 | }
33 |
34 | type ErrorResponse struct {
35 | Error Error `json:"error"`
36 | }
37 |
--------------------------------------------------------------------------------
/internal/openai/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/openai"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | openai.Plugin(version, nil),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/openai/plugin.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/openai/client"
5 | "github.com/blackstork-io/fabric/plugin"
6 | "github.com/blackstork-io/fabric/plugin/dataspec"
7 | )
8 |
9 | const (
10 | defaultModel = "gpt-3.5-turbo"
11 | )
12 |
13 | type ClientLoadFn func(opts ...client.Option) client.Client
14 |
15 | var DefaultClientLoader ClientLoadFn = client.New
16 |
17 | func Plugin(version string, loader ClientLoadFn) *plugin.Schema {
18 | if loader == nil {
19 | loader = DefaultClientLoader
20 | }
21 | return &plugin.Schema{
22 | Name: "blackstork/openai",
23 | Version: version,
24 | ContentProviders: plugin.ContentProviders{
25 | "openai_text": makeOpenAITextContentSchema(loader),
26 | },
27 | }
28 | }
29 |
30 | func makeClient(loader ClientLoadFn, cfg *dataspec.Block) (client.Client, error) {
31 | opts := []client.Option{
32 | client.WithAPIKey(cfg.GetAttrVal("api_key").AsString()),
33 | }
34 | orgID := cfg.GetAttrVal("organization_id")
35 | if !orgID.IsNull() && orgID.AsString() != "" {
36 | opts = append(opts, client.WithOrgID(orgID.AsString()))
37 | }
38 | return loader(opts...), nil
39 | }
40 |
--------------------------------------------------------------------------------
/internal/openai/plugin_test.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/openai", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.ContentProviders["openai_text"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/opencti/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/opencti"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | opencti.Plugin(version),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/opencti/plugin_test.go:
--------------------------------------------------------------------------------
1 | package opencti
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3")
11 | assert.Equal(t, "blackstork/opencti", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["opencti"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/postgresql/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/postgresql"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | postgresql.Plugin(version),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/postgresql/plugin.go:
--------------------------------------------------------------------------------
1 | package postgresql
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/plugin"
5 | )
6 |
7 | func Plugin(version string) *plugin.Schema {
8 | return &plugin.Schema{
9 | Name: "blackstork/postgresql",
10 | Version: version,
11 | DataSources: plugin.DataSources{
12 | "postgresql": makePostgreSQLDataSource(),
13 | },
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/internal/postgresql/plugin_test.go:
--------------------------------------------------------------------------------
1 | package postgresql
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3")
11 | assert.Equal(t, "blackstork/postgresql", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["postgresql"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/postgresql/testdata/data.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE testdata_empty (
2 | id SERIAL PRIMARY KEY,
3 | name text
4 | );
5 |
6 | CREATE TABLE testdata (
7 | id SERIAL PRIMARY KEY,
8 | text_val text,
9 | int_val integer,
10 | bool_val boolean,
11 | null_val text DEFAULT NULL
12 | );
13 |
14 | INSERT INTO testdata (text_val, int_val, bool_val) VALUES ('text_1', 1, true);
15 | INSERT INTO testdata (text_val, int_val, bool_val, null_val) VALUES ('text_2', 2, false, 'null_val_2');
--------------------------------------------------------------------------------
/internal/snyk/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/snyk"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | snyk.Plugin(version, snyk.DefaultClientLoader),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/snyk/plugin.go:
--------------------------------------------------------------------------------
1 | package snyk
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/snyk/client"
5 | "github.com/blackstork-io/fabric/plugin"
6 | )
7 |
8 | const (
9 | pageSize = 100
10 | )
11 |
12 | type ClientLoadFn func(apiKey string) client.Client
13 |
14 | var DefaultClientLoader ClientLoadFn = client.New
15 |
16 | func Plugin(version string, loader ClientLoadFn) *plugin.Schema {
17 | if loader == nil {
18 | loader = DefaultClientLoader
19 | }
20 | return &plugin.Schema{
21 | Name: "blackstork/snyk",
22 | Version: version,
23 | DataSources: plugin.DataSources{
24 | "snyk_issues": makeSnykIssuesDataSource(loader),
25 | },
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/internal/snyk/plugin_test.go:
--------------------------------------------------------------------------------
1 | package snyk
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/snyk", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["snyk_issues"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/splunk/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/splunk"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | splunk.Plugin(version, splunk.DefaultClientLoader),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/splunk/plugin.go:
--------------------------------------------------------------------------------
1 | package splunk
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/zclconf/go-cty/cty"
7 |
8 | "github.com/blackstork-io/fabric/internal/splunk/client"
9 | "github.com/blackstork-io/fabric/plugin"
10 | "github.com/blackstork-io/fabric/plugin/dataspec"
11 | )
12 |
13 | type ClientLoadFn func(token, host, deployment string) client.Client
14 |
15 | var DefaultClientLoader ClientLoadFn = client.New
16 |
17 | func Plugin(version string, loader ClientLoadFn) *plugin.Schema {
18 | if loader == nil {
19 | loader = DefaultClientLoader
20 | }
21 | return &plugin.Schema{
22 | Name: "blackstork/splunk",
23 | Version: version,
24 | DataSources: plugin.DataSources{
25 | "splunk_search": makeSplunkSearchDataSchema(loader),
26 | },
27 | }
28 | }
29 |
30 | func makeClient(loader ClientLoadFn, cfg *dataspec.Block) (client.Client, error) {
31 | if cfg == nil {
32 | return nil, fmt.Errorf("configuration is required")
33 | }
34 |
35 | token := cfg.GetAttrVal("auth_token")
36 | if token.IsNull() || token.AsString() == "" {
37 | return nil, fmt.Errorf("auth_token is required in configuration")
38 | }
39 | host := cfg.GetAttrVal("host")
40 | if host.IsNull() {
41 | host = cty.StringVal("")
42 | }
43 | deployment := cfg.GetAttrVal("deployment_name")
44 | if deployment.IsNull() {
45 | deployment = cty.StringVal("")
46 | }
47 | if host.AsString() == "" && deployment.AsString() == "" {
48 | return nil, fmt.Errorf("host or deployment_name is required in configuration")
49 | }
50 | return loader(token.AsString(), host.AsString(), deployment.AsString()), nil
51 | }
52 |
--------------------------------------------------------------------------------
/internal/splunk/plugin_test.go:
--------------------------------------------------------------------------------
1 | package splunk
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/splunk", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["splunk_search"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/sqlite/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/sqlite"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | sqlite.Plugin(version),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/sqlite/plugin.go:
--------------------------------------------------------------------------------
1 | package sqlite
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/plugin"
5 | )
6 |
7 | func Plugin(version string) *plugin.Schema {
8 | return &plugin.Schema{
9 | Name: "blackstork/sqlite",
10 | Version: version,
11 | DataSources: plugin.DataSources{
12 | "sqlite": makeSqliteDataSource(),
13 | },
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/internal/sqlite/plugin_test.go:
--------------------------------------------------------------------------------
1 | package sqlite
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3")
11 | assert.Equal(t, "blackstork/sqlite", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["sqlite"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/stixview/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/stixview"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | stixview.Plugin(version),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/stixview/plugin.go:
--------------------------------------------------------------------------------
1 | package stixview
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/plugin"
5 | )
6 |
7 | func Plugin(version string) *plugin.Schema {
8 | return &plugin.Schema{
9 | Name: "blackstork/stixview",
10 | Version: version,
11 | ContentProviders: plugin.ContentProviders{
12 | "stixview": makeStixViewContentProvider(),
13 | },
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/internal/stixview/plugin_test.go:
--------------------------------------------------------------------------------
1 | package stixview
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3")
11 | assert.Equal(t, "blackstork/stixview", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.ContentProviders["stixview"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/terraform/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/terraform"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | terraform.Plugin(version),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/terraform/plugin.go:
--------------------------------------------------------------------------------
1 | package terraform
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/plugin"
5 | )
6 |
7 | func Plugin(version string) *plugin.Schema {
8 | return &plugin.Schema{
9 | Name: "blackstork/terraform",
10 | Version: version,
11 | DataSources: plugin.DataSources{
12 | "terraform_state_local": makeTerraformStateLocalDataSource(),
13 | },
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/internal/terraform/plugin_test.go:
--------------------------------------------------------------------------------
1 | package terraform
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3")
11 | assert.Equal(t, "blackstork/terraform", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["terraform_state_local"])
14 | }
15 |
--------------------------------------------------------------------------------
/internal/terraform/testdata/terraform.tfstate:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "serial": 0,
4 | "modules": [
5 | {
6 | "path": ["root"],
7 | "outputs": {},
8 | "resources": {}
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/internal/virustotal/client/dto.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "time"
7 | )
8 |
9 | type GetUserAPIUsageReq struct {
10 | User string `url:"-"`
11 | StartDate *Date `url:"start_date,omitempty"`
12 | EndDate *Date `url:"end_date,omitempty"`
13 | }
14 |
15 | type GetGroupAPIUsageReq struct {
16 | Group string `url:"-"`
17 | StartDate *Date `url:"start_date,omitempty"`
18 | EndDate *Date `url:"end_date,omitempty"`
19 | }
20 |
21 | type Error struct {
22 | Code string `json:"code"`
23 | Message string `json:"message"`
24 | }
25 |
26 | func (e Error) Error() string {
27 | return fmt.Sprintf("%s: %s", e.Code, e.Message)
28 | }
29 |
30 | type ErrorRes struct {
31 | Error Error `json:"error"`
32 | }
33 |
34 | type GetUserAPIUsageRes struct {
35 | Data map[string]any `json:"data"`
36 | }
37 |
38 | type GetGroupAPIUsageRes struct {
39 | Data map[string]any `json:"data"`
40 | }
41 |
42 | type Date struct {
43 | time.Time
44 | }
45 |
46 | func (d Date) EncodeValues(key string, v *url.Values) error {
47 | v.Add(key, d.Time.Format("20060102"))
48 | return nil
49 | }
50 |
--------------------------------------------------------------------------------
/internal/virustotal/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/virustotal"
5 | pluginapiv1 "github.com/blackstork-io/fabric/plugin/pluginapi/v1"
6 | )
7 |
8 | var version string
9 |
10 | func main() {
11 | pluginapiv1.Serve(
12 | virustotal.Plugin(version, virustotal.DefaultClientLoader),
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/internal/virustotal/plugin.go:
--------------------------------------------------------------------------------
1 | package virustotal
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/internal/virustotal/client"
5 | "github.com/blackstork-io/fabric/plugin"
6 | )
7 |
8 | type ClientLoadFn func(key string) client.Client
9 |
10 | var DefaultClientLoader ClientLoadFn = client.New
11 |
12 | func Plugin(version string, loader ClientLoadFn) *plugin.Schema {
13 | if loader == nil {
14 | loader = DefaultClientLoader
15 | }
16 | return &plugin.Schema{
17 | Name: "blackstork/virustotal",
18 | Version: version,
19 | DataSources: plugin.DataSources{
20 | "virustotal_api_usage": makeVirusTotalAPIUsageDataSchema(loader),
21 | },
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/internal/virustotal/plugin_test.go:
--------------------------------------------------------------------------------
1 | package virustotal
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPlugin_Schema(t *testing.T) {
10 | schema := Plugin("1.2.3", nil)
11 | assert.Equal(t, "blackstork/virustotal", schema.Name)
12 | assert.Equal(t, "1.2.3", schema.Version)
13 | assert.NotNil(t, schema.DataSources["virustotal_api_usage"])
14 | }
15 |
--------------------------------------------------------------------------------
/justfile:
--------------------------------------------------------------------------------
1 | default: build test-run
2 |
3 | build:
4 | goreleaser build --config ./.goreleaser-dev.yaml --single-target --snapshot --clean
5 |
6 | test-run:
7 | ./dist/fabric render "document.hello" --source-dir ./examples/templates/basic_hello/ -v
8 |
9 | format:
10 | go mod tidy
11 | ./codegen/format.sh
12 |
13 | format-extra: format
14 | gofumpt -w -extra .
15 |
16 | lint: format
17 | golangci-lint run
18 |
19 | test:
20 | go test -timeout 10s -race -short -v ./...
21 |
22 | test-pretty:
23 | gotestsum --format dots-v2 -- -timeout 10s -race -short -v ./...
24 |
25 | test-all:
26 | go test -timeout 5m -race -v ./...
27 |
28 | generate:
29 | ./codegen/gen_code.sh
30 |
31 | generate-docs:
32 | ./codegen/gen_docs.sh
33 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/blackstork-io/fabric/cmd"
5 | )
6 |
7 | func main() {
8 | cmd.Execute()
9 | }
10 |
--------------------------------------------------------------------------------
/parser/defined_blocks_test.go:
--------------------------------------------------------------------------------
1 | package parser_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hashicorp/hcl/v2/hclsyntax"
7 | "github.com/stretchr/testify/assert"
8 |
9 | definitions_mocks "github.com/blackstork-io/fabric/mocks/parser/definitions"
10 | "github.com/blackstork-io/fabric/parser"
11 | )
12 |
13 | func TestAddIfMissing(t *testing.T) {
14 | t.Parallel()
15 | assert := assert.New(t)
16 | m := map[string]*definitions_mocks.FabricBlock{}
17 |
18 | m1 := definitions_mocks.NewFabricBlock(t)
19 | m1.EXPECT().GetHCLBlock().Return(&hclsyntax.Block{})
20 |
21 | diag := parser.AddIfMissing(m, "key_1", m1)
22 | assert.Empty(diag)
23 | assert.Same(m1, m["key_1"])
24 |
25 | m2 := definitions_mocks.NewFabricBlock(t)
26 |
27 | diag = parser.AddIfMissing(m, "key_2", m2)
28 | assert.Empty(diag)
29 | assert.Same(m1, m["key_1"])
30 | assert.Same(m2, m["key_2"])
31 |
32 | m3 := definitions_mocks.NewFabricBlock(t)
33 | m3.EXPECT().GetHCLBlock().Return(&hclsyntax.Block{}).Once()
34 |
35 | diag = parser.AddIfMissing(m, "key_1", m3)
36 | assert.NotEmpty(diag)
37 | assert.Same(m1, m["key_1"])
38 | assert.Same(m2, m["key_2"])
39 | }
40 |
--------------------------------------------------------------------------------
/parser/definitions/config_ptr.go:
--------------------------------------------------------------------------------
1 | package definitions
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/hcl/v2"
7 |
8 | "github.com/blackstork-io/fabric/parser/evaluation"
9 | "github.com/blackstork-io/fabric/pkg/diagnostics"
10 | "github.com/blackstork-io/fabric/plugin/dataspec"
11 | )
12 |
13 | // Attribute referencing a configuration block (`config = path.to.config`).
14 | type ConfigPtr struct {
15 | Cfg *Config
16 | Ptr *hcl.Attribute
17 | }
18 |
19 | // Exists implements evaluation.Configuration.
20 | func (c *ConfigPtr) Exists() bool {
21 | return c != nil
22 | }
23 |
24 | // ParseConfig implements Configuration.
25 | func (c *ConfigPtr) ParseConfig(ctx context.Context, spec *dataspec.RootSpec) (val *dataspec.Block, diags diagnostics.Diag) {
26 | return c.Cfg.ParseConfig(ctx, spec)
27 | }
28 |
29 | // Range implements Configuration.
30 | func (c *ConfigPtr) Range() hcl.Range {
31 | // Use the location of "config = *traversal*" for error reporting, not original config's Range
32 | return c.Ptr.Range
33 | }
34 |
35 | var _ evaluation.Configuration = (*ConfigPtr)(nil)
36 |
--------------------------------------------------------------------------------
/parser/definitions/definitions.go:
--------------------------------------------------------------------------------
1 | package definitions
2 |
3 | import (
4 | "github.com/hashicorp/hcl/v2/hclsyntax"
5 | "github.com/zclconf/go-cty/cty"
6 | )
7 |
8 | const (
9 | BlockKindDocument = "document"
10 | BlockKindConfig = "config"
11 | BlockKindContent = "content"
12 | BlockKindPublish = "publish"
13 | BlockKindData = "data"
14 | BlockKindMeta = "meta"
15 | BlockKindVars = "vars"
16 | BlockKindSection = "section"
17 | BlockKindGlobalConfig = "fabric"
18 | BlockKindDynamic = "dynamic"
19 |
20 | PluginTypeRef = "ref"
21 | AttrRefBase = "base"
22 | AttrTitle = "title"
23 | AttrDependsOn = "depends_on"
24 | AttrLocalVar = "local_var"
25 | AttrRequiredVars = "required_vars"
26 | AttrIsIncluded = "is_included"
27 | AttrDynamicItems = "items"
28 | )
29 |
30 | type FabricBlock interface {
31 | GetHCLBlock() *hclsyntax.Block
32 | CtyType() cty.Type
33 | }
34 |
35 | func ToCtyValue(b FabricBlock) cty.Value {
36 | return cty.CapsuleVal(b.CtyType(), b)
37 | }
38 |
39 | // Identifies a plugin block
40 | type Key struct {
41 | PluginKind string
42 | PluginName string
43 | BlockName string
44 | }
45 |
--------------------------------------------------------------------------------
/parser/definitions/document.go:
--------------------------------------------------------------------------------
1 | package definitions
2 |
3 | import (
4 | "github.com/hashicorp/hcl/v2"
5 | "github.com/hashicorp/hcl/v2/hclsyntax"
6 | "github.com/zclconf/go-cty/cty"
7 |
8 | "github.com/blackstork-io/fabric/pkg/diagnostics"
9 | "github.com/blackstork-io/fabric/pkg/encapsulator"
10 | )
11 |
12 | type Document struct {
13 | Block *hclsyntax.Block
14 | Name string
15 | Meta *MetaBlock
16 | }
17 |
18 | var _ FabricBlock = (*Document)(nil)
19 |
20 | func (d *Document) GetHCLBlock() *hclsyntax.Block {
21 | return d.Block
22 | }
23 |
24 | var ctyDocumentType = encapsulator.NewEncoder[Document]("document", nil)
25 |
26 | func (d *Document) CtyType() cty.Type {
27 | return ctyDocumentType.CtyType()
28 | }
29 |
30 | func DefineDocument(block *hclsyntax.Block) (doc *Document, diags diagnostics.Diag) {
31 | diags.Append(validateBlockName(block, 0, true))
32 | diags.Append(validateLabelsLength(block, 1, "document_name"))
33 | if diags.HasErrors() {
34 | return
35 | }
36 |
37 | if block.Labels[0] == AttrRefBase {
38 | diags.Append(&hcl.Diagnostic{
39 | Severity: hcl.DiagError,
40 | Summary: "Invalid document declaration",
41 | Detail: "Documents can't be refs, only sections can",
42 | Subject: &block.LabelRanges[0],
43 | Context: block.DefRange().Ptr(),
44 | })
45 | }
46 |
47 | doc = &Document{
48 | Block: block,
49 | Name: block.Labels[0],
50 | }
51 | return
52 | }
53 |
--------------------------------------------------------------------------------
/parser/definitions/dynamic.go:
--------------------------------------------------------------------------------
1 | package definitions
2 |
3 | import (
4 | "github.com/hashicorp/hcl/v2/hclsyntax"
5 | )
6 |
7 | type Dynamic struct {
8 | ParseResult *ParsedDynamic
9 | }
10 |
11 | type ParsedDynamic struct {
12 | Block *hclsyntax.Block
13 | // Items is a list of items to be iterated over dynamically.
14 | // Always present.
15 | Items *hclsyntax.Attribute
16 | Content []*ParsedContent
17 | }
18 |
--------------------------------------------------------------------------------
/parser/definitions/meta.go:
--------------------------------------------------------------------------------
1 | package definitions
2 |
3 | import (
4 | "slices"
5 |
6 | "github.com/blackstork-io/fabric/plugin/plugindata"
7 | )
8 |
9 | type MetaBlock struct {
10 | Name string `hcl:"name,optional"`
11 | Description string `hcl:"description,optional"`
12 | Url string `hcl:"url,optional"`
13 | License string `hcl:"license,optional"`
14 | Authors []string `hcl:"authors,optional"`
15 | Tags []string `hcl:"tags,optional"`
16 | UpdatedAt string `hcl:"updated_at,optional"`
17 | Version string `hcl:"version,optional"`
18 |
19 | // TODO: ?store def range defRange hcl.Range
20 | }
21 |
22 | func (m *MetaBlock) MatchesTags(requiredTags []string) bool {
23 | var tags []string
24 | if m != nil {
25 | tags = m.Tags
26 | }
27 | if len(tags) < len(requiredTags) {
28 | return false
29 | }
30 | for _, tag := range requiredTags {
31 | if !slices.Contains(tags, tag) {
32 | return false
33 | }
34 | }
35 | return true
36 | }
37 |
38 | func (m *MetaBlock) AsPluginData() plugindata.Data {
39 | tags := make(plugindata.List, len(m.Tags))
40 | authors := make(plugindata.List, len(m.Authors))
41 | for i, tag := range m.Tags {
42 | tags[i] = plugindata.String(tag)
43 | }
44 | for i, author := range m.Authors {
45 | authors[i] = plugindata.String(author)
46 | }
47 | return plugindata.Map{
48 | "authors": authors,
49 | "name": plugindata.String(m.Name),
50 | "tags": tags,
51 | "version": plugindata.String(m.Version),
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/parser/definitions/parsed_document.go:
--------------------------------------------------------------------------------
1 | package definitions
2 |
3 | type ParsedDocument struct {
4 | Source *Document
5 | Meta *MetaBlock
6 | Vars *ParsedVars
7 | RequiredVars []string
8 | Content []*ParsedContent
9 | Data []*ParsedPlugin
10 | Publish []*ParsedPlugin
11 | }
12 |
--------------------------------------------------------------------------------
/parser/definitions/parsed_plugin.go:
--------------------------------------------------------------------------------
1 | package definitions
2 |
3 | import (
4 | "github.com/hashicorp/hcl/v2/hclsyntax"
5 |
6 | "github.com/blackstork-io/fabric/parser/evaluation"
7 | )
8 |
9 | type ParsedPlugin struct {
10 | Source *Plugin
11 | PluginName string
12 | BlockName string
13 | Meta *MetaBlock
14 | Config evaluation.Configuration
15 | Invocation *evaluation.BlockInvocation
16 | Vars *ParsedVars
17 | RequiredVars []string
18 | DependsOn []string
19 | IsIncluded *hclsyntax.Attribute
20 | }
21 |
22 | type ParsedContent struct {
23 | Section *ParsedSection
24 | Plugin *ParsedPlugin
25 | Dynamic *ParsedDynamic
26 | }
27 |
--------------------------------------------------------------------------------
/parser/definitions/vars.go:
--------------------------------------------------------------------------------
1 | package definitions
2 |
3 | import (
4 | "maps"
5 | "slices"
6 |
7 | "github.com/blackstork-io/fabric/plugin/dataspec"
8 | )
9 |
10 | const LocalVarName = "local"
11 |
12 | type ParsedVars struct {
13 | // stored in the order of definition
14 | Variables []*dataspec.Attr
15 | ByName map[string]int
16 | }
17 |
18 | func (pv *ParsedVars) Empty() bool {
19 | return pv == nil || len(pv.Variables) == 0
20 | }
21 |
22 | // MergeWithBaseVars handles merging with vars from ref base.
23 | // Shadowing has different rules, and will be handled at the evaluation stage.
24 | func (pv *ParsedVars) MergeWithBaseVars(baseVars *ParsedVars) *ParsedVars {
25 | if pv.Empty() {
26 | return baseVars
27 | }
28 | if baseVars.Empty() {
29 | return pv
30 | }
31 |
32 | vars := slices.Clone(baseVars.Variables)
33 | byName := maps.Clone(baseVars.ByName)
34 | for _, v := range pv.Variables {
35 | if idx, found := byName[v.Name]; found {
36 | // redefine, but keep the definition order
37 | vars[idx] = v
38 | } else {
39 | byName[v.Name] = len(vars)
40 | vars = append(vars, v)
41 | }
42 | }
43 | return &ParsedVars{
44 | Variables: vars,
45 | ByName: byName,
46 | }
47 | }
48 |
49 | // AppendVar append a variable to the parsed vars struct (last in evaluation order).
50 | func (pv *ParsedVars) AppendVar(variable *dataspec.Attr) {
51 | idx := len(pv.Variables)
52 | pv.Variables = append(pv.Variables, variable)
53 | if idx == 0 {
54 | pv.ByName = make(map[string]int)
55 | }
56 | pv.ByName[variable.Name] = idx
57 | }
58 |
--------------------------------------------------------------------------------
/parser/evaluation/block_invocation.go:
--------------------------------------------------------------------------------
1 | package evaluation
2 |
3 | import (
4 | "github.com/hashicorp/hcl/v2/hclsyntax"
5 | )
6 |
7 | type BlockInvocation struct {
8 | *hclsyntax.Block
9 | }
10 |
--------------------------------------------------------------------------------
/parser/evaluation/evaluation.go:
--------------------------------------------------------------------------------
1 | package evaluation
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/hcl/v2"
7 |
8 | "github.com/blackstork-io/fabric/pkg/diagnostics"
9 | "github.com/blackstork-io/fabric/plugin/dataspec"
10 | )
11 |
12 | // To act as a plugin configuration struct must implement this interface.
13 | type Configuration interface {
14 | ParseConfig(ctx context.Context, spec *dataspec.RootSpec) (*dataspec.Block, diagnostics.Diag)
15 | Range() hcl.Range
16 | Exists() bool
17 | }
18 |
--------------------------------------------------------------------------------
/parser/parse_title.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/hcl/v2"
7 | "github.com/hashicorp/hcl/v2/hclsyntax"
8 | "github.com/zclconf/go-cty/cty"
9 |
10 | "github.com/blackstork-io/fabric/parser/definitions"
11 | "github.com/blackstork-io/fabric/pkg/diagnostics"
12 | "github.com/blackstork-io/fabric/pkg/utils"
13 | )
14 |
15 | func (db *DefinedBlocks) ParseTitle(ctx context.Context, title *hclsyntax.Attribute) (res *definitions.ParsedContent, diags diagnostics.Diag) {
16 | const pluginName = "title"
17 |
18 | value := *title
19 | value.Name = "value"
20 |
21 | relativeSize := *title
22 | relativeSize.Name = "relative_size"
23 | relativeSize.Expr = &hclsyntax.LiteralValueExpr{
24 | Val: cty.NumberIntVal(-1),
25 | SrcRange: title.Expr.Range(),
26 | }
27 |
28 | block := &hclsyntax.Block{
29 | Type: definitions.BlockKindContent,
30 | TypeRange: title.NameRange,
31 | Labels: []string{pluginName},
32 | LabelRanges: []hcl.Range{title.NameRange},
33 | Body: &hclsyntax.Body{
34 | Attributes: hclsyntax.Attributes{
35 | "value": &value,
36 | "relative_size": &relativeSize,
37 | },
38 | SrcRange: title.SrcRange,
39 | EndRange: utils.RangeEnd(title.Expr.Range()),
40 | },
41 | OpenBraceRange: utils.RangeStart(title.NameRange),
42 | CloseBraceRange: utils.RangeEnd(title.Expr.Range()),
43 | }
44 | def, diag := definitions.DefinePlugin(block, false)
45 | if diags.Extend(diag) {
46 | return
47 | }
48 | parsed, diag := db.ParsePlugin(ctx, def)
49 | if diags.Extend(diag) {
50 | return
51 | }
52 | res = &definitions.ParsedContent{
53 | Plugin: parsed,
54 | }
55 | return
56 | }
57 |
--------------------------------------------------------------------------------
/parser/parser_test.go:
--------------------------------------------------------------------------------
1 | package parser_test
2 |
3 | import (
4 | "testing"
5 | "testing/fstest"
6 |
7 | "github.com/stretchr/testify/assert"
8 |
9 | "github.com/blackstork-io/fabric/parser"
10 | )
11 |
12 | func TestFindFiles(t *testing.T) {
13 | t.Parallel()
14 | assert := assert.New(t)
15 | fs := fstest.MapFS{
16 | "f1.fabric": &fstest.MapFile{},
17 | "f2.fAbRiC": &fstest.MapFile{},
18 | "f3.not_fabric": &fstest.MapFile{},
19 | "subdir/f4.fAbRiC": &fstest.MapFile{},
20 | "subdir/f5.not_fabric": &fstest.MapFile{},
21 | }
22 |
23 | type testCase struct {
24 | name string
25 | recursive bool
26 | expected []string
27 | }
28 |
29 | testCases := []testCase{
30 | {
31 | name: "Recursive",
32 | recursive: true,
33 | expected: []string{
34 | "f1.fabric",
35 | "f2.fAbRiC",
36 | "subdir/f4.fAbRiC",
37 | },
38 | },
39 | {
40 | name: "Non-recursive",
41 | recursive: false,
42 | expected: []string{
43 | "f1.fabric",
44 | "f2.fAbRiC",
45 | },
46 | },
47 | }
48 | for _, tc := range testCases {
49 | tc := tc
50 |
51 | t.Run(tc.name, func(t *testing.T) {
52 | t.Parallel()
53 | var res []string
54 |
55 | diags := parser.FindFabricFiles(fs, tc.recursive, func(path string) {
56 | res = append(res, path)
57 | })
58 |
59 | assert.Equal(
60 | tc.expected,
61 | res,
62 | )
63 | assert.Empty(diags)
64 | })
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/parser/traversal.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/hashicorp/hcl/v2"
7 | "github.com/zclconf/go-cty/cty"
8 |
9 | "github.com/blackstork-io/fabric/parser/definitions"
10 | "github.com/blackstork-io/fabric/pkg/diagnostics"
11 | )
12 |
13 | func Resolve[B definitions.FabricBlock](db *DefinedBlocks, expr hcl.Expression) (res B, diags diagnostics.Diag) {
14 | resAny, diags := db.resolve(expr, res.CtyType())
15 | if diags.HasErrors() {
16 | return
17 | }
18 | res = resAny.(B) //nolint:forcetypeassert // This type assertion is done via cty in db.resolve
19 | return
20 | }
21 |
22 | func (db *DefinedBlocks) resolve(expr hcl.Expression, expectedType cty.Type) (res any, diags diagnostics.Diag) {
23 | val, diag := expr.Value(&hcl.EvalContext{
24 | Variables: db.AsValueMap(),
25 | })
26 | if diags.Extend(diag) {
27 | return
28 | }
29 | ty := val.Type()
30 | if !ty.Equals(expectedType) {
31 | diags.Append(&hcl.Diagnostic{
32 | Severity: hcl.DiagError,
33 | Summary: "Incorrect reference",
34 | Detail: fmt.Sprintf(
35 | "Expected reference to '%s' got reference to '%s'",
36 | expectedType.FriendlyName(),
37 | ty.FriendlyName(),
38 | ),
39 | Subject: expr.Range().Ptr(),
40 | })
41 | return
42 | }
43 | res = val.EncapsulatedValue()
44 | return
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/diagnostics/extra.go:
--------------------------------------------------------------------------------
1 | package diagnostics
2 |
3 | import "github.com/hashicorp/hcl/v2"
4 |
5 | type extraList []any
6 |
7 | func getExtra[T any](extra any) (_ T, _ bool) {
8 | for extra != nil {
9 | if list, ok := extra.(extraList); ok {
10 | for _, extra := range list {
11 | val, found := getExtra[T](extra)
12 | if found {
13 | return val, found
14 | }
15 | }
16 | return
17 | }
18 | if val, ok := extra.(T); ok {
19 | return val, true
20 | }
21 | if val, ok := extra.(hcl.DiagnosticExtraUnwrapper); ok {
22 | extra = val.UnwrapDiagnosticExtra()
23 | continue
24 | }
25 | return
26 | }
27 | return
28 | }
29 |
30 | // GetExtra finds extra of type T in a provided diagnostic
31 | func GetExtra[T any](diag *hcl.Diagnostic) (extra T, found bool) {
32 | return getExtra[T](diag.Extra)
33 | }
34 |
35 | // DiagnosticsGetExtra finds the first extra of type T in the slice of provided diagnostics
36 | func DiagnosticsGetExtra[T any](diags Diag) (extra T, found bool) {
37 | for _, diag := range diags {
38 | if extra, found = getExtra[T](diag.Extra); found {
39 | return
40 | }
41 | }
42 | return
43 | }
44 |
45 | func FindByExtra[T any](diags Diag) *hcl.Diagnostic {
46 | for _, diag := range diags {
47 | if _, found := getExtra[T](diag.Extra); found {
48 | return diag
49 | }
50 | }
51 | return nil
52 | }
53 |
54 | // Adds extra without replacing existing extras.
55 | func addExtraFunc(diag *hcl.Diagnostic, extra any) {
56 | switch extraT := diag.Extra.(type) {
57 | case nil:
58 | diag.Extra = extra
59 | case extraList:
60 | extraT = append(extraT, extra)
61 | diag.Extra = extraT
62 | default:
63 | diag.Extra = extraList{extraT, extra}
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/diagnostics/printer.go:
--------------------------------------------------------------------------------
1 | package diagnostics
2 |
3 | import (
4 | "bufio"
5 | "io"
6 | "log/slog"
7 | "os"
8 |
9 | "github.com/hashicorp/hcl/v2"
10 | "github.com/mattn/go-colorable"
11 | "golang.org/x/term"
12 | )
13 |
14 | func PrintDiags(output io.Writer, diags []*hcl.Diagnostic, fileMap map[string]*hcl.File, colorize bool) {
15 | if len(diags) == 0 {
16 | return
17 | }
18 | width := 80
19 | if file, ok := output.(*os.File); ok {
20 | termWidth, _, err := term.GetSize(int(file.Fd()))
21 | if err == nil && termWidth > 0 {
22 | width = termWidth
23 | }
24 | if colorize {
25 | output = colorable.NewColorable(file)
26 | }
27 | }
28 |
29 | bufWr := bufio.NewWriter(output)
30 | defer func() {
31 | err := bufWr.Flush()
32 | if err != nil {
33 | slog.Error("Failed to flush diagnostics", "err", err)
34 | }
35 | }()
36 |
37 | // Convert width to uint safely to avoid potential overflow
38 | var diagWidth uint
39 | if width > 0 {
40 | diagWidth = uint(width)
41 | } else {
42 | diagWidth = 80 // Default width
43 | }
44 |
45 | diagWriter := hcl.NewDiagnosticTextWriter(bufWr, fileMap, diagWidth, colorize)
46 |
47 | for _, diag := range diags {
48 | if _, isRepeated := GetExtra[repeatedError](diag); isRepeated {
49 | continue
50 | }
51 | if gojqErr, ok := GetExtra[GoJQError](diag); ok {
52 | gojqErr.improveDiagnostic(diag, fileMap)
53 | }
54 | if traceback, ok := GetExtra[TracebackExtra](diag); ok {
55 | traceback.improveDiagnostic(diag)
56 | }
57 | if path, ok := GetExtra[PathExtra](diag); ok {
58 | path.improveDiagnostic(diag)
59 | }
60 | err := diagWriter.WriteDiagnostic(diag)
61 | if err != nil {
62 | slog.Error("Failed to write diagnostics", "err", err)
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/diagnostics/refine.go:
--------------------------------------------------------------------------------
1 | package diagnostics
2 |
3 | import (
4 | "github.com/hashicorp/hcl/v2"
5 | "github.com/zclconf/go-cty/cty"
6 | )
7 |
8 | type Refiner interface {
9 | Refine(diags Diag)
10 | }
11 |
12 | // Set Summary field if empty
13 | type DefaultSummary string
14 |
15 | func (ds DefaultSummary) Refine(diags Diag) {
16 | for _, d := range diags {
17 | if d.Summary == "" {
18 | d.Summary = string(ds)
19 | }
20 | }
21 | }
22 |
23 | // Set Subject field if empty
24 | type DefaultSubject hcl.Range
25 |
26 | func (ds DefaultSubject) Refine(diags Diag) {
27 | for _, d := range diags {
28 | if d.Subject == nil {
29 | d.Subject = (*hcl.Range)(&ds)
30 | }
31 | }
32 | }
33 |
34 | // Adds an extra without replacing existing extras.
35 | func AddExtra(extra any) Refiner {
36 | switch eT := extra.(type) {
37 | case *PathExtra:
38 | return eT
39 | case cty.Path:
40 | return AddPath(eT)
41 | default:
42 | return &extraAdder{eT}
43 | }
44 | }
45 |
46 | type extraAdder struct {
47 | extra any
48 | }
49 |
50 | func (ae *extraAdder) Refine(diags Diag) {
51 | for _, d := range diags {
52 | addExtraFunc(d, ae.extra)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/diagnostics/repeated.go:
--------------------------------------------------------------------------------
1 | package diagnostics
2 |
3 | import "github.com/hashicorp/hcl/v2"
4 |
5 | type repeatedError struct{}
6 |
7 | // Invisible to user error, typically used to signal that the initial block evaluation
8 | // has failed (and already has reported its errors to user).
9 | var RepeatedError = &hcl.Diagnostic{
10 | Severity: hcl.DiagError,
11 | Extra: repeatedError{},
12 | }
13 |
--------------------------------------------------------------------------------
/pkg/diagnostics/traceback.go:
--------------------------------------------------------------------------------
1 | package diagnostics
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/hashicorp/hcl/v2"
7 | )
8 |
9 | type TracebackExtra = *tracebackExtra
10 |
11 | type tracebackExtra struct {
12 | Traceback []*hcl.Range
13 | }
14 |
15 | func (tb *tracebackExtra) improveDiagnostic(diag *hcl.Diagnostic) {
16 | sb := []byte(diag.Detail)
17 | for _, rng := range tb.Traceback {
18 | if rng != nil {
19 | sb = fmt.Appendf(sb, "\n at %s:%d:%d",
20 | rng.Filename, rng.Start.Line, rng.Start.Column,
21 | )
22 | } else {
23 | sb = append(sb, "\n at "...)
24 | }
25 | }
26 | diag.Detail = string(sb)
27 | }
28 |
29 | func NewTracebackExtra() TracebackExtra {
30 | return &tracebackExtra{}
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/encapsulator/codec.go:
--------------------------------------------------------------------------------
1 | package encapsulator
2 |
3 | import (
4 | "reflect"
5 | )
6 |
7 | // Codec is both an Encoder and a Decoder for *T.
8 | type Codec[T any] struct {
9 | Decoder[*T]
10 | Encoder[T]
11 | }
12 |
13 | // NewCodec creates a Codec (encoder + decoder).
14 | // Will use cty.Capsule if capsuleOps is nil, and cty.CapsuleWithOps otherwise
15 | func NewCodec[T any](friendlyName string, capsuleOps *CapsuleOps[T]) *Codec[T] {
16 | goType := reflect.TypeFor[T]()
17 | codec := &Codec[T]{
18 | Encoder: Encoder[T]{},
19 | Decoder: Decoder[*T]{
20 | decoderCore: decoderCore{
21 | goType: reflect.PointerTo(goType),
22 | },
23 | },
24 | }
25 | codec.initEncoderCore(friendlyName, goType, capsuleOps)
26 | return codec
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/encapsulator/util.go:
--------------------------------------------------------------------------------
1 | // Type-safe conversions to and from cty.CapsuleValue
2 | package encapsulator
3 |
4 | import (
5 | "github.com/zclconf/go-cty/cty"
6 | )
7 |
8 | // isValid tests that cty.Value is safe to work with.
9 | func isValid(v cty.Value) bool {
10 | return !v.IsNull() && v.IsKnown()
11 | }
12 |
13 | // Compatible checks that values produced by the given Encoder are decodable by the given Decoder.
14 | // This is a loose check, for example it allows decoding value that implements interface DT.
15 | func Compatible(encoder EncoderI, decoder DecoderI) bool {
16 | return decoder.Decodable(encoder.CtyType())
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/jsontools/jsontools.go:
--------------------------------------------------------------------------------
1 | package jsontools
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | )
8 |
9 | var (
10 | ErrTraversal = errors.New("traversal failed")
11 | ErrUnmarshalBytes = errors.New("expected a slice of bytes")
12 | )
13 |
14 | func MapSet(m map[string]any, keys []string, val any) (map[string]any, error) {
15 | curMap := m
16 |
17 | if len(keys) == 0 {
18 | return m, ErrTraversal
19 | }
20 |
21 | for _, k := range keys[:len(keys)-1] {
22 | v, found := curMap[k]
23 | if found {
24 | var ok bool
25 | if curMap, ok = v.(map[string]any); !ok {
26 | return m, ErrTraversal
27 | }
28 | } else {
29 | nextMap := map[string]any{}
30 | curMap[k] = nextMap
31 | curMap = nextMap
32 | }
33 | }
34 |
35 | curMap[keys[len(keys)-1]] = val
36 | return m, nil
37 | }
38 |
39 | func MapGet(m any, keys []string) (val any, err error) {
40 | if len(keys) == 0 {
41 | err = ErrTraversal
42 | return
43 | }
44 | val = m
45 | for _, k := range keys {
46 | asMap, ok := val.(map[string]any)
47 | if !ok {
48 | err = ErrTraversal
49 | return
50 | }
51 | val, ok = asMap[k]
52 | if !ok {
53 | err = ErrTraversal
54 | return
55 | }
56 | }
57 | return
58 | }
59 |
60 | func Dump(obj any) string {
61 | objBytes, err := json.Marshal(obj)
62 | if err != nil {
63 | objBytes = []byte(fmt.Sprintf("Failed to dump the object as json: %s", err))
64 | }
65 | return string(objBytes)
66 | }
67 |
68 | func UnmarshalBytes(bytes, value any) error {
69 | data, ok := bytes.([]byte)
70 | if !ok {
71 | return ErrUnmarshalBytes
72 | }
73 | return json.Unmarshal(data, value)
74 | }
75 |
--------------------------------------------------------------------------------
/pkg/parexec/limiter.go:
--------------------------------------------------------------------------------
1 | package parexec
2 |
3 | import (
4 | "runtime"
5 | "sync"
6 | )
7 |
8 | // NoLimit doesn't limit the degree of parallel execution.
9 | var NoLimit = (*Limiter)(nil)
10 |
11 | // DiskIOLimiter is a shared limiter for all Disk IO tasks.
12 | var DiskIOLimiter = NewLimiter(4)
13 |
14 | // CPULimiter is a shared limiter for all CPU-bound tasks.
15 | var CPULimiter = NewLimiter(2 * runtime.NumCPU())
16 |
17 | // 4 -> 4+2
18 | // 8 -> 8+3
19 | // 16 -> 16+4
20 |
21 | type Limiter struct {
22 | cond sync.Cond
23 | available int
24 | total int
25 | }
26 |
27 | // Limits parallel executions to at most limit simultaneously.
28 | //
29 | // Can be shared between multiple [ParExecutor]s.
30 | func NewLimiter(limit int) *Limiter {
31 | l := &Limiter{
32 | available: limit,
33 | total: limit,
34 | }
35 | l.cond = *sync.NewCond(&sync.Mutex{})
36 | return l
37 | }
38 |
39 | // Takes a limiter token. Must [Return] it after.
40 | func (l *Limiter) Take() {
41 | l.cond.L.Lock()
42 | for l.available <= 0 {
43 | l.cond.Wait()
44 | }
45 | l.available--
46 | l.cond.L.Unlock()
47 | }
48 |
49 | // Returns a token taken with Take.
50 | func (l *Limiter) Return() {
51 | l.cond.L.Lock()
52 | l.available++
53 | l.cond.L.Unlock()
54 | l.cond.Signal()
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/parexec/limiter_test.go:
--------------------------------------------------------------------------------
1 | package parexec
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestLimiterOnPanicingJob(t *testing.T) {
10 | defer func() {
11 | if r := recover(); r == nil {
12 | t.Log("panic caught!", r)
13 | }
14 | }()
15 |
16 | t.Parallel()
17 | assert := assert.New(t)
18 |
19 | limiter := NewLimiter(4)
20 |
21 | var resArr []int
22 |
23 | pe := New(limiter, func(res, idx int) (cmd Command) {
24 | resArr = append(resArr, res)
25 | return
26 | })
27 |
28 | for i := 0; i < 8; i++ {
29 | pe.Go(func() int {
30 | panic("panicErr")
31 | // return 0
32 | })
33 | }
34 |
35 | wasStopped := pe.WaitDoneAndLock()
36 | assert.False(wasStopped)
37 |
38 | limiter.cond.L.Lock()
39 | assert.Equal(limiter.total, limiter.available)
40 | limiter.cond.L.Unlock()
41 |
42 | pe.UnlockResume()
43 | // check that everything is still usable after panic in the executor
44 | for i := 0; i < 8; i++ {
45 | pe.Go(func() int {
46 | return 1
47 | })
48 | }
49 |
50 | wasStopped = pe.WaitDoneAndLock()
51 |
52 | assert.False(wasStopped)
53 | limiter.cond.L.Lock()
54 | assert.Equal(limiter.total, limiter.available)
55 | limiter.cond.L.Unlock()
56 |
57 | assert.Len(resArr, 8)
58 |
59 | for _, val := range resArr {
60 | assert.Equal(1, val)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/utils/golang.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "reflect"
5 | )
6 |
7 | // Correct version of nil check, works on nil interfaces as well as any other value.
8 | func IsNil(val any) bool {
9 | // Checking for nil on interface objects is terrible
10 | // Thanks to: https://stackoverflow.com/a/76595928/4632951
11 | if val == nil {
12 | return true
13 | }
14 | v := reflect.ValueOf(val)
15 | k := v.Kind()
16 | switch k {
17 | case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer,
18 | reflect.UnsafePointer, reflect.Interface, reflect.Slice:
19 | return v.IsNil()
20 | }
21 |
22 | return false
23 | }
24 |
25 | // Unwraps the value, panics if err is not nil.
26 | func Must[T any](val T, err error) T {
27 | if err != nil {
28 | panic(err)
29 | }
30 | return val
31 | }
32 |
33 | // Clone performs a shallow clone operation on pointer value
34 | func Clone[T any](val *T) *T {
35 | if val == nil {
36 | return nil
37 | }
38 | valC := *val
39 | return &valC
40 | }
41 |
--------------------------------------------------------------------------------
/pkg/utils/maps_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestPop(t *testing.T) {
10 | t.Parallel()
11 | assert := assert.New(t)
12 | var m map[string]int
13 | v, found := Pop(m, "d")
14 | assert.Zero(v)
15 | assert.False(found)
16 |
17 | m = map[string]int{
18 | "a": 1,
19 | "b": 2,
20 | "c": 3,
21 | }
22 | v, found = Pop(m, "d")
23 | assert.Zero(v)
24 | assert.False(found)
25 |
26 | assert.Contains(m, "a")
27 | v, found = Pop(m, "a")
28 | assert.Equal(1, v)
29 | assert.True(found)
30 |
31 | assert.Equal(
32 | map[string]int{
33 | "b": 2,
34 | "c": 3,
35 | },
36 | m,
37 | )
38 | }
39 |
40 | func TestSliceToSet(t *testing.T) {
41 | t.Parallel()
42 | assert := assert.New(t)
43 | var slice []string
44 |
45 | assert.Equal(map[string]struct{}{}, SliceToSet(slice))
46 |
47 | slice = []string{"a", "b", "c", "c", "b"}
48 |
49 | assert.Equal(map[string]struct{}{
50 | "a": {},
51 | "b": {},
52 | "c": {},
53 | }, SliceToSet(slice))
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/utils/slices.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "github.com/blackstork-io/fabric/pkg/diagnostics"
4 |
5 | // Sets slice[idx] = val, growing the slice if needed, and returns the updated slice.
6 | func SetAt[T any](slice []T, idx int, val T) []T {
7 | needToAlloc := idx - len(slice)
8 | switch {
9 | case needToAlloc > 0:
10 | slice = append(slice, make([]T, needToAlloc)...)
11 |
12 | fallthrough
13 | case needToAlloc == 0:
14 | slice = append(slice, val)
15 | default:
16 | slice[idx] = val
17 | }
18 | return slice
19 | }
20 |
21 | // Produce a new slice by applying function fn to items of the slice s.
22 | func FnMap[I, O any](s []I, fn func(I) O) []O {
23 | if s == nil {
24 | return nil
25 | }
26 | out := make([]O, len(s))
27 | for i, v := range s {
28 | out[i] = fn(v)
29 | }
30 | return out
31 | }
32 |
33 | // Produce a new slice by applying (possibly erroring) function fn to items of the slice s.
34 | // Returns on the first error with nil slice.
35 | func FnMapErr[I, O any](s []I, fn func(I) (O, error)) (out []O, err error) {
36 | if s == nil {
37 | return nil, nil
38 | }
39 | out = make([]O, len(s))
40 | for i, v := range s {
41 | out[i], err = fn(v)
42 | if err != nil {
43 | out = nil
44 | break
45 | }
46 | }
47 | return
48 | }
49 |
50 | // Produce a new slice by applying function fn to items of the slice s.
51 | // Collects slice-like errors from the second return value (diagnostics in our case)
52 | func FnMapDiags[I, O any](diags *diagnostics.Diag, s []I, fn func(I) (O, diagnostics.Diag)) []O {
53 | if s == nil {
54 | return nil
55 | }
56 | var diag diagnostics.Diag
57 | out := make([]O, len(s))
58 | for i, v := range s {
59 | out[i], diag = fn(v)
60 | diags.Extend(diag)
61 | }
62 | return out
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/utils/slices_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "slices"
5 | "strconv"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestSetAt(t *testing.T) {
12 | t.Parallel()
13 |
14 | type testCase struct {
15 | name string
16 | slice []int
17 | idx int
18 | val int
19 | }
20 |
21 | tests := []testCase{
22 | {
23 | name: "Regular append",
24 | slice: []int{0, 1, 2},
25 | idx: 3,
26 | val: 1337,
27 | },
28 | {
29 | name: "Set existing",
30 | slice: []int{0, 1, 2},
31 | idx: 1,
32 | val: 1337,
33 | },
34 | {
35 | name: "Set and extend",
36 | slice: []int{0, 1, 2},
37 | idx: 10,
38 | val: 1337,
39 | },
40 | }
41 |
42 | for _, tc := range tests {
43 | tc := tc
44 | t.Run(tc.name, func(t *testing.T) {
45 | t.Parallel()
46 | assert := assert.New(t)
47 | orig := slices.Clone(tc.slice)
48 | res := SetAt(tc.slice, tc.idx, tc.val)
49 |
50 | assert.Len(res, max(len(orig), tc.idx+1))
51 |
52 | for i := range res {
53 | switch {
54 | case i == tc.idx: // value at `idx` should become `val`
55 | assert.Equal(tc.val, res[i])
56 | case i < len(orig): // other values from the original slice shouldn't change
57 | assert.Equal(orig[i], res[i])
58 | default: // i >= len(orig)
59 | // other values should be filled by the zero value
60 | assert.Zero(res[i])
61 | }
62 | }
63 | })
64 | }
65 | }
66 |
67 | func TestFnMap(t *testing.T) {
68 | t.Parallel()
69 | assert := assert.New(t)
70 |
71 | assert.Equal([]string{"1", "2", "3"}, FnMap([]int{1, 2, 3}, strconv.Itoa))
72 | assert.Equal([]string{}, FnMap([]int{}, strconv.Itoa))
73 | }
74 |
--------------------------------------------------------------------------------
/pkg/utils/string_test.go:
--------------------------------------------------------------------------------
1 | package utils_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | "github.com/blackstork-io/fabric/pkg/utils"
9 | )
10 |
11 | func TestMemoizedKeys(t *testing.T) {
12 | t.Parallel()
13 | assert := assert.New(t)
14 | type testCase struct {
15 | name string
16 | m map[string]struct{}
17 | want string
18 | }
19 | tests := []testCase{
20 | {
21 | name: "None",
22 | m: map[string]struct{}{},
23 | want: "",
24 | },
25 | {
26 | name: "One",
27 | m: map[string]struct{}{
28 | "one": {},
29 | },
30 | want: "'one'",
31 | },
32 | {
33 | name: "Two",
34 | m: map[string]struct{}{
35 | "one": {},
36 | "two": {},
37 | },
38 | want: "'one', 'two'",
39 | },
40 |
41 | {
42 | name: "Sorted",
43 | m: map[string]struct{}{
44 | "E": {},
45 | "D": {},
46 | "C": {},
47 | "B": {},
48 | "A": {},
49 | },
50 | want: "'A', 'B', 'C', 'D', 'E'",
51 | },
52 | }
53 | for _, tc := range tests {
54 | tc := tc
55 | t.Run(tc.name, func(t *testing.T) {
56 | t.Parallel()
57 | assert.Equal(tc.want, utils.MemoizedKeys(&tc.m)())
58 | })
59 | }
60 | }
61 |
62 | func TestMemoizedKeysMemoizes(t *testing.T) {
63 | assert := assert.New(t)
64 | m := map[string]struct{}{
65 | "A": {},
66 | "B": {},
67 | }
68 | mk := utils.MemoizedKeys(&m)
69 | m["C"] = struct{}{}
70 | mkRes := mk()
71 | assert.Equal("'A', 'B', 'C'", mkRes)
72 | m["D"] = struct{}{}
73 | mkRes2 := mk()
74 | assert.Equal("'A', 'B', 'C'", mkRes2)
75 | }
76 |
--------------------------------------------------------------------------------
/plugin/ast/astsrc/astsrc.go:
--------------------------------------------------------------------------------
1 | package astsrc
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/yuin/goldmark/text"
7 | )
8 |
9 | // ASTSource holds the source of the markdown AST (a read-only byte slice).
10 | type ASTSource []byte
11 |
12 | // Append appends bytes to the source and returns corresponding segment.
13 | func (s *ASTSource) Append(data []byte) text.Segment {
14 | start := len(*s)
15 | *s = append(*s, data...)
16 | return text.NewSegment(start, len(*s))
17 | }
18 |
19 | // AppendMultiple appends multiple byte slices to the source and returns corresponding segments.
20 | func (s *ASTSource) AppendMultiple(data [][]byte) *text.Segments {
21 | values := make([]text.Segment, len(data))
22 | for i, segment := range data {
23 | values[i] = s.Append(segment)
24 | }
25 | res := text.NewSegments()
26 | res.AppendAll(values)
27 | return res
28 | }
29 |
30 | // AppendString appends a string to the source and returns corresponding segment.
31 | func (s *ASTSource) AppendString(data string) text.Segment {
32 | return s.Append([]byte(data))
33 | }
34 |
35 | // Appendf appends formatted string to the source and returns corresponding segment.
36 | func (s *ASTSource) Appendf(format string, args ...interface{}) text.Segment {
37 | start := len(*s)
38 | *s = fmt.Appendf(*s, format, args...)
39 | return text.NewSegment(start, len(*s))
40 | }
41 |
42 | // AsBytes returns the source as a byte slice.
43 | // Returned bytes should be treated as read-only or modified with care,
44 | // ensuring that the offsets are not changed.
45 | func (s ASTSource) AsBytes() []byte {
46 | return s
47 | }
48 |
--------------------------------------------------------------------------------
/plugin/ast/nodes/content_node.go:
--------------------------------------------------------------------------------
1 | package nodes
2 |
3 | import "github.com/yuin/goldmark/ast"
4 |
5 | type FabricContentNode struct {
6 | ast.BaseBlock
7 | Meta *ContentMeta
8 | }
9 |
10 | func ToFabricContentNode(node ast.Node) (meta *FabricContentNode) {
11 | switch n := node.(type) {
12 | case *FabricContentNode:
13 | meta = n
14 | case *ast.Document:
15 | meta = &FabricContentNode{}
16 | child := n.FirstChild()
17 | for child != nil {
18 | c := child
19 | child = child.NextSibling()
20 | meta.AppendChild(meta, c)
21 | }
22 | case nil:
23 | // meta is nil
24 | default:
25 | meta = &FabricContentNode{}
26 | meta.AppendChild(meta, n)
27 | }
28 | return
29 | }
30 |
31 | // Dump implements ast.Node.
32 | func (m *FabricContentNode) Dump(source []byte, level int) {
33 | var kv map[string]string
34 | if m == nil || m.Meta == nil {
35 | kv = map[string]string{
36 | "meta": "nil",
37 | }
38 | } else {
39 | kv = map[string]string{
40 | "meta.provider": m.Meta.Provider,
41 | "meta.plugin": m.Meta.Plugin,
42 | "meta.version": m.Meta.Version,
43 | }
44 | }
45 | ast.DumpHelper(m, source, level, kv, nil)
46 | }
47 |
48 | // Kind implements ast.Node.
49 | func (m *FabricContentNode) Kind() ast.NodeKind {
50 | return ContentNodeKind
51 | }
52 |
53 | var _ ast.Node = &FabricContentNode{}
54 |
--------------------------------------------------------------------------------
/plugin/ast/nodes/metadata.go:
--------------------------------------------------------------------------------
1 | package nodes
2 |
3 | import "github.com/blackstork-io/fabric/plugin/plugindata"
4 |
5 | type ContentMeta struct {
6 | Provider string
7 | Plugin string
8 | Version string
9 | }
10 |
11 | func (meta *ContentMeta) AsData() plugindata.Data {
12 | if meta == nil {
13 | return nil
14 | }
15 | return plugindata.Map{
16 | "provider": plugindata.String(meta.Provider),
17 | "plugin": plugindata.String(meta.Plugin),
18 | "version": plugindata.String(meta.Version),
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/plugin/ast/v1/bubble_error.go:
--------------------------------------------------------------------------------
1 | package astv1
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | // bubbleError is a wrapper for errors that are meant to be panic'ed
8 | // and caught by the higher level caller.
9 | // This is considered to be a bad practice, but return-error propagation
10 | // is not practical in deeply nested encoding/decoding functions.
11 | // Standard library uses this pattern in some places, i.e. encoding/json.
12 | type bubbleError struct {
13 | err error
14 | }
15 |
16 | // bubbleWrap wraps an error in a bubbleError and returns it (not panics).
17 | func bubbleWrap(err error) bubbleError {
18 | return bubbleError{err: err}
19 | }
20 |
21 | // bubbleUp wraps an error in a bubbleError and panics it.
22 | func bubbleUp(err error) {
23 | panic(bubbleWrap(err))
24 | }
25 |
26 | // recoverBubbleError expects to be called in a defer statement with
27 | // the current error and a recover(). If bubbleError is recovered, it
28 | // would be [errors.Join]'ed with the current error (if non nil) and
29 | // returned.
30 | // If there was no panic, the passed in error would be returned.
31 | // If there is any other panic, it would be re-panicked.
32 | func recoverBubbleError(err error, recovered any) error {
33 | if recovered == nil {
34 | return err
35 | }
36 | if pErr, ok := recovered.(bubbleError); ok {
37 | if err != nil {
38 | return errors.Join(err, pErr.err)
39 | }
40 | return pErr.err
41 | }
42 | panic(recovered)
43 | }
44 |
--------------------------------------------------------------------------------
/plugin/ast/v1/testdata/fuzz/FuzzEncoder/6b5e47038908d6af:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | []byte(" ```\n\t")
3 |
--------------------------------------------------------------------------------
/plugin/dataspec/attr_spec_doc_comment.gotmpl:
--------------------------------------------------------------------------------
1 | {{- if .Doc -}}
2 | {{ .Doc }}
3 |
4 | {{ end -}}
5 | {{ if .IsRequired }}Required{{ else }}Optional{{ end }} {{ .Type }}.
6 | {{- if .OneOf }}
7 | Must be one of: {{ .OneOf }}
8 | {{- end }}
9 | {{- if and .MinVal .MaxVal }}
10 | Must be between {{ .MinVal }} and {{ .MaxVal }} (inclusive)
11 | {{- else if .MinVal }}
12 | Must be >= {{ .MinVal }}
13 | {{- else if .MaxVal }}
14 | Must be <= {{ .MaxVal }}
15 | {{- end }}
16 | {{- if and .MinLenVal .MaxLenVal -}}
17 | {{- if eq .MinLenVal .MaxLenVal }}
18 | Must have a length of {{ .MinLenVal }}
19 | {{- else }}
20 | Must have a length between {{ .MinLenVal }} and {{ .MaxLenVal }} (inclusive)
21 | {{- end -}}
22 | {{- else if .MinLenVal -}}
23 | {{- if eq .MinLenVal "1" }}
24 | Must be non-empty
25 | {{- else }}
26 | Must contain at least {{ .MinLenVal }} elements.
27 | {{- end -}}
28 | {{- else if .MaxLenVal }}
29 | Must contain no more than {{ .MaxLenVal }} elements.
30 | {{- end -}}
31 | {{- if .IsRequired }}
32 |
33 | For example:
34 | {{- else -}}
35 | {{- if .Example }}
36 |
37 | For example:
38 | {{ .Example }}
39 | {{- end }}
40 | Default value:
41 | {{- end -}}
42 |
--------------------------------------------------------------------------------
/plugin/dataspec/constraint/constraint_test.go:
--------------------------------------------------------------------------------
1 | package constraint
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | // lock constraints to their expected values
10 | // changing these would be backwards incompatible (for protobuf)
11 | func TestConstraints(t *testing.T) {
12 | assert := assert.New(t)
13 | assert.EqualValues(1, Required)
14 | assert.EqualValues(2, NonNull)
15 | assert.EqualValues(4, NonEmpty)
16 | assert.EqualValues(8, TrimSpace)
17 | assert.EqualValues(16, Integer)
18 | }
19 |
--------------------------------------------------------------------------------
/plugin/dataspec/dataspec.go:
--------------------------------------------------------------------------------
1 | // Documentable wrapper types form
2 | package dataspec
3 |
4 | import (
5 | "bytes"
6 | "strings"
7 |
8 | "github.com/hashicorp/hcl/v2/hclwrite"
9 | )
10 |
11 | type (
12 | Blocks []*Block
13 | Attributes map[string]*Attr
14 | )
15 |
16 | // RenderDoc renders the block documentation for spec.
17 | func RenderDoc(spec *RootSpec, blockName string, labels ...string) string {
18 | // Special-casing the first line generation:
19 | // config "data" "csv" { -> config data csv {
20 |
21 | if strings.Contains(blockName, " ") {
22 | return ""
23 | }
24 |
25 | f := hclwrite.NewEmptyFile()
26 | spec.BlockSpec().WriteBodyDoc(f.Body().AppendNewBlock(blockName, labels).Body())
27 | doc := hclwrite.Format(f.Bytes())
28 | blockBodyStart := bytes.IndexByte(doc, '{')
29 | if blockBodyStart == -1 {
30 | return ""
31 | }
32 |
33 | header := formatHeader(blockName, labels)
34 | newStart := blockBodyStart - len(header)
35 | if newStart >= 0 {
36 | copy(doc[newStart:], header)
37 | doc = doc[newStart:]
38 | } else {
39 | doc = append(header, doc[blockBodyStart:]...)
40 | }
41 |
42 | return string(doc)
43 | }
44 |
--------------------------------------------------------------------------------
/plugin/errors.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import "fmt"
4 |
5 | var ErrContentLocationNotFound = fmt.Errorf("content location not found")
6 |
--------------------------------------------------------------------------------
/plugin/pluginapi/v1/data_decoder.go:
--------------------------------------------------------------------------------
1 | package pluginapiv1
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/blackstork-io/fabric/pkg/utils"
7 | "github.com/blackstork-io/fabric/plugin/plugindata"
8 | )
9 |
10 | func decodeData(src *Data) plugindata.Data {
11 | switch src.GetData().(type) {
12 | case nil:
13 | return nil
14 | case *Data_NumberVal:
15 | return plugindata.Number(src.GetNumberVal())
16 | case *Data_StringVal:
17 | return plugindata.String(src.GetStringVal())
18 | case *Data_BoolVal:
19 | return plugindata.Bool(src.GetBoolVal())
20 | case *Data_MapVal:
21 | return decodeMapData(src.GetMapVal().GetValue())
22 | case *Data_ListVal:
23 | return plugindata.List(utils.FnMap(src.GetListVal().GetValue(), decodeData))
24 | case *Data_TimeVal:
25 | return plugindata.Time(src.GetTimeVal().AsTime())
26 | }
27 | panic(fmt.Sprintf("Unexpected src data type: %T", src.GetData()))
28 | }
29 |
30 | func decodeMapData(src map[string]*Data) plugindata.Map {
31 | return plugindata.Map(utils.MapMap(src, decodeData))
32 | }
33 |
--------------------------------------------------------------------------------
/plugin/pluginapi/v1/data_encoder.go:
--------------------------------------------------------------------------------
1 | package pluginapiv1
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "google.golang.org/protobuf/types/known/timestamppb"
8 |
9 | "github.com/blackstork-io/fabric/pkg/utils"
10 | "github.com/blackstork-io/fabric/plugin/plugindata"
11 | )
12 |
13 | func encodeData(d plugindata.Data) *Data {
14 | switch v := d.(type) {
15 | case nil:
16 | return nil
17 | case plugindata.Number:
18 | return &Data{
19 | Data: &Data_NumberVal{
20 | NumberVal: float64(v),
21 | },
22 | }
23 | case plugindata.String:
24 | return &Data{
25 | Data: &Data_StringVal{
26 | StringVal: string(v),
27 | },
28 | }
29 | case plugindata.Bool:
30 | return &Data{
31 | Data: &Data_BoolVal{
32 | BoolVal: bool(v),
33 | },
34 | }
35 | case plugindata.Map:
36 | return &Data{
37 | Data: &Data_MapVal{
38 | encodeMapData(v),
39 | },
40 | }
41 | case plugindata.List:
42 | return &Data{
43 | Data: &Data_ListVal{
44 | ListVal: &ListData{
45 | Value: utils.FnMap(v, encodeData),
46 | },
47 | },
48 | }
49 | case plugindata.Time:
50 | return &Data{
51 | Data: &Data_TimeVal{
52 | TimeVal: timestamppb.New(time.Time(d.(plugindata.Time))),
53 | },
54 | }
55 | default:
56 | if cd, ok := d.(plugindata.Convertible); ok {
57 | return encodeData(cd.AsPluginData())
58 | }
59 | }
60 | panic(fmt.Errorf("unexpected plugin data type: %T", d))
61 | }
62 |
63 | func encodeMapData(m plugindata.Map) *MapData {
64 | return &MapData{
65 | Value: utils.MapMap(m, encodeData),
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/plugin/pluginapi/v1/hcl_decoder.go:
--------------------------------------------------------------------------------
1 | package pluginapiv1
2 |
3 | import (
4 | "github.com/hashicorp/hcl/v2"
5 |
6 | "github.com/blackstork-io/fabric/pkg/utils"
7 | )
8 |
9 | func decodeDiagnosticList(src []*Diagnostic) []*hcl.Diagnostic {
10 | return utils.FnMap(src, decodeDiagnostic)
11 | }
12 |
13 | func decodeDiagnostic(src *Diagnostic) *hcl.Diagnostic {
14 | if src == nil {
15 | return nil
16 | }
17 | return &hcl.Diagnostic{
18 | Severity: hcl.DiagnosticSeverity(src.GetSeverity()),
19 | Summary: src.GetSummary(),
20 | Detail: src.GetDetail(),
21 | Subject: decodeRange(src.GetSubject()).Ptr(),
22 | Context: decodeRange(src.GetContext()).Ptr(),
23 | }
24 | }
25 |
26 | func decodePos(src *Pos) hcl.Pos {
27 | if src == nil {
28 | return hcl.InitialPos
29 | }
30 | return hcl.Pos{
31 | Line: int(src.GetLine()),
32 | Column: int(src.GetColumn()),
33 | Byte: int(src.GetByte()),
34 | }
35 | }
36 |
37 | func decodeRange(src *Range) hcl.Range {
38 | if src == nil {
39 | return hcl.Range{
40 | Filename: "",
41 | Start: hcl.InitialPos,
42 | End: hcl.InitialPos,
43 | }
44 | }
45 | return hcl.Range{
46 | Filename: src.GetFilename(),
47 | Start: decodePos(src.GetStart()),
48 | End: decodePos(src.GetEnd()),
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/plugin/pluginapi/v1/hcl_encoder.go:
--------------------------------------------------------------------------------
1 | package pluginapiv1
2 |
3 | import (
4 | "github.com/hashicorp/hcl/v2"
5 |
6 | "github.com/blackstork-io/fabric/pkg/utils"
7 | )
8 |
9 | func encodeDiagnosticList(src []*hcl.Diagnostic) []*Diagnostic {
10 | return utils.FnMap(src, encodeDiagnostic)
11 | }
12 |
13 | func encodeDiagnostic(src *hcl.Diagnostic) *Diagnostic {
14 | if src == nil {
15 | return nil
16 | }
17 | return &Diagnostic{
18 | Severity: int64(src.Severity),
19 | Summary: src.Summary,
20 | Detail: src.Detail,
21 | Subject: encodeRange(src.Subject),
22 | Context: encodeRange(src.Context),
23 | }
24 | }
25 |
26 | func encodePos(src hcl.Pos) *Pos {
27 | return &Pos{
28 | Line: int64(src.Line),
29 | Column: int64(src.Column),
30 | Byte: int64(src.Byte),
31 | }
32 | }
33 |
34 | func encodeRange(src *hcl.Range) *Range {
35 | if src == nil {
36 | return nil
37 | }
38 | return &Range{
39 | Filename: src.Filename,
40 | Start: encodePos(src.Start),
41 | End: encodePos(src.End),
42 | }
43 | }
44 |
45 | func encodeRangeVal(src hcl.Range) *Range {
46 | return encodeRange(&src)
47 | }
48 |
--------------------------------------------------------------------------------
/plugin/pluginapi/v1/is_not_plugin.go:
--------------------------------------------------------------------------------
1 | //go:build !fabricplugin
2 |
3 | package pluginapiv1
4 |
5 | import (
6 | "github.com/hashicorp/go-hclog"
7 | )
8 |
9 | func loggerForGoplugin() hclog.Logger {
10 | panic("Attempted to run a plugin built without `fabricplugin` tag")
11 | }
12 |
--------------------------------------------------------------------------------
/plugin/resolver/options.go:
--------------------------------------------------------------------------------
1 | package resolver
2 |
3 | import (
4 | "io"
5 | "log/slog"
6 |
7 | "go.opentelemetry.io/otel/trace"
8 | tracenoop "go.opentelemetry.io/otel/trace/noop"
9 | )
10 |
11 | // options for the resolver.
12 | type options struct {
13 | logger *slog.Logger
14 | tracer trace.Tracer
15 | sources []Source
16 | }
17 |
18 | var defaultOptions = options{
19 | logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
20 | sources: []Source{},
21 | tracer: tracenoop.Tracer{},
22 | }
23 |
24 | // Option is a functional option for the resolver.
25 | type Option func(*options)
26 |
27 | // WithLogger sets the logger for the resolver.
28 | func WithLogger(logger *slog.Logger) Option {
29 | return func(o *options) {
30 | o.logger = logger
31 | }
32 | }
33 |
34 | // WithSources sets the sources for the resolver.
35 | func WithSources(sources ...Source) Option {
36 | return func(o *options) {
37 | o.sources = sources
38 | }
39 | }
40 |
41 | // WithTracer sets the tracer for the resolver.
42 | func WithTracer(tracer trace.Tracer) Option {
43 | return func(o *options) {
44 | o.tracer = tracer
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/plugin/resolver/version.go:
--------------------------------------------------------------------------------
1 | package resolver
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 |
7 | "github.com/Masterminds/semver/v3"
8 | )
9 |
10 | // Version is a version of a plugin. It is a wrapper around semver.Version with strict parsing.
11 | type Version struct {
12 | *semver.Version
13 | }
14 |
15 | // UnmarshalJSON parses a JSON string into a PluginVersion using strict semver parsing.
16 | func (v *Version) UnmarshalJSON(data []byte) error {
17 | raw, err := strconv.Unquote(string(data))
18 | if err != nil {
19 | return fmt.Errorf("failed to unquote version: %w", err)
20 | }
21 | ver, err := semver.StrictNewVersion(raw)
22 | if err != nil {
23 | return err
24 | }
25 | *v = Version{ver}
26 | return nil
27 | }
28 |
29 | // Compare compares the version with another version.
30 | func (v Version) Compare(other Version) int {
31 | return v.Version.Compare(other.Version)
32 | }
33 |
34 | // ConstraintMap is a map of plugin names to version constraints.
35 | type ConstraintMap map[Name]*semver.Constraints
36 |
37 | // ParseConstraintMap parses string map into a PluginConstraintMap.
38 | func ParseConstraintMap(src map[string]string) (ConstraintMap, error) {
39 | if src == nil {
40 | return nil, nil
41 | }
42 | parsed := make(ConstraintMap)
43 | for name, version := range src {
44 | if version == "" {
45 | return nil, fmt.Errorf("missing plugin version constraint for '%s'", name)
46 | }
47 | parsedName, err := ParseName(name)
48 | if err != nil {
49 | return nil, err
50 | }
51 | constraints, err := semver.NewConstraint(version)
52 | if err != nil {
53 | return nil, err
54 | }
55 | parsed[parsedName] = constraints
56 | }
57 | return parsed, nil
58 | }
59 |
--------------------------------------------------------------------------------
/print/htmlprint/document.gotempl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{- if .Description}}
7 |
8 | {{- end}}
9 | {{.Title}}
10 | {{- range .JSSources}}
11 |
12 | {{- end}}
13 | {{- range .CSSSources}}
14 |
15 | {{- end}}
16 | {{- if .JS}}
17 |
20 | {{- end}}
21 | {{- if .CSS}}
22 |
25 | {{- end}}
26 |
27 |
28 | {{.Content}}
29 |
30 |
--------------------------------------------------------------------------------
/print/logging.go:
--------------------------------------------------------------------------------
1 | package print
2 |
3 | import (
4 | "context"
5 | "io"
6 | "log/slog"
7 |
8 | "github.com/blackstork-io/fabric/plugin"
9 | )
10 |
11 | type logging struct {
12 | next Printer
13 | logger *slog.Logger
14 | attrs []slog.Attr
15 | }
16 |
17 | // WithLogging wraps the printer with logging instrumentation.
18 | func WithLogging(next Printer, logger *slog.Logger, attrs ...slog.Attr) Printer {
19 | return &logging{
20 | next: next,
21 | logger: logger,
22 | attrs: attrs,
23 | }
24 | }
25 |
26 | func (p logging) Print(ctx context.Context, w io.Writer, el plugin.Content) (err error) {
27 | p.logger.LogAttrs(ctx, slog.LevelDebug, "Printing content", p.attrs...)
28 | return p.next.Print(ctx, w, el)
29 | }
30 |
--------------------------------------------------------------------------------
/print/tracing.go:
--------------------------------------------------------------------------------
1 | package print
2 |
3 | import (
4 | "context"
5 | "io"
6 |
7 | "go.opentelemetry.io/otel/attribute"
8 | "go.opentelemetry.io/otel/codes"
9 | "go.opentelemetry.io/otel/trace"
10 |
11 | "github.com/blackstork-io/fabric/plugin"
12 | )
13 |
14 | type tracing struct {
15 | next Printer
16 | tracer trace.Tracer
17 | attrs []attribute.KeyValue
18 | }
19 |
20 | // WithTracing wraps a printer with tracing instrumentation.
21 | func WithTracing(next Printer, tracer trace.Tracer, attrs ...attribute.KeyValue) Printer {
22 | return tracing{
23 | next: next,
24 | tracer: tracer,
25 | attrs: attrs,
26 | }
27 | }
28 |
29 | func (p tracing) Print(ctx context.Context, w io.Writer, el plugin.Content) (err error) {
30 | ctx, span := p.tracer.Start(ctx, "Printer.Print", trace.WithAttributes(p.attrs...))
31 | defer func() {
32 | if err != nil {
33 | span.RecordError(err)
34 | span.SetStatus(codes.Error, err.Error())
35 | } else {
36 | span.SetStatus(codes.Ok, "success")
37 | }
38 | span.End()
39 | }()
40 | return p.next.Print(ctx, w, el)
41 | }
42 |
--------------------------------------------------------------------------------
/proto/buf.yaml:
--------------------------------------------------------------------------------
1 | version: v1
2 | breaking:
3 | use:
4 | - FILE
5 | lint:
6 | use:
7 | - DEFAULT
8 |
--------------------------------------------------------------------------------
/proto/pluginapi/v1/content.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package pluginapi.v1;
4 | import "ast/v1/ast.proto";
5 |
6 |
7 | enum LocationEffect {
8 | LOCATION_EFFECT_UNSPECIFIED = 0;
9 | LOCATION_EFFECT_BEFORE = 1;
10 | LOCATION_EFFECT_AFTER = 2;
11 | }
12 |
13 | message Location {
14 | uint32 index = 1;
15 | LocationEffect effect = 2;
16 | }
17 |
18 | message ContentResult {
19 | Content content = 1;
20 | Location location = 2;
21 | }
22 |
23 | message Content {
24 | oneof value {
25 | ContentElement element = 1;
26 | ContentSection section = 2;
27 | ContentEmpty empty = 3;
28 | };
29 | }
30 |
31 | message ContentSection {
32 | repeated Content children = 1;
33 | ast.v1.Metadata meta = 2;
34 | }
35 |
36 | message ContentElement {
37 | bytes markdown = 1;
38 | optional ast.v1.FabricContentNode ast = 2;
39 | ast.v1.Metadata meta = 3;
40 | }
41 |
42 | message ContentEmpty {}
--------------------------------------------------------------------------------
/proto/pluginapi/v1/data.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | import "google/protobuf/timestamp.proto";
4 |
5 | package pluginapi.v1;
6 |
7 | message Data {
8 | oneof data {
9 | string string_val = 1;
10 | double number_val = 2;
11 | bool bool_val = 3;
12 | ListData list_val = 4;
13 | MapData map_val = 5;
14 | google.protobuf.Timestamp time_val = 6;
15 | };
16 | }
17 |
18 | message ListData {
19 | repeated Data value = 1;
20 | }
21 |
22 | message MapData {
23 | map value = 1;
24 | }
25 |
--------------------------------------------------------------------------------
/proto/pluginapi/v1/hcl.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package pluginapi.v1;
4 |
5 | message Pos {
6 | int64 line = 1;
7 | int64 column = 2;
8 | int64 byte = 3;
9 | }
10 |
11 | message Range {
12 | string filename = 1;
13 | Pos start = 2;
14 | Pos end = 3;
15 | }
16 |
17 | message Diagnostic {
18 | int64 severity = 1;
19 | string summary = 2;
20 | string detail = 3;
21 | Range subject = 4;
22 | Range context = 5;
23 | }
24 |
--------------------------------------------------------------------------------
/proto/pluginapi/v1/plugin.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package pluginapi.v1;
4 |
5 | import "pluginapi/v1/content.proto";
6 | import "pluginapi/v1/data.proto";
7 | import "pluginapi/v1/hcl.proto";
8 | import "pluginapi/v1/schema.proto";
9 | import "pluginapi/v1/dataspec.proto";
10 |
11 |
12 | service PluginService {
13 | rpc GetSchema(GetSchemaRequest) returns (GetSchemaResponse) {}
14 | rpc RetrieveData(RetrieveDataRequest) returns (RetrieveDataResponse) {}
15 | rpc ProvideContent(ProvideContentRequest) returns (ProvideContentResponse) {}
16 | rpc Publish(PublishRequest) returns (PublishResponse) {}
17 | }
18 |
19 | message GetSchemaRequest {}
20 |
21 | message GetSchemaResponse {
22 | Schema schema = 1;
23 | }
24 |
25 | message RetrieveDataRequest {
26 | string source = 1;
27 | Block args = 2;
28 | Block config = 3;
29 | }
30 |
31 | message RetrieveDataResponse {
32 | Data data = 1;
33 | repeated Diagnostic diagnostics = 2;
34 | }
35 |
36 | message ProvideContentRequest {
37 | string provider = 1;
38 | Block args = 2;
39 | Block config = 3;
40 | MapData data_context = 4;
41 | uint32 content_id = 5;
42 | }
43 |
44 | message ProvideContentResponse {
45 | ContentResult result = 1;
46 | repeated Diagnostic diagnostics = 2;
47 | }
48 |
49 |
50 | message PublishRequest {
51 | string publisher = 1;
52 | Block args = 2;
53 | Block config = 3;
54 | MapData data_context = 4;
55 | OutputFormat format = 5;
56 | string document_name = 6;
57 | }
58 |
59 | message PublishResponse {
60 | repeated Diagnostic diagnostics = 1;
61 | }
--------------------------------------------------------------------------------
/proto/pluginapi/v1/schema.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package pluginapi.v1;
4 |
5 | import "pluginapi/v1/dataspec.proto";
6 |
7 | message Schema {
8 | string name = 1;
9 | string version = 2;
10 | // Plugin components
11 | map data_sources = 3;
12 | map content_providers = 4;
13 | map publishers = 7;
14 | string doc = 5;
15 | repeated string tags = 6;
16 | }
17 |
18 | message DataSourceSchema {
19 | BlockSpec args = 3;
20 | BlockSpec config = 4;
21 | string doc = 5;
22 | repeated string tags = 6;
23 | }
24 |
25 | enum InvocationOrder {
26 | INVOCATION_ORDER_UNSPECIFIED = 0;
27 | INVOCATION_ORDER_BEGIN = 2;
28 | INVOCATION_ORDER_END = 3;
29 | }
30 |
31 | message ContentProviderSchema {
32 | BlockSpec args = 4;
33 | BlockSpec config = 5;
34 | InvocationOrder invocation_order = 3;
35 | string doc = 6;
36 | repeated string tags = 7;
37 | }
38 |
39 |
40 | enum OutputFormat {
41 | OUTPUT_FORMAT_UNSPECIFIED = 0;
42 | OUTPUT_FORMAT_MD = 1;
43 | OUTPUT_FORMAT_HTML = 2;
44 | OUTPUT_FORMAT_PDF = 3;
45 | }
46 |
47 | message PublisherSchema {
48 | BlockSpec args = 1;
49 | BlockSpec config = 2;
50 | string doc = 3;
51 | repeated string tags = 4;
52 | repeated OutputFormat allowed_formats = 5;
53 | }
--------------------------------------------------------------------------------
/tools/pluginmeta/metadata.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type Metadata struct {
4 | Plugins []*PluginMetadata `json:"plugins"`
5 | }
6 |
7 | type PluginMetadata struct {
8 | Name string `json:"name"`
9 | Version string `json:"version"`
10 | Archives []*PluginArchiveMetadata `json:"archives"`
11 | }
12 |
13 | type PluginArchiveMetadata struct {
14 | Filename string `json:"filename"`
15 | OS string `json:"os"`
16 | Arch string `json:"arch"`
17 | BinaryChecksum string `json:"binary_checksum"`
18 | }
19 |
--------------------------------------------------------------------------------
/tools/pluginmeta/releaser_config.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type ReleaserConfig struct {
4 | Builds []ReleaserBuild `yaml:"builds"`
5 | Archives []ReleaserArchive `yaml:"archives"`
6 | }
7 |
8 | type ReleaserBuild struct {
9 | ID string `yaml:"id"`
10 | GOOS []string `yaml:"goos"`
11 | }
12 |
13 | type ReleaserArchive struct {
14 | ID string `yaml:"id"`
15 | Format string `yaml:"format"`
16 | Builds []string `yaml:"builds"`
17 | NameTemplate string `yaml:"name_template"`
18 | }
19 |
20 | type ReleaserFormatOverride struct {
21 | GOOS string `yaml:"goos"`
22 | Format string `yaml:"format"`
23 | }
24 |
--------------------------------------------------------------------------------