├── .github ├── dependabot.yml ├── workflows │ ├── dependabot.yml │ ├── lts.yml │ ├── main.yml │ ├── pr.yml │ ├── release.yml │ └── test.yml └── zizmor.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── AGENTS.md ├── CLAUDE.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── app ├── app.go ├── appmanifest │ ├── v1alpha1 │ │ ├── appmanifest_codec_gen.go │ │ ├── appmanifest_metadata_gen.go │ │ ├── appmanifest_object_gen.go │ │ ├── appmanifest_schema_gen.go │ │ ├── appmanifest_spec_gen.go │ │ ├── appmanifest_status_gen.go │ │ ├── constants.go │ │ ├── conversion.go │ │ ├── conversion_test.go │ │ ├── generated_code_test.go │ │ └── testfiles │ │ │ ├── manifest-01.json │ │ │ ├── schema-01.json │ │ │ └── spec-01.json │ └── v1alpha2 │ │ ├── appmanifest_codec_gen.go │ │ ├── appmanifest_metadata_gen.go │ │ ├── appmanifest_object_gen.go │ │ ├── appmanifest_schema_gen.go │ │ ├── appmanifest_spec_gen.go │ │ ├── appmanifest_status_gen.go │ │ ├── constants.go │ │ ├── conversion.go │ │ ├── conversion_test.go │ │ ├── generated_code_test.go │ │ └── testfiles │ │ ├── manifest-01.json │ │ ├── schema-01.json │ │ └── spec-01.json ├── cue.mod │ └── module.cue ├── definitions │ ├── app-manifest-manifest.json │ └── appmanifest.apps.grafana.com.json ├── manifest.cue ├── manifest.go ├── manifest_test.go ├── manifest_v1alpha1.cue ├── manifest_v1alpha2.cue ├── runner.go └── runner_test.go ├── benchmark ├── benchmark_informer_supplier_test.go ├── benchmark_mocks_test.go └── benchmark_utils_test.go ├── cmd └── grafana-app-sdk │ ├── generate.go │ ├── main.go │ ├── manifest.go │ ├── project.go │ ├── project_local.go │ ├── project_local_datasources.go │ ├── templates │ ├── Magefile.go.tmpl │ ├── Makefile.tmpl │ ├── constants.ts.tmpl │ ├── frontend-static │ │ ├── .config │ │ │ ├── .eslintrc │ │ │ ├── .prettierrc.js │ │ │ ├── Dockerfile │ │ │ ├── README.md │ │ │ ├── jest-setup.js │ │ │ ├── jest.config.js │ │ │ ├── jest │ │ │ │ ├── mocks │ │ │ │ │ └── react-inlinesvg.tsx │ │ │ │ └── utils.js │ │ │ ├── tsconfig.json │ │ │ ├── types │ │ │ │ └── custom.d.ts │ │ │ └── webpack │ │ │ │ ├── constants.ts │ │ │ │ ├── utils.ts │ │ │ │ └── webpack.config.ts │ │ ├── .eslintrc │ │ ├── .nvmrc │ │ ├── .prettierrc.js │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── jest-setup.js │ │ ├── jest.config.js │ │ ├── src │ │ │ ├── App.tsx │ │ │ ├── components │ │ │ │ └── Routes │ │ │ │ │ ├── Routes.tsx │ │ │ │ │ └── index.tsx │ │ │ ├── module.ts │ │ │ ├── pages │ │ │ │ ├── index.tsx │ │ │ │ └── main.tsx │ │ │ ├── types.ts │ │ │ └── utils │ │ │ │ ├── utils.plugin.ts │ │ │ │ └── utils.routing.ts │ │ └── tsconfig.json │ ├── kind.cue.tmpl │ ├── kindmeta.cue.tmpl │ ├── kindversion.cue.tmpl │ ├── local │ │ ├── Tiltfile │ │ ├── config.yaml │ │ ├── generated │ │ │ ├── aggregator-access.yaml │ │ │ ├── configure-grafana.sh │ │ │ ├── crd_roles.yaml │ │ │ ├── datasources │ │ │ │ ├── agent.yaml │ │ │ │ ├── cortex.yaml │ │ │ │ ├── loki.yaml │ │ │ │ ├── minio.yaml │ │ │ │ └── tempo.yaml │ │ │ ├── grafana.yaml │ │ │ ├── k3d-config.json │ │ │ └── operator.yaml │ │ └── scripts │ │ │ ├── cluster.sh │ │ │ └── push_image.sh │ ├── manifest.cue.tmpl │ ├── operator_Dockerfile.tmpl │ ├── package.json.tmpl │ └── plugin.json.tmpl │ ├── util.go │ └── version.go ├── codegen ├── README.md ├── cuekind │ ├── cue.mod │ │ └── module.cue │ ├── def.cue │ ├── generators.go │ ├── generators_test.go │ ├── parser.go │ ├── parser_test.go │ └── testing │ │ ├── cue.mod │ │ └── module.cue │ │ ├── customkind.cue │ │ ├── testkind.cue │ │ └── testkind2.cue ├── generator.go ├── jennies │ ├── app.go │ ├── backendplugin.go │ ├── codec.go │ ├── constants.go │ ├── crd.go │ ├── cuetil.go │ ├── customroutes.go │ ├── goclients.go │ ├── gotypes.go │ ├── manifest.go │ ├── openapi.go │ ├── operator.go │ ├── resourceobject.go │ ├── routercode.go │ ├── schema.go │ ├── typescript.go │ └── util.go ├── kind.go ├── manifest.go ├── templates │ ├── app │ │ ├── app.tmpl │ │ └── watcher.tmpl │ ├── codec.tmpl │ ├── constants.tmpl │ ├── lineage.tmpl │ ├── manifest_go.tmpl │ ├── operator │ │ ├── config.tmpl │ │ ├── kubeconfig.tmpl │ │ └── main.tmpl │ ├── plugin │ │ ├── handler_models.tmpl │ │ ├── handler_resource.tmpl │ │ ├── main.tmpl │ │ └── plugin.tmpl │ ├── resourceclient.tmpl │ ├── resourceobject.tmpl │ ├── runtimeobject.tmpl │ ├── schema.tmpl │ ├── secure │ │ ├── data.tmpl │ │ ├── middleware.tmpl │ │ └── retriever.tmpl │ ├── templates.go │ ├── themacodec.tmpl │ ├── tstype.tmpl │ └── wrappedtype.tmpl └── testing │ ├── README.md │ └── golden_generated │ ├── crd │ ├── customkind.customapp.ext.grafana.com.json.txt │ ├── customkind.customapp.ext.grafana.com.yaml.txt │ ├── testkind.testapp.ext.grafana.com.json.txt │ ├── testkind.testapp.ext.grafana.com.yaml.txt │ ├── testkind2.testapp.ext.grafana.com.json.txt │ └── testkind2.testapp.ext.grafana.com.yaml.txt │ ├── go │ ├── groupbygroup │ │ ├── customapp │ │ │ ├── v0_0 │ │ │ │ ├── constants.go.txt │ │ │ │ ├── customkind_client_gen.go.txt │ │ │ │ ├── customkind_codec_gen.go.txt │ │ │ │ ├── customkind_metadata_gen.go.txt │ │ │ │ ├── customkind_object_gen.go.txt │ │ │ │ ├── customkind_schema_gen.go.txt │ │ │ │ ├── customkind_spec_gen.go.txt │ │ │ │ └── customkind_status_gen.go.txt │ │ │ └── v1_0 │ │ │ │ ├── constants.go.txt │ │ │ │ ├── customkind_client_gen.go.txt │ │ │ │ ├── customkind_codec_gen.go.txt │ │ │ │ ├── customkind_metadata_gen.go.txt │ │ │ │ ├── customkind_object_gen.go.txt │ │ │ │ ├── customkind_schema_gen.go.txt │ │ │ │ ├── customkind_spec_gen.go.txt │ │ │ │ └── customkind_status_gen.go.txt │ │ └── testapp │ │ │ ├── v1 │ │ │ ├── constants.go.txt │ │ │ ├── testkind2_client_gen.go.txt │ │ │ ├── testkind2_codec_gen.go.txt │ │ │ ├── testkind2_metadata_gen.go.txt │ │ │ ├── testkind2_object_gen.go.txt │ │ │ ├── testkind2_schema_gen.go.txt │ │ │ ├── testkind2_spec_gen.go.txt │ │ │ ├── testkind2_status_gen.go.txt │ │ │ ├── testkind_client_gen.go.txt │ │ │ ├── testkind_codec_gen.go.txt │ │ │ ├── testkind_metadata_gen.go.txt │ │ │ ├── testkind_object_gen.go.txt │ │ │ ├── testkind_schema_gen.go.txt │ │ │ ├── testkind_spec_gen.go.txt │ │ │ └── testkind_status_gen.go.txt │ │ │ ├── v2 │ │ │ ├── constants.go.txt │ │ │ ├── testkind_client_gen.go.txt │ │ │ ├── testkind_codec_gen.go.txt │ │ │ ├── testkind_metadata_gen.go.txt │ │ │ ├── testkind_object_gen.go.txt │ │ │ ├── testkind_schema_gen.go.txt │ │ │ ├── testkind_spec_gen.go.txt │ │ │ └── testkind_status_gen.go.txt │ │ │ └── v3 │ │ │ ├── constants.go.txt │ │ │ ├── testkind_client_gen.go.txt │ │ │ ├── testkind_codec_gen.go.txt │ │ │ ├── testkind_createreconcile_request_body_types_gen.go.txt │ │ │ ├── testkind_createreconcile_response_types_gen.go.txt │ │ │ ├── testkind_gettestkindsearchresult_request_params_object_gen.go.txt │ │ │ ├── testkind_gettestkindsearchresult_request_params_types_gen.go.txt │ │ │ ├── testkind_gettestkindsearchresult_response_body_types_gen.go.txt │ │ │ ├── testkind_gettestkindsearchresult_response_object_types_gen.go.txt │ │ │ ├── testkind_metadata_gen.go.txt │ │ │ ├── testkind_object_gen.go.txt │ │ │ ├── testkind_schema_gen.go.txt │ │ │ ├── testkind_spec_gen.go.txt │ │ │ └── testkind_status_gen.go.txt │ └── groupbykind │ │ ├── customapp_manifest.go.txt │ │ └── customkind │ │ ├── v0_0 │ │ ├── constants.go.txt │ │ ├── customkind_client_gen.go.txt │ │ ├── customkind_codec_gen.go.txt │ │ ├── customkind_metadata_gen.go.txt │ │ ├── customkind_object_gen.go.txt │ │ ├── customkind_schema_gen.go.txt │ │ ├── customkind_spec_gen.go.txt │ │ └── customkind_status_gen.go.txt │ │ └── v1_0 │ │ ├── constants.go.txt │ │ ├── customkind_client_gen.go.txt │ │ ├── customkind_codec_gen.go.txt │ │ ├── customkind_metadata_gen.go.txt │ │ ├── customkind_object_gen.go.txt │ │ ├── customkind_schema_gen.go.txt │ │ ├── customkind_spec_gen.go.txt │ │ └── customkind_status_gen.go.txt │ ├── manifest │ ├── custom-app-manifest.json.txt │ ├── custom-app-manifest.yaml.txt │ ├── go │ │ ├── groupbygroup │ │ │ ├── customapp_manifest.go.txt │ │ │ └── testapp_manifest.go.txt │ │ └── groupbykind │ │ │ └── customapp_manifest.go.txt │ ├── test-app-manifest.json.txt │ └── test-app-manifest.yaml.txt │ └── typescript │ └── versioned │ ├── customkind │ ├── v0-0 │ │ ├── customkind_object_gen.ts.txt │ │ ├── types.metadata.gen.ts.txt │ │ ├── types.spec.gen.ts.txt │ │ └── types.status.gen.ts.txt │ └── v1-0 │ │ ├── customkind_object_gen.ts.txt │ │ ├── types.metadata.gen.ts.txt │ │ ├── types.spec.gen.ts.txt │ │ └── types.status.gen.ts.txt │ └── testkind │ └── v2 │ ├── testkind_object_gen.ts.txt │ ├── types.metadata.gen.ts.txt │ ├── types.spec.gen.ts.txt │ └── types.status.gen.ts.txt ├── docs ├── README.md ├── admission-control.md ├── app-manifest.md ├── application-design │ ├── README.md │ ├── img │ │ ├── api-hierarchy-example.excalidraw.png │ │ ├── api-service.excalidraw.png │ │ ├── async-logic-example.png │ │ ├── extending-storage-example.excalidraw.png │ │ ├── frontend-plugin.excalidraw.png │ │ └── operator-backend.excalidraw.png │ └── platform-concepts.md ├── architecture │ └── reconciliation.md ├── cli.md ├── code-generation.md ├── custom-kinds │ ├── README.md │ ├── managing-multiple-versions.md │ ├── using-kinds.md │ └── writing-kinds.md ├── diagrams │ ├── app_logic.drawio │ ├── app_logic.png │ ├── codegen.excalidraw │ ├── codegen_dark.png │ ├── codegen_light.png │ ├── design_pattern_kubernetes.drawio │ ├── design_pattern_kubernetes.png │ ├── design_pattern_operator_only_kubernetes.drawio │ ├── design_pattern_operator_only_kubernetes.png │ ├── design_pattern_plugin_only.drawio │ ├── design_pattern_plugin_only.png │ ├── design_pattern_simple.drawio │ ├── design_pattern_simple.png │ ├── kind-overview-dark.png │ ├── kind-overview.excalidraw │ ├── kind-overview.png │ ├── sdk-app-overview.drawio │ └── sdk-app-overview.png ├── kubernetes.md ├── local-development.md ├── migrations │ ├── README.md │ ├── v0.15.md │ ├── v0.27.md │ ├── v0.28.md │ ├── v0.30.md │ ├── v0.32.md │ └── v0.40.md ├── operators.md ├── resource-objects.md ├── resource-stores.md ├── tutorials │ └── issue-tracker │ │ ├── 01-project-init.md │ │ ├── 02-defining-our-kinds.md │ │ ├── 03-generate-kind-code.md │ │ ├── 04-boilerplate.md │ │ ├── 05-local-deployment.md │ │ ├── 06-frontend.md │ │ ├── 07-operator-watcher.md │ │ ├── 08-adding-admission-control.md │ │ ├── 09-wrap-up.md │ │ ├── README.md │ │ ├── cue │ │ ├── issue-v1.cue │ │ ├── issue-v2.cue │ │ ├── issue-v3.cue │ │ └── issue-v4.cue │ │ ├── frontend-files │ │ ├── issue-client.ts │ │ └── main.tsx │ │ ├── images │ │ ├── grafana-watcher-dashboard.png │ │ ├── tilt-console-errors.png │ │ └── tilt-console-success.png │ │ └── issue-dashboard.json ├── watching-unowned-resources.md ├── writing-a-reconciler.md └── writing-an-app.md ├── examples ├── apiserver │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── apis │ │ ├── example │ │ │ ├── v0alpha1 │ │ │ │ ├── constants.go │ │ │ │ ├── testkind_client_gen.go │ │ │ │ ├── testkind_codec_gen.go │ │ │ │ ├── testkind_metadata_gen.go │ │ │ │ ├── testkind_object_gen.go │ │ │ │ ├── testkind_schema_gen.go │ │ │ │ ├── testkind_spec_gen.go │ │ │ │ └── testkind_status_gen.go │ │ │ └── v1alpha1 │ │ │ │ ├── constants.go │ │ │ │ ├── getclusterfoobar_response_body_types_gen.go │ │ │ │ ├── getclusterfoobar_response_object_types_gen.go │ │ │ │ ├── getfoobar_request_body_types_gen.go │ │ │ │ ├── getfoobar_request_params_object_gen.go │ │ │ │ ├── getfoobar_request_params_types_gen.go │ │ │ │ ├── getfoobar_response_body_types_gen.go │ │ │ │ ├── getfoobar_response_object_types_gen.go │ │ │ │ ├── testkind_client_gen.go │ │ │ │ ├── testkind_codec_gen.go │ │ │ │ ├── testkind_getfoo_request_body_types_gen.go │ │ │ │ ├── testkind_getfoo_request_params_object_gen.go │ │ │ │ ├── testkind_getfoo_request_params_types_gen.go │ │ │ │ ├── testkind_getfoo_response_body_types_gen.go │ │ │ │ ├── testkind_getfoo_response_object_types_gen.go │ │ │ │ ├── testkind_getmessage_response_body_types_gen.go │ │ │ │ ├── testkind_getmessage_response_object_types_gen.go │ │ │ │ ├── testkind_getrecursiveresponse_response_body_types_gen.go │ │ │ │ ├── testkind_getrecursiveresponse_response_object_types_gen.go │ │ │ │ ├── testkind_metadata_gen.go │ │ │ │ ├── testkind_mysubresource_gen.go │ │ │ │ ├── testkind_object_gen.go │ │ │ │ ├── testkind_schema_gen.go │ │ │ │ ├── testkind_spec_gen.go │ │ │ │ └── testkind_status_gen.go │ │ └── example_manifest.go │ ├── go.mod │ ├── go.sum │ ├── kinds │ │ ├── cue.mod │ │ │ └── module.cue │ │ ├── manifest.cue │ │ └── testkind.cue │ └── server.go ├── create_cluster.sh ├── operator │ └── simple │ │ ├── README.md │ │ ├── basic.yaml │ │ ├── example.yaml │ │ ├── reconciler │ │ └── main.go │ │ ├── run.sh │ │ └── watcher │ │ └── main.go └── resource │ └── store │ ├── README.md │ ├── run.sh │ └── store.go ├── go.mod ├── go.sum ├── go.work ├── go.work.sum ├── health ├── health.go ├── observer.go └── observer_test.go ├── k8s ├── admission.go ├── admission_test.go ├── apiserver │ ├── admission.go │ ├── admission_test.go │ ├── cmd │ │ └── server │ │ │ └── start.go │ ├── config.go │ ├── connector.go │ ├── installer.go │ ├── installer_test.go │ ├── openapi.go │ ├── options.go │ ├── storage.go │ └── strategy.go ├── cache │ ├── controller.go │ └── reflector.go ├── client.go ├── client_registry.go ├── client_test.go ├── constants.go ├── conversion.go ├── doc.go ├── dynamic.go ├── errors.go ├── errors_test.go ├── gvclient.go ├── manager.go ├── manager_test.go ├── namespace.go ├── negotiator.go ├── negotiator_test.go ├── schemaless.go ├── schemaless_test.go ├── stream_error.go ├── stream_error_test.go ├── telemetry.go ├── translation.go ├── translation_test.go ├── webhooks.go ├── webhooks_test.go └── wrappers.go ├── logging ├── go.mod ├── go.sum ├── logger.go └── slog.go ├── metrics ├── config.go └── metrics.go ├── operator ├── cache_memcached.go ├── concurrentwatcher.go ├── concurrentwatcher_test.go ├── errors.go ├── finalizers.go ├── informer_concurrent.go ├── informer_controller.go ├── informer_controller_test.go ├── informer_customcache.go ├── informer_customcache_test.go ├── informer_kubernetes.go ├── informer_kubernetes_test.go ├── informer_processor.go ├── informer_processor_test.go ├── kubeconfig.go ├── metrics_server.go ├── operator.go ├── operator_test.go ├── opinionatedwatcher.go ├── opinionatedwatcher_test.go ├── reconciler.go ├── reconciler_test.go ├── runner.go ├── simplewatcher.go ├── simplewatcher_test.go ├── telemetry.go ├── util.go └── util_test.go ├── plugin ├── README.md ├── errors.go ├── go.mod ├── go.sum ├── kubeconfig │ ├── config.go │ ├── initializer.go │ ├── initializer_test.go │ ├── loader.go │ ├── loader_test.go │ ├── middleware.go │ └── middleware_test.go ├── logging.go └── router │ ├── json_router.go │ ├── json_router_test.go │ ├── middleware.go │ ├── middleware_test.go │ ├── resourcegroup_router.go │ ├── resourcegroup_router_test.go │ ├── router.go │ ├── router_test.go │ ├── vars.go │ └── vars_test.go ├── resource ├── admission.go ├── client.go ├── kind.go ├── kind_test.go ├── manager.go ├── metadata.go ├── metadata_test.go ├── namespaced_client.go ├── object.go ├── object_test.go ├── schema.go ├── schema_test.go ├── simplestore.go ├── simplestore_test.go ├── store.go ├── store_test.go ├── typedclient.go ├── typedobject.go ├── typedobject_test.go ├── typedstore.go ├── typedstore_test.go ├── unstructured.go ├── untypedobject.go ├── updater.go ├── updater_test.go └── wrappedobject.go ├── scripts ├── regenerate_golden_test_files.sh └── release.sh └── simple ├── app.go ├── app_test.go ├── memcached.go ├── operator.go ├── reconciler.go ├── tracing.go └── watcher.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | groups: 8 | cue: 9 | patterns: 10 | - "*cue*" 11 | - "*thema*" 12 | - "*codejen*" 13 | all: 14 | exclude-patterns: 15 | - "*cue*" 16 | - "*thema*" 17 | - "*codejen*" 18 | patterns: 19 | - "*" 20 | labels: 21 | - "type/dependabot" 22 | - package-ecosystem: gomod 23 | directory: "/plugin/" 24 | schedule: 25 | interval: daily 26 | groups: 27 | all: 28 | patterns: 29 | - "*" 30 | # LTS branch 31 | - package-ecosystem: gomod 32 | directory: "/" 33 | target-branch: "lts/v0.24" 34 | schedule: 35 | interval: daily 36 | groups: 37 | cue: 38 | patterns: 39 | - "*cue*" 40 | - "*thema*" 41 | - "*codejen*" 42 | all: 43 | exclude-patterns: 44 | - "*cue*" 45 | - "*thema*" 46 | - "*codejen*" 47 | patterns: 48 | - "*" 49 | labels: 50 | - "type/dependabot" 51 | - package-ecosystem: gomod 52 | directory: "/plugin/" 53 | target-branch: "lts/v0.24" 54 | schedule: 55 | interval: daily 56 | groups: 57 | all: 58 | patterns: 59 | - "*" 60 | exclude-patterns: 61 | - "*grafana-app-sdk*" # Don't update the app-sdk version via dependabot for this branch -------------------------------------------------------------------------------- /.github/workflows/lts.yml: -------------------------------------------------------------------------------- 1 | name: Build LTS 2 | 3 | on: 4 | push: 5 | branches: 6 | - "lts/v0.24" 7 | 8 | jobs: 9 | test: 10 | permissions: 11 | contents: read 12 | uses: ./.github/workflows/test.yml -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build Main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | permissions: 11 | contents: read 12 | uses: ./.github/workflows/test.yml 13 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Build PR 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - "lts/v0.24" 8 | - "feat/*" 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | check: 15 | name: Go Workspace Check 16 | runs-on: ubuntu-latest 17 | # Exlude dependabot from this check--it can't run make update-workspace, 18 | # We'll just force the next PR author to do it instead for now 19 | if: ${{ github.actor != 'dependabot[bot]' }} 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 24 | with: 25 | persist-credentials: false 26 | 27 | - name: Set go version 28 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5 29 | with: 30 | go-version-file: 'go.mod' 31 | 32 | - name: Update workspace 33 | run: make update-workspace 34 | 35 | - name: Check for go mod & workspace changes 36 | run: | 37 | if ! git diff --exit-code --quiet; then 38 | echo "Changes detected:" 39 | git diff 40 | echo "Please run 'make update-workspace' and commit the changes." 41 | exit 1 42 | fi 43 | lint: 44 | runs-on: ubuntu-latest 45 | steps: 46 | # git checkout 47 | - name: Checkout code 48 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 49 | with: 50 | fetch-depth: 0 51 | persist-credentials: false 52 | # go env 53 | - name: Set up Go 54 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5 55 | with: 56 | go-version-file: 'go.mod' 57 | # make lint 58 | - name: Lint 59 | uses: golangci/golangci-lint-action@b002b6ecfcabe6ac0e2c6cba1bcc779eb34ac51f 60 | with: 61 | version: v2.5.0 62 | only-new-issues: true 63 | args: --timeout 5m $(go list -f '{{.Dir}}/...' -m | tr '\n' ' ') 64 | test: 65 | uses: ./.github/workflows/test.yml 66 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | test: 10 | permissions: 11 | contents: read 12 | uses: ./.github/workflows/test.yml 13 | release: 14 | permissions: 15 | contents: write 16 | needs: ['test'] 17 | runs-on: ubuntu-latest 18 | steps: 19 | # git checkout 20 | - name: Checkout code 21 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 22 | with: 23 | fetch-depth: 0 24 | persist-credentials: false 25 | # Go env 26 | - name: Set up Go 27 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5 28 | with: 29 | go-version-file: 'go.mod' 30 | cache: false 31 | # Release 32 | - name: Create Release 33 | uses: goreleaser/goreleaser-action@a08664b80c0ab417b1babcbf750274aed2018fef 34 | with: 35 | distribution: goreleaser 36 | version: latest 37 | args: release --clean 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | -------------------------------------------------------------------------------- /.github/zizmor.yml: -------------------------------------------------------------------------------- 1 | # This is also used as the default configuration for the Zizmor reusable workflow. 2 | 3 | rules: 4 | unpinned-uses: 5 | config: 6 | policies: 7 | actions/*: any # trust GitHub 8 | grafana/*: any # trust Grafana 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | vendor/ 16 | 17 | # IDE files 18 | .idea 19 | 20 | # Binaries 21 | target/** 22 | 23 | # Vendored dependencies 24 | vendor/ 25 | 26 | # gorelease dist dir 27 | dist/ 28 | 29 | # WIP docs 30 | docs/wip 31 | 32 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Make sure to check the documentation at https://goreleaser.com 2 | project_name: grafana-app-sdk 3 | before: 4 | hooks: 5 | # TODO: 6 | # We currently don't use code generation in the SDK itself 7 | # but we do use it in the examples and that breaks since it requires 8 | # the SDK binary to be present in the first place. 9 | # - go generate ./... 10 | - go mod tidy 11 | builds: 12 | - id: grafana-app-sdk 13 | binary: grafana-app-sdk 14 | main: ./cmd/grafana-app-sdk 15 | env: 16 | - CGO_ENABLED=0 17 | goos: 18 | - linux 19 | - windows 20 | - darwin 21 | goarch: 22 | - amd64 23 | - arm64 24 | checksum: 25 | name_template: 'checksums.txt' 26 | snapshot: 27 | name_template: "{{ incpatch .Version }}-next" 28 | changelog: 29 | sort: asc 30 | filters: 31 | exclude: 32 | - '^docs:' 33 | - '^test:' -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | AGENTS.md -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://help.github.com/articles/about-codeowners/ 2 | # https://git-scm.com/docs/gitignore#_pattern_format 3 | 4 | # See the following docs for more details about order in CODEOWNERS 5 | # https://github.community/t/order-of-owners-in-codeowners-file/143073 6 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax 7 | 8 | * @grafana/grafana-app-platform-squad 9 | -------------------------------------------------------------------------------- /app/appmanifest/v1alpha1/appmanifest_codec_gen.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by grafana-app-sdk. DO NOT EDIT. 3 | // 4 | 5 | package v1alpha1 6 | 7 | import ( 8 | "encoding/json" 9 | "io" 10 | 11 | "github.com/grafana/grafana-app-sdk/resource" 12 | ) 13 | 14 | // AppManifestJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding 15 | type AppManifestJSONCodec struct{} 16 | 17 | // Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` 18 | func (*AppManifestJSONCodec) Read(reader io.Reader, into resource.Object) error { 19 | return json.NewDecoder(reader).Decode(into) 20 | } 21 | 22 | // Write writes JSON-encoded bytes into `writer` marshaled from `from` 23 | func (*AppManifestJSONCodec) Write(writer io.Writer, from resource.Object) error { 24 | return json.NewEncoder(writer).Encode(from) 25 | } 26 | 27 | // Interface compliance checks 28 | var _ resource.Codec = &AppManifestJSONCodec{} 29 | -------------------------------------------------------------------------------- /app/appmanifest/v1alpha1/appmanifest_metadata_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated - EDITING IS FUTILE. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | time "time" 7 | ) 8 | 9 | // metadata contains embedded CommonMetadata and can be extended with custom string fields 10 | // TODO: use CommonMetadata instead of redefining here; currently needs to be defined here 11 | // without external reference as using the CommonMetadata reference breaks thema codegen. 12 | type AppManifestMetadata struct { 13 | UpdateTimestamp time.Time `json:"updateTimestamp"` 14 | CreatedBy string `json:"createdBy"` 15 | Uid string `json:"uid"` 16 | CreationTimestamp time.Time `json:"creationTimestamp"` 17 | DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` 18 | Finalizers []string `json:"finalizers"` 19 | ResourceVersion string `json:"resourceVersion"` 20 | Generation int64 `json:"generation"` 21 | UpdatedBy string `json:"updatedBy"` 22 | Labels map[string]string `json:"labels"` 23 | } 24 | 25 | // NewAppManifestMetadata creates a new AppManifestMetadata object. 26 | func NewAppManifestMetadata() *AppManifestMetadata { 27 | return &AppManifestMetadata{ 28 | Finalizers: []string{}, 29 | Labels: map[string]string{}, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/appmanifest/v1alpha1/appmanifest_schema_gen.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by grafana-app-sdk. DO NOT EDIT. 3 | // 4 | 5 | package v1alpha1 6 | 7 | import ( 8 | "github.com/grafana/grafana-app-sdk/resource" 9 | ) 10 | 11 | // schema is unexported to prevent accidental overwrites 12 | var ( 13 | schemaAppManifest = resource.NewSimpleSchema("apps.grafana.com", "v1alpha1", &AppManifest{}, &AppManifestList{}, resource.WithKind("AppManifest"), 14 | resource.WithPlural("appmanifests"), resource.WithScope(resource.ClusterScope)) 15 | kindAppManifest = resource.Kind{ 16 | Schema: schemaAppManifest, 17 | Codecs: map[resource.KindEncoding]resource.Codec{ 18 | resource.KindEncodingJSON: &AppManifestJSONCodec{}, 19 | }, 20 | } 21 | ) 22 | 23 | // Kind returns a resource.Kind for this Schema with a JSON codec 24 | func AppManifestKind() resource.Kind { 25 | return kindAppManifest 26 | } 27 | 28 | // Schema returns a resource.SimpleSchema representation of AppManifest 29 | func AppManifestSchema() *resource.SimpleSchema { 30 | return schemaAppManifest 31 | } 32 | 33 | // Interface compliance checks 34 | var _ resource.Schema = kindAppManifest 35 | -------------------------------------------------------------------------------- /app/appmanifest/v1alpha1/constants.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import "k8s.io/apimachinery/pkg/runtime/schema" 4 | 5 | const ( 6 | // APIGroup is the API group used by all kinds in this package 7 | APIGroup = "apps.grafana.com" 8 | // APIVersion is the API version used by all kinds in this package 9 | APIVersion = "v1alpha1" 10 | ) 11 | 12 | var ( 13 | // GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package 14 | GroupVersion = schema.GroupVersion{ 15 | Group: APIGroup, 16 | Version: APIVersion, 17 | } 18 | ) 19 | -------------------------------------------------------------------------------- /app/appmanifest/v1alpha1/conversion_test.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | 12 | "github.com/grafana/grafana-app-sdk/app" 13 | ) 14 | 15 | func TestAppManifestSpec_ToManifestData(t *testing.T) { 16 | t.Run("successful conversion", func(t *testing.T) { 17 | // For v1alpha1, app.ManifestData is essentially a subset of v1alpha1.AppManifestSpec, 18 | // so we only need to check that the same JSON loaded for the AppManifestSpec and using ToManifestData() 19 | // is identical to loading that JSON for app.ManifestData 20 | file, err := os.ReadFile(filepath.Join("testfiles", "spec-01.json")) 21 | require.Nil(t, err) 22 | v1alpha1 := AppManifestSpec{} 23 | md := app.ManifestData{} 24 | err = json.Unmarshal(file, &v1alpha1) 25 | require.Nil(t, err) 26 | err = json.Unmarshal(file, &md) 27 | require.Nil(t, err) 28 | schFile, err := os.ReadFile(filepath.Join("testfiles", "schema-01.json")) 29 | require.Nil(t, err) 30 | m := make(map[string]any) 31 | err = json.Unmarshal(schFile, &m) 32 | require.Nil(t, err) 33 | md.Versions[0].Kinds[0].Schema, err = app.VersionSchemaFromMap(m, md.Versions[0].Kinds[0].Kind) 34 | require.Nil(t, err) 35 | v1md, err := v1alpha1.ToManifestData() 36 | require.Nil(t, err) 37 | assert.Equal(t, md, v1md) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /app/appmanifest/v1alpha2/appmanifest_codec_gen.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by grafana-app-sdk. DO NOT EDIT. 3 | // 4 | 5 | package v1alpha2 6 | 7 | import ( 8 | "encoding/json" 9 | "io" 10 | 11 | "github.com/grafana/grafana-app-sdk/resource" 12 | ) 13 | 14 | // AppManifestJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding 15 | type AppManifestJSONCodec struct{} 16 | 17 | // Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` 18 | func (*AppManifestJSONCodec) Read(reader io.Reader, into resource.Object) error { 19 | return json.NewDecoder(reader).Decode(into) 20 | } 21 | 22 | // Write writes JSON-encoded bytes into `writer` marshaled from `from` 23 | func (*AppManifestJSONCodec) Write(writer io.Writer, from resource.Object) error { 24 | return json.NewEncoder(writer).Encode(from) 25 | } 26 | 27 | // Interface compliance checks 28 | var _ resource.Codec = &AppManifestJSONCodec{} 29 | -------------------------------------------------------------------------------- /app/appmanifest/v1alpha2/appmanifest_metadata_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated - EDITING IS FUTILE. DO NOT EDIT. 2 | 3 | package v1alpha2 4 | 5 | import ( 6 | time "time" 7 | ) 8 | 9 | // metadata contains embedded CommonMetadata and can be extended with custom string fields 10 | // TODO: use CommonMetadata instead of redefining here; currently needs to be defined here 11 | // without external reference as using the CommonMetadata reference breaks thema codegen. 12 | type AppManifestMetadata struct { 13 | UpdateTimestamp time.Time `json:"updateTimestamp"` 14 | CreatedBy string `json:"createdBy"` 15 | Uid string `json:"uid"` 16 | CreationTimestamp time.Time `json:"creationTimestamp"` 17 | DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` 18 | Finalizers []string `json:"finalizers"` 19 | ResourceVersion string `json:"resourceVersion"` 20 | Generation int64 `json:"generation"` 21 | UpdatedBy string `json:"updatedBy"` 22 | Labels map[string]string `json:"labels"` 23 | } 24 | 25 | // NewAppManifestMetadata creates a new AppManifestMetadata object. 26 | func NewAppManifestMetadata() *AppManifestMetadata { 27 | return &AppManifestMetadata{ 28 | Finalizers: []string{}, 29 | Labels: map[string]string{}, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/appmanifest/v1alpha2/appmanifest_schema_gen.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by grafana-app-sdk. DO NOT EDIT. 3 | // 4 | 5 | package v1alpha2 6 | 7 | import ( 8 | "github.com/grafana/grafana-app-sdk/resource" 9 | ) 10 | 11 | // schema is unexported to prevent accidental overwrites 12 | var ( 13 | schemaAppManifest = resource.NewSimpleSchema("apps.grafana.com", "v1alpha2", &AppManifest{}, &AppManifestList{}, resource.WithKind("AppManifest"), 14 | resource.WithPlural("appmanifests"), resource.WithScope(resource.ClusterScope)) 15 | kindAppManifest = resource.Kind{ 16 | Schema: schemaAppManifest, 17 | Codecs: map[resource.KindEncoding]resource.Codec{ 18 | resource.KindEncodingJSON: &AppManifestJSONCodec{}, 19 | }, 20 | } 21 | ) 22 | 23 | // Kind returns a resource.Kind for this Schema with a JSON codec 24 | func AppManifestKind() resource.Kind { 25 | return kindAppManifest 26 | } 27 | 28 | // Schema returns a resource.SimpleSchema representation of AppManifest 29 | func AppManifestSchema() *resource.SimpleSchema { 30 | return schemaAppManifest 31 | } 32 | 33 | // Interface compliance checks 34 | var _ resource.Schema = kindAppManifest 35 | -------------------------------------------------------------------------------- /app/appmanifest/v1alpha2/constants.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import "k8s.io/apimachinery/pkg/runtime/schema" 4 | 5 | const ( 6 | // APIGroup is the API group used by all kinds in this package 7 | APIGroup = "apps.grafana.com" 8 | // APIVersion is the API version used by all kinds in this package 9 | APIVersion = "v1alpha2" 10 | ) 11 | 12 | var ( 13 | // GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package 14 | GroupVersion = schema.GroupVersion{ 15 | Group: APIGroup, 16 | Version: APIVersion, 17 | } 18 | ) 19 | -------------------------------------------------------------------------------- /app/appmanifest/v1alpha2/conversion_test.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/grafana/grafana-app-sdk/app" 14 | ) 15 | 16 | func TestAppManifestSpec_ToManifestData(t *testing.T) { 17 | t.Run("successful conversion", func(t *testing.T) { 18 | // For v1alpha1, app.ManifestData is essentially a subset of v1alpha1.AppManifestSpec, 19 | // so we only need to check that the same JSON loaded for the AppManifestSpec and using ToManifestData() 20 | // is identical to loading that JSON for app.ManifestData 21 | file, err := os.ReadFile(filepath.Join("testfiles", "spec-01.json")) 22 | require.Nil(t, err) 23 | v1alpha1 := AppManifestSpec{} 24 | md := app.ManifestData{} 25 | err = json.Unmarshal(file, &v1alpha1) 26 | require.Nil(t, err) 27 | err = json.Unmarshal(file, &md) 28 | schFile, err := os.ReadFile(filepath.Join("testfiles", "schema-01.json")) 29 | require.Nil(t, err) 30 | m := make(map[string]any) 31 | err = json.Unmarshal(schFile, &m) 32 | require.Nil(t, err) 33 | md.Versions[0].Kinds[0].Schema, err = app.VersionSchemaFromMap(m, md.Versions[0].Kinds[0].Kind) 34 | require.Nil(t, err) 35 | require.Nil(t, err) 36 | v1md, err := v1alpha1.ToManifestData() 37 | require.Nil(t, err) 38 | assert.Equal(t, md, v1md) 39 | }) 40 | 41 | t.Run("bad schema data", func(t *testing.T) { 42 | v1alpha1 := AppManifestSpec{ 43 | Versions: []AppManifestManifestVersion{{ 44 | Kinds: []AppManifestManifestVersionKind{{ 45 | Kind: "Foo", 46 | Schemas: map[string]interface{}{ 47 | "bar": "foo", // Bad OpenAPI document, conversion will fail when loading the openAPI 48 | }, 49 | }}, 50 | }}, 51 | } 52 | _, err := v1alpha1.ToManifestData() 53 | assert.Equal(t, errors.New("schemas for Foo must contain an entry named 'Foo'"), err) 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /app/cue.mod/module.cue: -------------------------------------------------------------------------------- 1 | module: "github.com/grafana/grafana-app-sdk/app" 2 | language: { 3 | version: "v0.8.0" 4 | } 5 | -------------------------------------------------------------------------------- /app/manifest.cue: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | manifest: { 4 | appName: "app-manifest" 5 | groupOverride: "apps.grafana.com" 6 | versions: { 7 | "v1alpha1": { 8 | codegen: ts: enabled: false 9 | kinds: [appManifestv1alpha1] 10 | } 11 | "v1alpha2": { 12 | codegen: ts: enabled: false 13 | kinds: [appManifestv1alpha2] 14 | } 15 | } 16 | extraPermissions: { 17 | accessKinds: [{ 18 | group: "apiextensions.k8s.io", 19 | resource: "customresourcedefinitions", 20 | actions: ["get","list","create","update","delete","watch"], 21 | }] 22 | } 23 | } 24 | 25 | appManifestKind: { 26 | kind: "AppManifest" 27 | scope: "Cluster" 28 | codegen: { 29 | ts: enabled: false 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cmd/grafana-app-sdk/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | FormatCUE = "cue" 11 | FormatNone = "none" 12 | ) 13 | 14 | var rootCmd = &cobra.Command{ 15 | Use: "grafana-app-sdk ", 16 | Short: "A tool for working with grafana apps, used for generating code from CUE kinds, creating project boilerplate, and running local deployments", 17 | Long: "A tool for working with grafana apps, used for generating code from CUE kinds, creating project boilerplate, and running local deployments", 18 | } 19 | 20 | // Persistent flags for all commands 21 | const ( 22 | sourceFlag = "source" 23 | formatFlag = "format" 24 | selectorFlag = "manifest" 25 | genOperatorStateFlag = "genoperatorstate" 26 | ) 27 | 28 | func main() { 29 | rootCmd.PersistentFlags().StringP(sourceFlag, "s", "kinds", "Path to directory with your codegen source files (such as a CUE module)") 30 | rootCmd.PersistentFlags().StringP(formatFlag, "f", FormatCUE, "Format in which kinds are written for this project (currently allowed values are 'cue')") 31 | rootCmd.PersistentFlags().String(selectorFlag, "manifest", "Path selector to use for the manifest") 32 | rootCmd.PersistentFlags().Bool(genOperatorStateFlag, true, "Generate operator state code") 33 | 34 | setupVersionCmd() 35 | setupGenerateCmd() 36 | setupProjectCmd() 37 | 38 | rootCmd.AddCommand(versionCmd) 39 | rootCmd.AddCommand(generateCmd) 40 | rootCmd.AddCommand(projectCmd) 41 | 42 | err := rootCmd.Execute() 43 | if err != nil { 44 | os.Exit(1) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cmd/grafana-app-sdk/project_local_datasources.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Datasource configuration. New datasources for the local dev environment codegen should go here 4 | var ( 5 | localDatasourceConfigs = map[string]dataSourceConfig{ 6 | "cortex": { 7 | Access: "proxy", 8 | Name: "grafana-k3d-cortex-prom", 9 | Type: "prometheus", 10 | UID: "grafana-prom-cortex", 11 | URL: "http://cortex.default.svc.cluster.local:9009/api/prom", //nolint:revive 12 | Dependencies: []string{"minio"}, 13 | }, 14 | "tempo": { 15 | Access: "proxy", 16 | Name: "grafana-k3d-tempo", 17 | Type: "tempo", 18 | UID: "grafana-traces-tempo", 19 | URL: "http://tempo.default.svc.cluster.local:3100", //nolint:revive 20 | }, 21 | "loki": { 22 | Access: "proxy", 23 | Name: "grafana-k3d-loki", 24 | Type: "loki", 25 | UID: "grafana-logs-loki", 26 | URL: "http://loki.default.svc.cluster.local:3100", //nolint:revive 27 | Dependencies: []string{"minio", "agent"}, 28 | }, 29 | } 30 | 31 | localDatasourceFiles = map[string][]string{ 32 | "cortex": {"templates/local/generated/datasources/cortex.yaml"}, 33 | "tempo": {"templates/local/generated/datasources/tempo.yaml"}, 34 | "loki": {"templates/local/generated/datasources/loki.yaml"}, 35 | } 36 | 37 | localDatasourceDependencyManifests = map[string][]string{ 38 | "minio": {"templates/local/generated/datasources/minio.yaml"}, 39 | "agent": {"templates/local/generated/datasources/agent.yaml"}, 40 | } 41 | ) 42 | -------------------------------------------------------------------------------- /cmd/grafana-app-sdk/templates/Magefile.go.tmpl: -------------------------------------------------------------------------------- 1 | //go:build mage 2 | // +build mage 3 | 4 | package main 5 | 6 | import ( 7 | // mage:import 8 | build "github.com/grafana/grafana-plugin-sdk-go/build" 9 | ) 10 | 11 | // Default configures the default target. 12 | var Default = build.BuildAll 13 | -------------------------------------------------------------------------------- /cmd/grafana-app-sdk/templates/constants.ts.tmpl: -------------------------------------------------------------------------------- 1 | import { NavItem } from 'types'; 2 | 3 | export const PLUGIN_ID = "{{.PluginID}}-app"; 4 | export const PLUGIN_BASE_URL = `/a/${PLUGIN_ID}`; 5 | export const PLUGIN_API_URL = `/api/plugins/${PLUGIN_ID}/resources/v1`; 6 | 7 | export enum ROUTES { 8 | Main = 'todos', 9 | } 10 | 11 | export const NAVIGATION_TITLE = 'Basic App Plugin'; 12 | export const NAVIGATION_SUBTITLE = 'Some extra description...'; 13 | 14 | // Add a navigation item for each route you would like to display in the navigation bar 15 | export const NAVIGATION: Record = { 16 | [ROUTES.Main]: { 17 | id: ROUTES.Main, 18 | text: 'Main Page', 19 | icon: 'database', 20 | url: `${PLUGIN_BASE_URL}/main`, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /cmd/grafana-app-sdk/templates/frontend-static/.config/.eslintrc: -------------------------------------------------------------------------------- 1 | /* 2 | * ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️ 3 | * 4 | * In order to extend the configuration follow the steps in 5 | * https://grafana.com/developers/plugin-tools/create-a-plugin/extend-a-plugin/extend-configurations#extend-the-eslint-config 6 | */ 7 | { 8 | "extends": ["@grafana/eslint-config"], 9 | "root": true, 10 | "rules": { 11 | "react/prop-types": "off" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cmd/grafana-app-sdk/templates/frontend-static/.config/.prettierrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️ 3 | * 4 | * In order to extend the configuration follow the steps in .config/README.md 5 | */ 6 | 7 | module.exports = { 8 | "endOfLine": "auto", 9 | "printWidth": 120, 10 | "trailingComma": "es5", 11 | "semi": true, 12 | "jsxSingleQuote": false, 13 | "singleQuote": true, 14 | "useTabs": false, 15 | "tabWidth": 2 16 | }; -------------------------------------------------------------------------------- /cmd/grafana-app-sdk/templates/frontend-static/.config/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG grafana_version=latest@sha256:0fa9cbdd2826d3866238a22f12330aa70f263129324509756fe0ee1b570a8e21 2 | ARG grafana_image=grafana-enterprise 3 | 4 | FROM grafana/${grafana_image}:${grafana_version} 5 | 6 | # Make it as simple as possible to access the grafana instance for development purposes 7 | # Do NOT enable these settings in a public facing / production grafana instance 8 | ENV GF_AUTH_ANONYMOUS_ORG_ROLE "Admin" 9 | ENV GF_AUTH_ANONYMOUS_ENABLED "true" 10 | ENV GF_AUTH_BASIC_ENABLED "false" 11 | # Set development mode so plugins can be loaded without the need to sign 12 | ENV GF_DEFAULT_APP_MODE "development" 13 | 14 | # Inject livereload script into grafana index.html 15 | USER root 16 | RUN sed -i 's/<\/body><\/html>/