diff --git a/README.md b/README.md index 2381b9453..c0a37b47b 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ At the moment, the following artifacts kinds are supported *(with plans to suppo - [OLM operators](https://github.com/operator-framework) - [OpenCost plugins](https://www.opencost.io) - [Open Policy Agent (OPA) policies](https://www.openpolicyagent.org/) +- [Radius Recipes](https://radapp.io) - [Tekton tasks, pipelines and stepactions](https://tekton.dev/) - [Tinkerbell actions](https://tinkerbell.org/) diff --git a/charts/artifact-hub/Chart.yaml b/charts/artifact-hub/Chart.yaml index f2b0b54c1..f2e56d6ea 100644 --- a/charts/artifact-hub/Chart.yaml +++ b/charts/artifact-hub/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: artifact-hub description: Artifact Hub is a web-based application that enables finding, installing, and publishing Cloud Native packages. type: application -version: 1.19.1-0 +version: 1.19.1-1 appVersion: 1.19.0 kubeVersion: ">= 1.19.0-0" home: https://artifacthub.io @@ -32,6 +32,7 @@ keywords: - inspektor gadget - meshery - opencost + - radius maintainers: - name: Sergio email: tegioz@icloud.com diff --git a/charts/artifact-hub/values-production.yaml b/charts/artifact-hub/values-production.yaml index 55511fc89..b9a6bf036 100644 --- a/charts/artifact-hub/values-production.yaml +++ b/charts/artifact-hub/values-production.yaml @@ -119,6 +119,7 @@ tracker: requests: cpu: 2 memory: 4000Mi + repositoryTimeout: 25m trivy: deploy: diff --git a/charts/artifact-hub/values.schema.json b/charts/artifact-hub/values.schema.json index 29d715d71..b0f2a26a5 100644 --- a/charts/artifact-hub/values.schema.json +++ b/charts/artifact-hub/values.schema.json @@ -1162,7 +1162,7 @@ }, "repositoriesKinds": { "title": "Repositories kinds to process ([] = all)", - "description": "The following kinds are supported at the moment: falco, helm, olm, opa, tbaction, krew, helm-plugin, tekton-task, keda-scaler, coredns, keptn, tekton-pipeline, container, kubewarden, gatekeeper, kyverno, knative-client-plugin, backstage, argo-template, kubearmor, kcl, headlamp, inspektor-gadget, tekton-stepaction, meshery, opencost", + "description": "The following kinds are supported at the moment: falco, helm, olm, opa, tbaction, krew, helm-plugin, tekton-task, keda-scaler, coredns, keptn, tekton-pipeline, container, kubewarden, gatekeeper, kyverno, knative-client-plugin, backstage, argo-template, kubearmor, kcl, headlamp, inspektor-gadget, tekton-stepaction, meshery, opencost, radius", "type": "array", "items": { "type": "string" diff --git a/cmd/ah/lint.go b/cmd/ah/lint.go index 4037a712e..237ea366f 100644 --- a/cmd/ah/lint.go +++ b/cmd/ah/lint.go @@ -97,7 +97,7 @@ func newLintCmd() *cobra.Command { return lint(opts, &output{cmd.OutOrStdout()}) }, } - lintCmd.Flags().StringVarP(&opts.kind, "kind", "k", "helm", "repository kind: argo-template, backstage, coredns, falco, gatekeeper, headlamp, helm, helm-plugin, inspektor-gadget, kcl, keda-scaler, keptn, knative-client-plugin, krew, kubearmor, kubewarden, kyverno, meshery, olm, opa, opencost, tbaction, tekton-pipeline, tekton-stepaction, tekton-task") + lintCmd.Flags().StringVarP(&opts.kind, "kind", "k", "helm", "repository kind: argo-template, backstage, coredns, falco, gatekeeper, headlamp, helm, helm-plugin, inspektor-gadget, kcl, keda-scaler, keptn, knative-client-plugin, krew, kubearmor, kubewarden, kyverno, meshery, olm, opa, opencost, radius, tbaction, tekton-pipeline, tekton-stepaction, tekton-task") lintCmd.Flags().StringVarP(&opts.path, "path", "p", ".", "repository's packages path") lintCmd.Flags().StringVarP(&opts.tektonVersioning, "tekton-versioning", "", hub.TektonDirBasedVersioning, "tekton versioning option: directory, git") return lintCmd @@ -134,6 +134,7 @@ func lint(opts *lintOptions, out *output) error { hub.Meshery, hub.OPA, hub.OpenCost, + hub.Radius, hub.TBAction: report = lintGeneric(opts.path, kind) case hub.Helm: @@ -761,6 +762,7 @@ func (out *output) printPkgDetails(pkg *hub.Package) { hub.Meshery, hub.OPA, hub.OpenCost, + hub.Radius, hub.TBAction: // Install diff --git a/database/migrations/schema/060_radius_recipes.sql b/database/migrations/schema/060_radius_recipes.sql new file mode 100644 index 000000000..d30b0f09a --- /dev/null +++ b/database/migrations/schema/060_radius_recipes.sql @@ -0,0 +1,5 @@ +insert into repository_kind values (26, 'Radius recipes'); + +---- create above / drop below ---- + +delete from repository_kind where repository_kind_id = 26; diff --git a/database/tests/schema/schema.sql b/database/tests/schema/schema.sql index 17ed8aa55..b9c540258 100644 --- a/database/tests/schema/schema.sql +++ b/database/tests/schema/schema.sql @@ -567,7 +567,8 @@ select results_eq( (22, 'Inspektor gadgets'), (23, 'Tekton stepactions'), (24, 'Meshery designs'), - (25, 'OpenCost plugins') + (25, 'OpenCost plugins'), + (26, 'Radius recipes') $$, 'Repository kinds should exist' ); diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 7668605f7..ab65d5601 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -1621,6 +1621,29 @@ paths: $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalServerError" + "/packages/radius/{repoName}/{packageName}": + get: + tags: + - Packages + summary: Get package details + description: Get package details + operationId: getRadiusRecipeDetails + parameters: + - $ref: "#/components/parameters/RepoNameParam" + - $ref: "#/components/parameters/PackageNameParam" + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: "#/components/schemas/RadiusRecipe" + "404": + $ref: "#/components/responses/NotFoundResponse" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalServerError" "/packages/tbaction/{repoName}/{packageName}": get: tags: @@ -2242,6 +2265,30 @@ paths: $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalServerError" + "/packages/radius/{repoName}/{packageName}/{version}": + get: + tags: + - Packages + summary: Get package version details + description: Get package version details + operationId: getRadiusRecipeVersionDetails + parameters: + - $ref: "#/components/parameters/RepoNameParam" + - $ref: "#/components/parameters/PackageNameParam" + - $ref: "#/components/parameters/VersionParam" + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: "#/components/schemas/RadiusRecipe" + "404": + $ref: "#/components/responses/NotFoundResponse" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalServerError" "/packages/tbaction/{repoName}/{packageName}/{version}": get: tags: @@ -4509,6 +4556,18 @@ components: condition: (evt.num < 0) OpencostPlugin: $ref: "#/components/schemas/Package" + RadiusRecipe: + allOf: + - $ref: "#/components/schemas/Package" + - type: object + properties: + data: + type: object + properties: + recipe: + type: object + nullable: false + additionalProperties: true TBActionPackage: $ref: "#/components/schemas/Package" TektonPipelinePackage: @@ -5019,6 +5078,7 @@ components: * `23` - Tekton stepactions * `24` - Meshery designs * `25` - Opencost plugins + * `26` - Radius recipes RepositoryKindParam: type: string enum: @@ -5048,6 +5108,7 @@ components: - tekton-stepaction - meshery - opencost + - radius description: | Repository kind name: * `helm` - Helm charts @@ -5076,6 +5137,7 @@ components: * `tekton-stepaction` - Tekton stepactions * `meshery` - Meshery designs * `opencost` - Opencost plugins + * `radius` - Radius recipes RepositorySummary: type: object required: @@ -5621,6 +5683,7 @@ components: * `23` - Tekton stepactions * `24` - Meshery designs * `25` - Opencost plugins + * `26` - Radius recipes PackageNameParam: in: path name: packageName diff --git a/docs/radius_recipes_repositories.md b/docs/radius_recipes_repositories.md new file mode 100644 index 000000000..a22b6a3c7 --- /dev/null +++ b/docs/radius_recipes_repositories.md @@ -0,0 +1,54 @@ +## Radius recipes repositories + +Radius recipes repositories are expected to be hosted in GitHub, GitLab or Bitbucket repos. When adding your repository to Artifact Hub, the url used **must** follow the following format: + +- `https://github.com/user/repo[/path/to/packages]` +- `https://gitlab.com/user/repo[/path/to/packages]` +- `https://bitbucket.org/user/repo[/path/to/packages]` + +By default the `master` branch is used, but it's possible to specify a different one from the UI. + +*Please NOTE that the repository URL used when adding the repository to Artifact Hub **must NOT** contain the git hosting platform specific parts, like **tree/branch**, just the path to your packages like it would show in the filesystem.* + +The *path/to/packages* provided can contain metadata for one or more packages. Each package version **must** be on a separate folder, and it's up to you to decide if you want to publish one or multiple versions of your package. + +The structure of a repository with multiple recipes packages and versions could look something like this: + +```sh +$ tree path/to/packages +path/to/packages +├── artifacthub-repo.yml +├── recipe1 +│   ├── 1.0.0 +│   │   ├── README.md +│   │   ├── artifacthub-pkg.yml +│   │   └── recipe.bicep +│   └── 2.0.0 +│      ├── README.md +│      ├── artifacthub-pkg.yml +│   └── recipe.bicep +└── recipe2 + └── 1.0.0 +       ├── README.md +      ├── artifacthub-pkg.yml +      ├── main.tf + └── variables.tf +``` + +This structure is flexible, and in some cases where you only have a package and a version it can be greatly simplified. In the case of a single package with a single version available at a time (the publisher doesn't want to make previous ones available, for example), the structure could look like this: + +```sh +$ tree path/to/packages +path/to/packages +├── artifacthub-repo.yml +└── recipe1 +    ├── README.md +    ├── recipe.bicep + └── artifacthub-pkg.yml +``` + +In the previous case, even the `package1` directory could be omitted. The reason is that both packages names and versions are read from the `artifacthub-pkg.yml` metadata file, so directories names are not used at all. + +Each package version **needs** an `artifacthub-pkg.yml` metadata file. Please see the file [spec](https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-pkg.yml) for more details. The [artifacthub-repo.yml](https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-repo.yml) repository metadata file shown above can be used to setup features like [Verified publisher](https://github.com/artifacthub/hub/blob/master/docs/repositories.md#verified-publisher) or [Ownership claim](https://github.com/artifacthub/hub/blob/master/docs/repositories.md#ownership-claim). This file must be located at `/path/to/packages`. + +Once you have added your repository, you are all set up. As you add new versions of your recipes packages or new packages to your git repository, they'll be automatically indexed and listed in Artifact Hub. diff --git a/docs/repositories.md b/docs/repositories.md index a477e8503..49234146b 100644 --- a/docs/repositories.md +++ b/docs/repositories.md @@ -25,6 +25,7 @@ The following repositories kinds are supported at the moment: - [OLM operators repositories](https://github.com/artifacthub/hub/blob/master/docs/olm_operators_repositories.md) - [OPA policies repositories](https://github.com/artifacthub/hub/blob/master/docs/opa_policies_repositories.md) - [OpenCost plugins repositories](https://github.com/artifacthub/hub/blob/master/docs/opencost_plugins_repositories.md) +- [Radius recipes repositories](https://github.com/artifacthub/hub/blob/master/docs/radius_recipes_repositories.md) - [Tekton pipelines repositories](https://github.com/artifacthub/hub/blob/master/docs/tekton_pipelines_repositories.md) - [Tekton tasks repositories](https://github.com/artifacthub/hub/blob/master/docs/tekton_tasks_repositories.md) - [Tekton stepactions repositories](https://github.com/artifacthub/hub/blob/master/docs/tekton_stepactions_repositories.md) diff --git a/docs/www/headers/radius_recipes_repositories b/docs/www/headers/radius_recipes_repositories new file mode 100644 index 000000000..ebcd57de0 --- /dev/null +++ b/docs/www/headers/radius_recipes_repositories @@ -0,0 +1,6 @@ +--- +title: "Radius recipes" +aliases: [ + "/radius_recipes_repositories", +] +--- diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 8de4e9cfc..44d02828a 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -265,7 +265,7 @@ func (h *Handlers) setupRouter() { r.Get("/stats", h.Packages.GetStats) r.With(corsMW).Get("/search", h.Packages.Search) r.With(h.Users.RequireLogin).Get("/starred", h.Packages.GetStarredByUser) - r.Route("/{^helm$|^falco$|^opa$|^olm|^tbaction|^krew|^helm-plugin|^tekton-task|^keda-scaler|^coredns|^keptn|^tekton-pipeline|^container|^kubewarden|^gatekeeper|^kyverno|^knative-client-plugin|^backstage|^argo-template|^kubearmor|^kcl|^headlamp|^inspektor-gadget|^tekton-stepaction|^meshery|^opencost$}/{repoName}/{packageName}", func(r chi.Router) { + r.Route("/{^helm$|^falco$|^opa$|^olm|^tbaction|^krew|^helm-plugin|^tekton-task|^keda-scaler|^coredns|^keptn|^tekton-pipeline|^container|^kubewarden|^gatekeeper|^kyverno|^knative-client-plugin|^backstage|^argo-template|^kubearmor|^kcl|^headlamp|^inspektor-gadget|^tekton-stepaction|^meshery|^opencost|^radius$}/{repoName}/{packageName}", func(r chi.Router) { r.Get("/feed/rss", h.Packages.RssFeed) r.With(corsMW).Get("/summary", h.Packages.GetSummary) r.Get("/{version}", h.Packages.Get) @@ -430,7 +430,7 @@ func (h *Handlers) setupRouter() { // Index special entry points r.Route("/packages", func(r chi.Router) { - r.Route("/{^helm$|^falco$|^opa$|^olm|^tbaction|^krew|^helm-plugin|^tekton-task|^keda-scaler|^coredns|^keptn|^tekton-pipeline|^container|^kubewarden|^gatekeeper|^kyverno|^knative-client-plugin|^backstage|^argo-template|^kubearmor|^kcl|^headlamp|^inspektor-gadget|^tekton-stepaction|^meshery|^opencost$}/{repoName}/{packageName}", func(r chi.Router) { + r.Route("/{^helm$|^falco$|^opa$|^olm|^tbaction|^krew|^helm-plugin|^tekton-task|^keda-scaler|^coredns|^keptn|^tekton-pipeline|^container|^kubewarden|^gatekeeper|^kyverno|^knative-client-plugin|^backstage|^argo-template|^kubearmor|^kcl|^headlamp|^inspektor-gadget|^tekton-stepaction|^meshery|^opencost|^radius$}/{repoName}/{packageName}", func(r chi.Router) { r.With(h.Packages.InjectIndexMeta).Get("/{version}", h.Static.Index) r.With(h.Packages.InjectIndexMeta).Get("/", h.Static.Index) }) diff --git a/internal/handlers/pkg/handlers_test.go b/internal/handlers/pkg/handlers_test.go index ac6aa7851..2c01c5bdc 100644 --- a/internal/handlers/pkg/handlers_test.go +++ b/internal/handlers/pkg/handlers_test.go @@ -2210,6 +2210,17 @@ func TestBuildURL(t *testing.T) { "2.0.0", baseURL + "/packages/opencost/repo1/pkg1/2.0.0", }, + { + &hub.Package{ + NormalizedName: "pkg1", + Repository: &hub.Repository{ + Kind: hub.Radius, + Name: "repo1", + }, + }, + "2.0.0", + baseURL + "/packages/radius/repo1/pkg1/2.0.0", + }, } for _, tc := range testCases { tc := tc diff --git a/internal/hub/repo.go b/internal/hub/repo.go index c1ddeb2a0..384258765 100644 --- a/internal/hub/repo.go +++ b/internal/hub/repo.go @@ -124,6 +124,9 @@ const ( // OpenCost represents a repository with OpenCost plugins. OpenCost RepositoryKind = 25 + + // Radius represents a repository with Radius recipes. + Radius RepositoryKind = 26 ) // GetKindName returns the name of the provided repository kind. @@ -173,6 +176,8 @@ func GetKindName(kind RepositoryKind) string { return "opa" case OpenCost: return "opencost" + case Radius: + return "radius" case TBAction: return "tbaction" case TektonPipeline: @@ -234,6 +239,8 @@ func GetKindFromName(kind string) (RepositoryKind, error) { return OPA, nil case "opencost": return OpenCost, nil + case "radius": + return Radius, nil case "tbaction": return TBAction, nil case "tekton-pipeline": diff --git a/internal/repo/manager.go b/internal/repo/manager.go index 9b3cfa556..e966430ca 100644 --- a/internal/repo/manager.go +++ b/internal/repo/manager.go @@ -101,6 +101,7 @@ var ( hub.OLM, hub.OPA, hub.OpenCost, + hub.Radius, hub.TBAction, hub.TektonPipeline, hub.TektonTask, @@ -304,6 +305,7 @@ func (m *Manager) ClaimOwnership(ctx context.Context, repoName, orgName string) hub.OLM, hub.OPA, hub.OpenCost, + hub.Radius, hub.TBAction, hub.TektonPipeline, hub.TektonTask, @@ -487,6 +489,7 @@ func (m *Manager) locateMetadataFile(r *hub.Repository, basePath string) string hub.OLM, hub.OPA, hub.OpenCost, + hub.Radius, hub.TBAction, hub.TektonPipeline, hub.TektonTask, @@ -855,6 +858,7 @@ func (m *Manager) validateURL(r *hub.Repository) error { hub.OLM, hub.OPA, hub.OpenCost, + hub.Radius, hub.TBAction, hub.TektonPipeline, hub.TektonTask, diff --git a/internal/tracker/helpers.go b/internal/tracker/helpers.go index 4a56d8fdc..bfd8b5865 100644 --- a/internal/tracker/helpers.go +++ b/internal/tracker/helpers.go @@ -129,6 +129,7 @@ func SetupSource(i *hub.TrackerSourceInput) hub.TrackerSource { hub.Meshery, hub.OPA, hub.OpenCost, + hub.Radius, hub.TBAction: source = generic.NewTrackerSource(i) case hub.TektonTask, hub.TektonPipeline, hub.TektonStepAction: diff --git a/internal/tracker/source/generic/generic.go b/internal/tracker/source/generic/generic.go index 1d3655d50..1a8de2e69 100644 --- a/internal/tracker/source/generic/generic.go +++ b/internal/tracker/source/generic/generic.go @@ -50,6 +50,10 @@ const ( // contains the raw policies. OPAPoliciesKey = "policies" + // RadiusRecipeKey represents the key used in the package's data field that + // contains the raw recipe files. + RadiusRecipeKey = "recipe" + // argoTemplateManifests represents the filename that contains the Argo // template manifests. argoTemplateManifests = "manifests.yaml" @@ -69,6 +73,18 @@ const ( // opaPoliciesSuffix is the suffix that each of the policies files in the // package must use. opaPoliciesSuffix = ".rego" + + // radiusBicepRecipe represents the filename that contains the Radius + // recipe file in Bicep format. + radiusBicepRecipe = "recipe.bicep" + + // radiusTFRecipe represents the filename that contains the Radius recipe + // file in Terraform format (main). + radiusTFRecipe = "main.tf" + + // radiusTFRecipeVariables represents the filename that contains the + // variables used by the Radius recipe file in Terraform format. + radiusTFRecipeVariables = "variables.tf" ) // TrackerSource is a hub.TrackerSource implementation used by several kinds @@ -230,6 +246,8 @@ func PreparePackage(r *hub.Repository, md *hub.PackageMetadata, pkgPath string) kindData, err = prepareMesheryData(pkgPath) case hub.OPA: kindData, err = prepareOPAData(pkgPath, ignorer) + case hub.Radius: + kindData, err = prepareRadiusData(pkgPath) } if err != nil { return nil, fmt.Errorf("error preparing package %s version %s data: %w", md.Name, md.Version, err) @@ -410,6 +428,37 @@ func prepareOPAData(pkgPath string, ignorer ignore.IgnoreParser) (map[string]int }, nil } +// prepareRadiusData reads and formats Radius specific data available in the +// path provided, returning the resulting data structure. +func prepareRadiusData(pkgPath string) (map[string]interface{}, error) { + // Read recipe files + files := make(map[string]string) + for _, fileName := range []string{radiusBicepRecipe, radiusTFRecipe, radiusTFRecipeVariables} { + filePath := path.Join(pkgPath, fileName) + content, err := util.ReadRegularFile(filePath) + if err == nil { + files[fileName] = string(content) + } else if !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("error reading recipe file (%s): %w", fileName, err) + } + } + + // Check recipe files found + _, bicepFound := files[radiusBicepRecipe] + _, tfFound := files[radiusTFRecipe] + if bicepFound && tfFound { + return nil, errors.New("invalid recipe: both bicep and terraform files found") + } + if len(files) == 0 { + return nil, errors.New("no recipe files found") + } + + // Return package data field + return map[string]interface{}{ + RadiusRecipeKey: files, + }, nil +} + // GetFilesWithSuffix returns the files with a given suffix in the path // provided, ignoring the ones the ignorer matches. func GetFilesWithSuffix(suffix, rootPath string, ignorer ignore.IgnoreParser) (map[string]string, error) { diff --git a/internal/tracker/tracker.go b/internal/tracker/tracker.go index f20e484f0..68078dc80 100644 --- a/internal/tracker/tracker.go +++ b/internal/tracker/tracker.go @@ -203,6 +203,7 @@ func (t *Tracker) cloneRepository() (string, string, error) { hub.Meshery, hub.OPA, hub.OpenCost, + hub.Radius, hub.TBAction, hub.TektonPipeline, hub.TektonTask, diff --git a/scripts/prepare-docs.sh b/scripts/prepare-docs.sh index 01226a10d..039f7742b 100755 --- a/scripts/prepare-docs.sh +++ b/scripts/prepare-docs.sh @@ -28,6 +28,7 @@ cat docs/www/headers/meshery_designs_repositories docs/meshery_designs_repositor cat docs/www/headers/olm_operators_repositories docs/olm_operators_repositories.md > docs/www/content/topics/repositories/olm-operators.md cat docs/www/headers/opa_policies_repositories docs/opa_policies_repositories.md > docs/www/content/topics/repositories/opa-policies.md cat docs/www/headers/opencost_plugins_repositories docs/opencost_plugins_repositories.md > docs/www/content/topics/repositories/opencost-plugins.md +cat docs/www/headers/radius_recipes_repositories docs/radius_recipes_repositories.md > docs/www/content/topics/repositories/radius-recipes.md cat docs/www/headers/tekton_pipelines_repositories docs/tekton_pipelines_repositories.md > docs/www/content/topics/repositories/tekton-pipelines.md cat docs/www/headers/tekton_tasks_repositories docs/tekton_tasks_repositories.md > docs/www/content/topics/repositories/tekton-tasks.md cat docs/www/headers/tekton_stepactions_repositories docs/tekton_stepactions_repositories.md > docs/www/content/topics/repositories/tekton-stepactions.md diff --git a/web/public/static/media/placeholder_pkg_radius.png b/web/public/static/media/placeholder_pkg_radius.png new file mode 100644 index 000000000..74828034f Binary files /dev/null and b/web/public/static/media/placeholder_pkg_radius.png differ diff --git a/web/public/static/media/radius-light.svg b/web/public/static/media/radius-light.svg new file mode 100644 index 000000000..5aff4d22b --- /dev/null +++ b/web/public/static/media/radius-light.svg @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/web/public/static/media/radius.svg b/web/public/static/media/radius.svg new file mode 100644 index 000000000..7e2e94365 --- /dev/null +++ b/web/public/static/media/radius.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/public/static/media/radius_icon.png b/web/public/static/media/radius_icon.png new file mode 100644 index 000000000..35fc6c401 Binary files /dev/null and b/web/public/static/media/radius_icon.png differ diff --git a/web/src/api/index.ts b/web/src/api/index.ts index 9b146b2d4..1f70a065f 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -95,6 +95,7 @@ class API_CLASS { 'crds_examples', 'examples', 'template', + 'recipe', ]; private HEADERS = { csrf: 'X-Csrf-Token', diff --git a/web/src/layout/common/Image.tsx b/web/src/layout/common/Image.tsx index a0507f97b..305282f1b 100644 --- a/web/src/layout/common/Image.tsx +++ b/web/src/layout/common/Image.tsx @@ -79,6 +79,8 @@ const Image = (props: Props) => { return '/static/media/placeholder_pkg_meshery.png'; case RepositoryKind.OpenCost: return '/static/media/placeholder_pkg_opencost.png'; + case RepositoryKind.RadiusRecipe: + return '/static/media/placeholder_pkg_radius.png'; default: return PLACEHOLDER_SRC; } diff --git a/web/src/layout/common/RepositoryIcon.test.tsx b/web/src/layout/common/RepositoryIcon.test.tsx index a64194e24..d4fd42008 100644 --- a/web/src/layout/common/RepositoryIcon.test.tsx +++ b/web/src/layout/common/RepositoryIcon.test.tsx @@ -163,6 +163,13 @@ describe('RepositoryIcon', () => { expect(icon).toHaveProperty('src', 'http://localhost/static/media/opencost-light.svg'); }); + it('renders Radius recipe icon', () => { + render(); + const icon = screen.getByAltText('Icon'); + expect(icon).toBeInTheDocument(); + expect(icon).toHaveProperty('src', 'http://localhost/static/media/radius-light.svg'); + }); + it('renders empty icon', () => { render(); expect(() => screen.getByAltText('Icon')).toThrow(); diff --git a/web/src/layout/common/RepositoryIcon.tsx b/web/src/layout/common/RepositoryIcon.tsx index 62ede6bf8..e19cc2f0a 100644 --- a/web/src/layout/common/RepositoryIcon.tsx +++ b/web/src/layout/common/RepositoryIcon.tsx @@ -113,6 +113,10 @@ const ICONS = { default: '/static/media/opencost.svg', white: '/static/media/opencost-light.svg', }, + [RepositoryKind.RadiusRecipe]: { + default: '/static/media/radius.svg', + white: '/static/media/radius-light.svg', + }, }; const RepositoryIcon = (props: Props) => { diff --git a/web/src/layout/controlPanel/repositories/Modal.tsx b/web/src/layout/controlPanel/repositories/Modal.tsx index e6601dd67..503909503 100644 --- a/web/src/layout/controlPanel/repositories/Modal.tsx +++ b/web/src/layout/controlPanel/repositories/Modal.tsx @@ -537,6 +537,17 @@ const RepositoryModal = (props: Props) => { ); break; + case RepositoryKind.RadiusRecipe: + link = ( + + Radius recipes + + ); + break; } if (isUndefined(link)) return; @@ -570,6 +581,7 @@ const RepositoryModal = (props: Props) => { case RepositoryKind.TektonStepAction: case RepositoryKind.MesheryDesign: case RepositoryKind.OpenCost: + case RepositoryKind.RadiusRecipe: return ( <>

{ RepositoryKind.TektonStepAction, RepositoryKind.MesheryDesign, RepositoryKind.OpenCost, + RepositoryKind.RadiusRecipe, ].includes(selectedKind) && (

{ RepositoryKind.TektonStepAction, RepositoryKind.MesheryDesign, RepositoryKind.OpenCost, + RepositoryKind.RadiusRecipe, ].includes(selectedKind) && (
diff --git a/web/src/layout/controlPanel/repositories/__snapshots__/Modal.test.tsx.snap b/web/src/layout/controlPanel/repositories/__snapshots__/Modal.test.tsx.snap index 22ee4af14..ded280603 100644 --- a/web/src/layout/controlPanel/repositories/__snapshots__/Modal.test.tsx.snap +++ b/web/src/layout/controlPanel/repositories/__snapshots__/Modal.test.tsx.snap @@ -169,6 +169,11 @@ exports[`Repository Modal - repositories section creates snapshot 1`] = ` > OpenCost plugins +
- +
+
+ +
+ Icon +
+ + Radius recipes + +
+
+
{ await waitFor(() => expect(API.getStats).toHaveBeenCalledTimes(1)); const links = await screen.findAllByRole('button'); - expect(links).toHaveLength(29); + expect(links).toHaveLength(30); expect(links[2]).toHaveProperty('href', 'https://github.com/artifacthub/hub'); expect(links[3]).toHaveProperty('href', 'https://cloud-native.slack.com/channels/artifact-hub'); @@ -157,10 +157,11 @@ describe('Home index', () => { expect(links[23]).toHaveProperty('href', 'https://github.com/operator-framework'); expect(links[24]).toHaveProperty('href', 'https://www.openpolicyagent.org/'); expect(links[25]).toHaveProperty('href', 'https://www.opencost.io/'); - expect(links[26]).toHaveProperty('href', 'https://tekton.dev/'); - expect(links[27]).toHaveProperty('href', 'https://tinkerbell.org/'); + expect(links[26]).toHaveProperty('href', 'https://radapp.io/'); + expect(links[27]).toHaveProperty('href', 'https://tekton.dev/'); + expect(links[28]).toHaveProperty('href', 'https://tinkerbell.org/'); - expect(links[28]).toHaveProperty('href', 'https://www.cncf.io/projects/'); + expect(links[29]).toHaveProperty('href', 'https://www.cncf.io/projects/'); }); }); }); diff --git a/web/src/layout/home/index.tsx b/web/src/layout/home/index.tsx index 4f121f5ba..0a51ff801 100644 --- a/web/src/layout/home/index.tsx +++ b/web/src/layout/home/index.tsx @@ -233,7 +233,7 @@ const HomeView = () => { kubectl plugins, Tekton tasks, pipelines and stepactions, KEDA scalers, CoreDNS plugins, Keptn integrations, container images, Kubewarden policies, Kyverno policies, Knative client, Backstage plugins, Argo templates, KubeArmor policies, KCL modules, Headlamp plugins, Inspektor gadgets, Meshery - designs and OpenCost plugins. + designs, OpenCost plugins and Radius recipes.
{
-
-
{
+ +
+ +
+ +
+ Radius recipes +
+
+
{ case RepositoryKind.InspektorGadget: case RepositoryKind.MesheryDesign: case RepositoryKind.OpenCost: + case RepositoryKind.RadiusRecipe: return ( <> {props.package.appVersion && ( diff --git a/web/src/layout/package/RecommendedPackages/index.tsx b/web/src/layout/package/RecommendedPackages/index.tsx index 736d9e21a..0eac23211 100644 --- a/web/src/layout/package/RecommendedPackages/index.tsx +++ b/web/src/layout/package/RecommendedPackages/index.tsx @@ -14,7 +14,7 @@ interface Props { } export const URL_regex = - /^https:\/\/([^\/?]+)\/packages\/(helm|falco|opa|olm|tbaction|krew|helm-plugin|tekton-task|keda-scaler|coredns|keptn|tekton-pipeline|kubewarden|gatekeeper|kyverno|knative-client-plugin|backstage|argo-template|kubearmor|inspektor-gadget|tekton-stepaction|meshery|opencost|container)\/([a-z0-9-]+)\/([a-z0-9-]+)$/; // eslint-disable-line + /^https:\/\/([^\/?]+)\/packages\/(helm|falco|opa|olm|tbaction|krew|helm-plugin|tekton-task|keda-scaler|coredns|keptn|tekton-pipeline|kubewarden|gatekeeper|kyverno|knative-client-plugin|backstage|argo-template|kubearmor|inspektor-gadget|tekton-stepaction|meshery|opencost|radius|container)\/([a-z0-9-]+)\/([a-z0-9-]+)$/; // eslint-disable-line const prepareRecommendations = (recommendations?: Recommendation[]): RecommendedPackage[] => { const list: RecommendedPackage[] = []; diff --git a/web/src/layout/package/index.tsx b/web/src/layout/package/index.tsx index b706e3701..d1ceff797 100644 --- a/web/src/layout/package/index.tsx +++ b/web/src/layout/package/index.tsx @@ -28,6 +28,7 @@ import { Package, PackageLink, PackageViewsStats, + RadiusRecipeData, Recommendation, RepositoryKind, Version, @@ -467,6 +468,20 @@ const PackageView = () => { return policy; }; + const getRadiusRecipeFiles = (): { [key: string]: string } | undefined => { + let files: { [key: string]: string } | undefined; + if ( + !isUndefined(detail) && + !isNull(detail) && + !isNull(detail.data) && + !isUndefined(detail.data) && + !isUndefined(detail.data[RadiusRecipeData.Recipe]) + ) { + files = detail.data[RadiusRecipeData.Recipe] as { [key: string]: string }; + } + return files; + }; + const getArgoTemplate = (): string | undefined => { let template: string | undefined; if ( @@ -578,6 +593,7 @@ const PackageView = () => { <> {(() => { let file: string | undefined; + let files: { [key: string]: string } | undefined; switch (detail.repository.kind) { case RepositoryKind.Krew: file = getManifestRaw(); @@ -747,6 +763,56 @@ const PackageView = () => { ); + case RepositoryKind.RadiusRecipe: + files = getRadiusRecipeFiles(); + if (!isUndefined(file)) { + additionalTitles += '# Recipe\n'; + } + return ( + <> + {!isUndefined(files) && ( +
+ + {Object.keys(files).map((fileName: string) => { + const content = files![fileName]; + const extension = fileName.split('.').pop(); + return ( +
+
{fileName}
+
+ + + {content} + +
+
+ ); + })} +
+ )} + + ); + default: return null; } diff --git a/web/src/types.ts b/web/src/types.ts index 7497030b0..9f6bbf516 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -27,6 +27,7 @@ export enum RepositoryKind { TektonStepAction, MesheryDesign, OpenCost, + RadiusRecipe, } export enum PackageCategory { @@ -83,6 +84,10 @@ export enum VersioningOption { Directory = 'directory', } +export enum RadiusRecipeData { + Recipe = 'recipe', +} + export interface Repository { repositoryId?: string; name: string; @@ -277,6 +282,7 @@ export interface PackageData { [ArgoTemplateData.Version]?: string; tasks?: TektonTaskInPipeline[]; alternativeLocations?: string[]; + [RadiusRecipeData.Recipe]?: { [key: string]: string }; } export interface TektonTaskInPipeline { diff --git a/web/src/utils/data.tsx b/web/src/utils/data.tsx index 0f873ec01..2f0242153 100644 --- a/web/src/utils/data.tsx +++ b/web/src/utils/data.tsx @@ -285,6 +285,15 @@ export const REPOSITORY_KINDS: RepoKindDef[] = [ icon: , active: true, }, + { + kind: RepositoryKind.RadiusRecipe, + label: 'radius', + name: 'Radius recipe', + singular: 'Radius recipe', + plural: 'Radius recipes', + icon: , + active: true, + }, { kind: RepositoryKind.TektonPipeline, label: 'tekton-pipeline', @@ -822,7 +831,7 @@ export const CVSS_V3_VECTORS: { [key: string]: CVSSVectorMetric[] } = { export const OCI_PREFIX = 'oci://'; export const PKG_DETAIL_PATH = - /^\/packages\/(helm|falco|opa|olm|tbaction|krew|helm-plugin|tekton-task|keda-scaler|coredns|keptn|tekton-pipeline|kubewarden|gatekeeper|kyverno|knative-client-plugin|backstage|argo-template|kubearmor|kcl|headlamp|inspektor-gadget|tekton-stepaction|meshery|opencost|container)\//; + /^\/packages\/(helm|falco|opa|olm|tbaction|krew|helm-plugin|tekton-task|keda-scaler|coredns|keptn|tekton-pipeline|kubewarden|gatekeeper|kyverno|knative-client-plugin|backstage|argo-template|kubearmor|kcl|headlamp|inspektor-gadget|tekton-stepaction|meshery|opencost|radius|container)\//; export const HOME_ROUTES = [ '/verify-email', diff --git a/web/src/utils/getInstallMethods.ts b/web/src/utils/getInstallMethods.ts index 7c6f664f5..81043e5f6 100644 --- a/web/src/utils/getInstallMethods.ts +++ b/web/src/utils/getInstallMethods.ts @@ -116,6 +116,7 @@ const getInstallMethods = (props: PackageInfo): InstallMethodOutput => { case RepositoryKind.InspektorGadget: case RepositoryKind.MesheryDesign: case RepositoryKind.OpenCost: + case RepositoryKind.RadiusRecipe: if (isUndefined(pkg.install)) { output.errorMessage = 'This package does not include installation instructions yet.'; hasError = true; diff --git a/web/src/utils/repoKind.test.tsx b/web/src/utils/repoKind.test.tsx index 2590dac7a..7b83605a4 100644 --- a/web/src/utils/repoKind.test.tsx +++ b/web/src/utils/repoKind.test.tsx @@ -91,6 +91,10 @@ describe('repoKind', () => { expect(methods.getRepoKind('opencost')).toBe(RepositoryKind.OpenCost); }); + it('radius', () => { + expect(methods.getRepoKind('radius')).toBe(RepositoryKind.RadiusRecipe); + }); + it('unknown', () => { expect(methods.getRepoKind('unknown')).toBeNull(); }); @@ -185,6 +189,10 @@ describe('repoKind', () => { expect(methods.getRepoKindName(RepositoryKind.OpenCost)).toBe('opencost'); }); + it('radius recipe kind', () => { + expect(methods.getRepoKindName(RepositoryKind.RadiusRecipe)).toBe('radius'); + }); + it('unknown kind', () => { expect(methods.getRepoKindName(50 as RepositoryKind)).toBeNull(); }); diff --git a/web/src/utils/repoKind.ts b/web/src/utils/repoKind.ts index b0f8789f9..170a20c43 100644 --- a/web/src/utils/repoKind.ts +++ b/web/src/utils/repoKind.ts @@ -54,6 +54,8 @@ const getRepoKind = (repoName: string): RepositoryKind | null => { return RepositoryKind.MesheryDesign; case 'opencost': return RepositoryKind.OpenCost; + case 'radius': + return RepositoryKind.RadiusRecipe; default: return null; } @@ -113,6 +115,8 @@ const getRepoKindName = (repoKind: RepositoryKind): string | null => { return 'meshery'; case RepositoryKind.OpenCost: return 'opencost'; + case RepositoryKind.RadiusRecipe: + return 'radius'; default: return null; } diff --git a/widget/src/layout/Widget.tsx b/widget/src/layout/Widget.tsx index fd5eb8e41..5f3f09d20 100644 --- a/widget/src/layout/Widget.tsx +++ b/widget/src/layout/Widget.tsx @@ -101,6 +101,8 @@ const getRepoKindName = (repoKind: RepositoryKind): string | null => { return 'meshery'; case RepositoryKind.OpenCost: return 'opencost'; + case RepositoryKind.RadiusRecipe: + return 'radius'; default: return null; } diff --git a/widget/src/layout/common/Image.test.tsx b/widget/src/layout/common/Image.test.tsx index 6cad13c2a..7a272bc53 100644 --- a/widget/src/layout/common/Image.test.tsx +++ b/widget/src/layout/common/Image.test.tsx @@ -193,6 +193,13 @@ describe('Image', () => { expect(image).toHaveProperty('src', 'https://localhost:8000/static/media/placeholder_pkg_opencost.png'); }); + it('renders Radius recipe icon', () => { + render(); + const image = screen.getByAltText('alt image'); + expect(image).toBeInTheDocument(); + expect(image).toHaveProperty('src', 'https://localhost:8000/static/media/placeholder_pkg_radius.png'); + }); + it('renders placeholder icon', () => { render(icon} />); expect(screen.getByText('icon')).toBeInTheDocument(); diff --git a/widget/src/layout/common/Image.tsx b/widget/src/layout/common/Image.tsx index 0413becf1..3761dfadc 100644 --- a/widget/src/layout/common/Image.tsx +++ b/widget/src/layout/common/Image.tsx @@ -80,6 +80,8 @@ const Image = (props: Props) => { return '/static/media/placeholder_pkg_meshery.png'; case RepositoryKind.OpenCost: return '/static/media/placeholder_pkg_opencost.png'; + case RepositoryKind.RadiusRecipe: + return '/static/media/placeholder_pkg_radius.png'; default: return PLACEHOLDER_SRC; } diff --git a/widget/src/layout/common/RepositoryIcon.tsx b/widget/src/layout/common/RepositoryIcon.tsx index b947ca67c..1e89fc0db 100644 --- a/widget/src/layout/common/RepositoryIcon.tsx +++ b/widget/src/layout/common/RepositoryIcon.tsx @@ -38,6 +38,7 @@ const ICONS: IconsList = { [RepositoryKind.TektonStepAction]: , [RepositoryKind.MesheryDesign]: , [RepositoryKind.OpenCost]: , + [RepositoryKind.RadiusRecipe]: , }; const RepositoryIcon = (props: Props) => ( diff --git a/widget/src/layout/common/RepositoryIconLabel.tsx b/widget/src/layout/common/RepositoryIconLabel.tsx index 8b37d1297..ee52a1e81 100644 --- a/widget/src/layout/common/RepositoryIconLabel.tsx +++ b/widget/src/layout/common/RepositoryIconLabel.tsx @@ -120,6 +120,10 @@ const REPOSITORY_KINDS: RepoKindDef[] = [ kind: RepositoryKind.OpenCost, name: 'OpenCost plugin', }, + { + kind: RepositoryKind.RadiusRecipe, + name: 'Radius recipe', + }, ]; const Wrapper = styled('span')` diff --git a/widget/src/layout/common/SVGIcons.test.tsx b/widget/src/layout/common/SVGIcons.test.tsx index 4562d7a16..99138ded4 100644 --- a/widget/src/layout/common/SVGIcons.test.tsx +++ b/widget/src/layout/common/SVGIcons.test.tsx @@ -138,6 +138,11 @@ describe('SVGIcons', () => { expect(screen.getByTitle('opencost')); }); + it('renders Radius recipe icon', () => { + render(); + expect(screen.getByTitle('radius')); + }); + it('does not render when name is not in the list', () => { render(); expect(screen.getByTestId('iconWrapper')).toBeEmptyDOMElement(); diff --git a/widget/src/layout/common/SVGIcons.tsx b/widget/src/layout/common/SVGIcons.tsx index 6c58ad624..afb1bba66 100644 --- a/widget/src/layout/common/SVGIcons.tsx +++ b/widget/src/layout/common/SVGIcons.tsx @@ -1255,6 +1255,40 @@ const SVGIcons = (props: Props) => ( ); + case 'radius': + return ( + + {props.name} + + + + + + + + + + ); + default: return null; } diff --git a/widget/src/types.ts b/widget/src/types.ts index 61b15835f..91493548d 100644 --- a/widget/src/types.ts +++ b/widget/src/types.ts @@ -54,6 +54,7 @@ export enum RepositoryKind { TektonStepAction, MesheryDesign, OpenCost, + RadiusRecipe, } export interface SearchResults {