diff --git a/README.md b/README.md index c0a37b47b5..319e58fb4c 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ At the moment, the following artifacts kinds are supported *(with plans to suppo - [Argo templates](https://argoproj.github.io/argo-workflows/) - [Backstage plugins](https://backstage.io) +- [Bootable containers](https://containers.github.io/bootc/) - [Containers images](https://opencontainers.org) - [CoreDNS plugins](https://coredns.io/) - [Falco configurations](https://falco.org/) diff --git a/charts/artifact-hub/Chart.yaml b/charts/artifact-hub/Chart.yaml index 46d627041a..b1a8a4951f 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.21.0-4 +version: 1.21.0-5 appVersion: 1.20.0 kubeVersion: ">= 1.19.0-0" home: https://artifacthub.io @@ -33,6 +33,7 @@ keywords: - meshery - opencost - radius + - bootable containers maintainers: - name: Sergio email: tegioz@icloud.com diff --git a/charts/artifact-hub/values.schema.json b/charts/artifact-hub/values.schema.json index 821c8d9970..bcd3080bbb 100644 --- a/charts/artifact-hub/values.schema.json +++ b/charts/artifact-hub/values.schema.json @@ -1224,7 +1224,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, radius", + "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, bootc", "type": "array", "items": { "type": "string" diff --git a/cmd/ah/lint.go b/cmd/ah/lint.go index 237ea366f2..fb537762bc 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, radius, tbaction, tekton-pipeline, tekton-stepaction, tekton-task") + lintCmd.Flags().StringVarP(&opts.kind, "kind", "k", "helm", "repository kind: argo-template, backstage, bootc, 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 @@ -119,6 +119,7 @@ func lint(opts *lintOptions, out *output) error { case hub.ArgoTemplate, hub.Backstage, + hub.Bootc, hub.CoreDNS, hub.Falco, hub.Gatekeeper, @@ -747,6 +748,7 @@ func (out *output) printPkgDetails(pkg *hub.Package) { case hub.ArgoTemplate, hub.Backstage, + hub.Bootc, hub.CoreDNS, hub.Falco, hub.Gatekeeper, diff --git a/database/migrations/schema/062_bootable_containers.sql b/database/migrations/schema/062_bootable_containers.sql new file mode 100644 index 0000000000..d85816c513 --- /dev/null +++ b/database/migrations/schema/062_bootable_containers.sql @@ -0,0 +1,5 @@ +insert into repository_kind values (27, 'Bootable containers'); + +---- create above / drop below ---- + +delete from repository_kind where repository_kind_id = 27; diff --git a/database/tests/schema/schema.sql b/database/tests/schema/schema.sql index a58b035c70..89b3a44f57 100644 --- a/database/tests/schema/schema.sql +++ b/database/tests/schema/schema.sql @@ -569,7 +569,8 @@ select results_eq( (23, 'Tekton stepactions'), (24, 'Meshery designs'), (25, 'OpenCost plugins'), - (26, 'Radius recipes') + (26, 'Radius recipes'), + (27, 'Bootable containers') $$, 'Repository kinds should exist' ); diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index c8671a4f5d..7e337ea4a3 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -1161,6 +1161,29 @@ paths: $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalServerError" + "/packages/bootc/{repoName}/{packageName}": + get: + tags: + - Packages + summary: Get package details + description: Get package details + operationId: getBootableContainersDetails + parameters: + - $ref: "#/components/parameters/RepoNameParam" + - $ref: "#/components/parameters/PackageNameParam" + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: "#/components/schemas/BootableContainer" + "404": + $ref: "#/components/responses/NotFoundResponse" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalServerError" "/packages/container/{repoName}/{packageName}": get: tags: @@ -1785,6 +1808,30 @@ paths: $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalServerError" + "/packages/bootc/{repoName}/{packageName}/{version}": + get: + tags: + - Packages + summary: Get package version details + description: Get package version details + operationId: getBootableContainersVersionDetails + parameters: + - $ref: "#/components/parameters/RepoNameParam" + - $ref: "#/components/parameters/PackageNameParam" + - $ref: "#/components/parameters/VersionParam" + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: "#/components/schemas/BootableContainer" + "404": + $ref: "#/components/responses/NotFoundResponse" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalServerError" "/packages/container/{repoName}/{packageName}/{version}": get: tags: @@ -4052,6 +4099,8 @@ components: example: "1.5.0" BackstagePlugin: $ref: "#/components/schemas/Package" + BootableContainer: + $ref: "#/components/schemas/Package" ContainerImage: allOf: - $ref: "#/components/schemas/Package" @@ -5049,6 +5098,12 @@ components: - 19 - 20 - 21 + - 22 + - 23 + - 24 + - 25 + - 26 + - 27 description: | Repository kind: * `0` - Helm charts @@ -5078,6 +5133,7 @@ components: * `24` - Meshery designs * `25` - Opencost plugins * `26` - Radius recipes + * `27` - Bootable containers RepositoryKindParam: type: string enum: @@ -5108,6 +5164,7 @@ components: - meshery - opencost - radius + - bootc description: | Repository kind name: * `helm` - Helm charts @@ -5137,6 +5194,7 @@ components: * `meshery` - Meshery designs * `opencost` - Opencost plugins * `radius` - Radius recipes + * `bootc` - Bootable containers RepositorySummary: type: object required: @@ -5683,6 +5741,7 @@ components: * `24` - Meshery designs * `25` - Opencost plugins * `26` - Radius recipes + * `27` - Bootable containers PackageNameParam: in: path name: packageName diff --git a/docs/bootable_containers_repositories.md b/docs/bootable_containers_repositories.md new file mode 100644 index 0000000000..68d30726ee --- /dev/null +++ b/docs/bootable_containers_repositories.md @@ -0,0 +1,51 @@ +## Bootable containers repositories + +Bootable containers 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 containers packages and versions could look something like this: + +```sh +$ tree path/to/packages +path/to/packages +├── artifacthub-repo.yml +├── container1 +│   ├── 1.0.0 +│   │   ├── README.md +│   │   └── artifacthub-pkg.yml +│   └── 2.0.0 +│      ├── README.md +│   └── artifacthub-pkg.yml +└── container2 + └── 1.0.0 +       ├── README.md + └── artifacthub-pkg.yml +``` + +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 + └── 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`. + +The [Artifact Hub metadata file](https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-pkg.yml) allows defining containers images. Bootable containers packages are **required** to provide a container image named `bootc`. Packages can optionally provide an alternative location for the bootable container image by defining a second image named `bootc-alternative-location`. Artifact Hub will check if the images provided have been signed with `cosign` and will consider the package to be *signed* when **all** images are signed. + +Once you have added your repository, you are all set up. As you add new versions of your containers 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 49234146ba..a92d8f8c14 100644 --- a/docs/repositories.md +++ b/docs/repositories.md @@ -6,6 +6,7 @@ The following repositories kinds are supported at the moment: - [Argo templates repositories](https://github.com/artifacthub/hub/blob/master/docs/argo_templates_repositories.md) - [Backstage plugins repositories](https://github.com/artifacthub/hub/blob/master/docs/backstage_plugins_repositories.md) +- [Bootable containers repositories](https://github.com/artifacthub/hub/blob/master/docs/bootable_containers_repositories.md) - [Containers images repositories](https://github.com/artifacthub/hub/blob/master/docs/container_images_repositories.md) - [CoreDNS plugins repositories](https://github.com/artifacthub/hub/blob/master/docs/coredns_plugins_repositories.md) - [Falco rules repositories](https://github.com/artifacthub/hub/blob/master/docs/falco_rules_repositories.md) diff --git a/docs/www/headers/bootable_containers_repositories b/docs/www/headers/bootable_containers_repositories new file mode 100644 index 0000000000..22ae3dcbb6 --- /dev/null +++ b/docs/www/headers/bootable_containers_repositories @@ -0,0 +1,6 @@ +--- +title: "Bootable containers" +aliases: [ + "/bootable_containers_repositories", +] +--- diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 44d02828a5..c554e9e8c1 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|^radius$}/{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|^bootc$}/{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|^radius$}/{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|^bootc$}/{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 7ee138d922..c7f240ca87 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/radius/repo1/pkg1/2.0.0", }, + { + &hub.Package{ + NormalizedName: "pkg1", + Repository: &hub.Repository{ + Kind: hub.Bootc, + Name: "repo1", + }, + }, + "2.0.0", + baseURL + "/packages/bootc/repo1/pkg1/2.0.0", + }, } for _, tc := range testCases { t.Run(tc.expectedPkgURL, func(t *testing.T) { diff --git a/internal/hub/repo.go b/internal/hub/repo.go index 16b650229a..fbaa8d822d 100644 --- a/internal/hub/repo.go +++ b/internal/hub/repo.go @@ -127,6 +127,9 @@ const ( // Radius represents a repository with Radius recipes. Radius RepositoryKind = 26 + + // Bootc represents a repository with Bootable containers. + Bootc RepositoryKind = 27 ) // GetKindName returns the name of the provided repository kind. @@ -136,6 +139,8 @@ func GetKindName(kind RepositoryKind) string { return "argo-template" case Backstage: return "backstage" + case Bootc: + return "bootc" case Container: return "container" case CoreDNS: @@ -199,6 +204,8 @@ func GetKindFromName(kind string) (RepositoryKind, error) { return ArgoTemplate, nil case "backstage": return Backstage, nil + case "bootc": + return Bootc, nil case "container": return Container, nil case "coredns": diff --git a/internal/pkg/metadata.go b/internal/pkg/metadata.go index d962ea5da9..52bbca865d 100644 --- a/internal/pkg/metadata.go +++ b/internal/pkg/metadata.go @@ -16,6 +16,14 @@ import ( ) const ( + // bootcImage represents the name that must be used for the container image + // in a Bootable container package. + bootcImage = "bootc" + + // bootcImageAlternativeLoc represents the name that must be used for the + // container image alternative location in a Bootable container package. + bootcImageAlternativeLoc = "bootc-alternative-location" + // gadgetImage represents the name that must be used for the gadget image // in an Inspektor gadget package. gadgetImage = "gadget" @@ -255,6 +263,33 @@ func ValidateContainersImages(kind hub.RepositoryKind, images []*hub.ContainerIm // Repository kind specific validation switch kind { + case hub.Bootc: + // Bootable container image is required + if len(images) == 0 || (len(images) == 1 && images[0].Name != bootcImage) { + errs = multierror.Append(errs, fmt.Errorf(`"%s" image not provided`, bootcImage)) + } + + // A second container image pointing to an alternative location may be provided + if len(images) == 2 { + imagesNames := []string{images[0].Name, images[1].Name} + sort.Strings(imagesNames) + if imagesNames[0] != bootcImage || imagesNames[1] != bootcImageAlternativeLoc { + errs = multierror.Append(errs, fmt.Errorf( + `only "%s" and "%s" images can be provided`, + bootcImage, + bootcImageAlternativeLoc, + )) + } + } + + // Providing more than two images is not allowed + if len(images) > 2 { + errs = multierror.Append(errs, fmt.Errorf( + `only "%s" and "%s" images can be provided`, + bootcImage, + bootcImageAlternativeLoc, + )) + } case hub.InspektorGadget: // Gadget image is required if len(images) == 0 || (len(images) == 1 && images[0].Name != gadgetImage) { diff --git a/internal/pkg/metadata_test.go b/internal/pkg/metadata_test.go index bfa7e9671e..ce9536754e 100644 --- a/internal/pkg/metadata_test.go +++ b/internal/pkg/metadata_test.go @@ -610,6 +610,88 @@ func TestValidatePackageMetadata(t *testing.T) { `only "gadget" and "gadget-alternative-location" images can be provided`, }, }, + { + hub.Bootc, + &hub.PackageMetadata{ + Version: "1.0.0", + Name: "pkg1", + DisplayName: "Package 1", + CreatedAt: "2006-01-02T15:04:05Z", + Description: "description", + }, + []string{ + `"bootc" image not provided`, + }, + }, + { + hub.Bootc, + &hub.PackageMetadata{ + Version: "1.0.0", + Name: "pkg1", + DisplayName: "Package 1", + CreatedAt: "2006-01-02T15:04:05Z", + Description: "description", + ContainersImages: []*hub.ContainerImage{ + { + Name: "something", + Image: "registry.io/namespace/something:tag", + }, + }, + }, + []string{ + `"bootc" image not provided`, + }, + }, + { + hub.Bootc, + &hub.PackageMetadata{ + Version: "1.0.0", + Name: "pkg1", + DisplayName: "Package 1", + CreatedAt: "2006-01-02T15:04:05Z", + Description: "description", + ContainersImages: []*hub.ContainerImage{ + { + Name: "bootc", + Image: "registry.io/namespace/bootc:tag", + }, + { + Name: "something", + Image: "registry.io/namespace/something:tag", + }, + }, + }, + []string{ + `only "bootc" and "bootc-alternative-location" images can be provided`, + }, + }, + { + hub.Bootc, + &hub.PackageMetadata{ + Version: "1.0.0", + Name: "pkg1", + DisplayName: "Package 1", + CreatedAt: "2006-01-02T15:04:05Z", + Description: "description", + ContainersImages: []*hub.ContainerImage{ + { + Name: "bootc", + Image: "registry.io/namespace/bootc:tag", + }, + { + Name: "bootc-alternative-location", + Image: "registry2.io/namespace/bootc:tag", + }, + { + Name: "something", + Image: "registry.io/namespace/something:tag", + }, + }, + }, + []string{ + `only "bootc" and "bootc-alternative-location" images can be provided`, + }, + }, } for i, tc := range testCases { t.Run(strconv.Itoa(i), func(t *testing.T) { @@ -769,6 +851,65 @@ func TestValidatePackageMetadata(t *testing.T) { }, }, }, + { + hub.Bootc, + &hub.PackageMetadata{ + Version: "1.0.0", + Name: "pkg1", + DisplayName: "Package 1", + CreatedAt: "2006-01-02T15:04:05Z", + Description: "description", + Category: "security", + ContainersImages: []*hub.ContainerImage{ + { + Name: "bootc", + Image: "registry.io/namespace/bootc:tag", + }, + }, + }, + }, + { + hub.Bootc, + &hub.PackageMetadata{ + Version: "1.0.0", + Name: "pkg1", + DisplayName: "Package 1", + CreatedAt: "2006-01-02T15:04:05Z", + Description: "description", + Category: "security", + ContainersImages: []*hub.ContainerImage{ + { + Name: "bootc", + Image: "registry.io/namespace/bootc:tag", + }, + { + Name: "bootc-alternative-location", + Image: "registry2.io/namespace/bootc:tag", + }, + }, + }, + }, + { + hub.Bootc, + &hub.PackageMetadata{ + Version: "1.0.0", + Name: "pkg1", + DisplayName: "Package 1", + CreatedAt: "2006-01-02T15:04:05Z", + Description: "description", + Category: "security", + ContainersImages: []*hub.ContainerImage{ + { + Name: "bootc-alternative-location", + Image: "registry2.io/namespace/bootc:tag", + }, + { + Name: "bootc", + Image: "registry.io/namespace/bootc:tag", + }, + }, + }, + }, } for i, tc := range testCases { t.Run(strconv.Itoa(i), func(t *testing.T) { diff --git a/internal/repo/manager.go b/internal/repo/manager.go index e966430ca1..ebf0995963 100644 --- a/internal/repo/manager.go +++ b/internal/repo/manager.go @@ -81,6 +81,7 @@ var ( validRepositoryKinds = []hub.RepositoryKind{ hub.ArgoTemplate, hub.Backstage, + hub.Bootc, hub.Container, hub.CoreDNS, hub.Falco, @@ -287,6 +288,7 @@ func (m *Manager) ClaimOwnership(ctx context.Context, repoName, orgName string) case hub.ArgoTemplate, hub.Backstage, + hub.Bootc, hub.CoreDNS, hub.Falco, hub.Gatekeeper, @@ -471,6 +473,7 @@ func (m *Manager) locateMetadataFile(r *hub.Repository, basePath string) string case hub.ArgoTemplate, hub.Backstage, + hub.Bootc, hub.CoreDNS, hub.Falco, hub.Gatekeeper, @@ -840,6 +843,7 @@ func (m *Manager) validateURL(r *hub.Repository) error { case hub.ArgoTemplate, hub.Backstage, + hub.Bootc, hub.CoreDNS, hub.Falco, hub.Gatekeeper, diff --git a/internal/tracker/helpers.go b/internal/tracker/helpers.go index bfd8b5865d..4926c7b4a8 100644 --- a/internal/tracker/helpers.go +++ b/internal/tracker/helpers.go @@ -115,6 +115,7 @@ func SetupSource(i *hub.TrackerSourceInput) hub.TrackerSource { case hub.ArgoTemplate, hub.Backstage, + hub.Bootc, hub.CoreDNS, hub.Gatekeeper, hub.Headlamp, diff --git a/internal/tracker/source/generic/generic.go b/internal/tracker/source/generic/generic.go index 1a8de2e691..3ce75cfec0 100644 --- a/internal/tracker/source/generic/generic.go +++ b/internal/tracker/source/generic/generic.go @@ -148,7 +148,7 @@ func (s *TrackerSource) GetPackagesAvailable() (map[string]*hub.Package, error) // Check if the package is signed (for applicable kinds) switch p.Repository.Kind { - case hub.InspektorGadget, hub.Kubewarden: + case hub.Bootc, hub.InspektorGadget, hub.Kubewarden: // We'll consider the package signed if all images are signed signedImages := 0 for _, entry := range p.ContainersImages { diff --git a/internal/tracker/tracker.go b/internal/tracker/tracker.go index 80f2be7cac..06c7d21622 100644 --- a/internal/tracker/tracker.go +++ b/internal/tracker/tracker.go @@ -186,6 +186,7 @@ func (t *Tracker) cloneRepository() (string, string, error) { case hub.ArgoTemplate, hub.Backstage, + hub.Bootc, hub.CoreDNS, hub.Falco, hub.Gatekeeper, diff --git a/scripts/prepare-docs.sh b/scripts/prepare-docs.sh index 039f7742bc..7dce31a754 100755 --- a/scripts/prepare-docs.sh +++ b/scripts/prepare-docs.sh @@ -8,6 +8,7 @@ mkdir -p docs/www/content/topics/repositories cat docs/www/headers/repositories docs/repositories.md > docs/www/content/topics/repositories/_index.md cat docs/www/headers/argo_templates_repositories docs/argo_templates_repositories.md > docs/www/content/topics/repositories/argo-templates.md cat docs/www/headers/backstage_plugins_repositories docs/backstage_plugins_repositories.md > docs/www/content/topics/repositories/backstage-plugins.md +cat docs/www/headers/bootable_containers_repositories docs/bootable_containers_repositories.md > docs/www/content/topics/repositories/bootable-containers.md cat docs/www/headers/container_images_repositories docs/container_images_repositories.md > docs/www/content/topics/repositories/container-images.md cat docs/www/headers/coredns_plugins_repositories docs/coredns_plugins_repositories.md > docs/www/content/topics/repositories/coredns-plugins.md cat docs/www/headers/falco_rules_repositories docs/falco_rules_repositories.md > docs/www/content/topics/repositories/falco-rules.md diff --git a/web/public/static/media/bootc-light.svg b/web/public/static/media/bootc-light.svg new file mode 100644 index 0000000000..0fb7245802 --- /dev/null +++ b/web/public/static/media/bootc-light.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/web/public/static/media/bootc.svg b/web/public/static/media/bootc.svg new file mode 100644 index 0000000000..8a9817fbbe --- /dev/null +++ b/web/public/static/media/bootc.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/web/public/static/media/bootc_icon.png b/web/public/static/media/bootc_icon.png new file mode 100644 index 0000000000..09ce8bbd20 Binary files /dev/null and b/web/public/static/media/bootc_icon.png differ diff --git a/web/public/static/media/placeholder_pkg_bootc.png b/web/public/static/media/placeholder_pkg_bootc.png new file mode 100644 index 0000000000..f23c20aa66 Binary files /dev/null and b/web/public/static/media/placeholder_pkg_bootc.png differ diff --git a/web/src/layout/common/Image.tsx b/web/src/layout/common/Image.tsx index 305282f1b0..d8830adaee 100644 --- a/web/src/layout/common/Image.tsx +++ b/web/src/layout/common/Image.tsx @@ -81,6 +81,8 @@ const Image = (props: Props) => { return '/static/media/placeholder_pkg_opencost.png'; case RepositoryKind.RadiusRecipe: return '/static/media/placeholder_pkg_radius.png'; + case RepositoryKind.Bootc: + return '/static/media/placeholder_pkg_bootc.png'; default: return PLACEHOLDER_SRC; } diff --git a/web/src/layout/common/RepositoryIcon.tsx b/web/src/layout/common/RepositoryIcon.tsx index e19cc2f0a2..398222db4e 100644 --- a/web/src/layout/common/RepositoryIcon.tsx +++ b/web/src/layout/common/RepositoryIcon.tsx @@ -117,6 +117,10 @@ const ICONS = { default: '/static/media/radius.svg', white: '/static/media/radius-light.svg', }, + [RepositoryKind.Bootc]: { + default: '/static/media/bootc.svg', + white: '/static/media/bootc-light.svg', + }, }; const RepositoryIcon = (props: Props) => { diff --git a/web/src/layout/common/badges/Signed.tsx b/web/src/layout/common/badges/Signed.tsx index 5ba8fb329f..26542e05b2 100644 --- a/web/src/layout/common/badges/Signed.tsx +++ b/web/src/layout/common/badges/Signed.tsx @@ -32,6 +32,7 @@ const Signed = (props: Props) => { RepositoryKind.TektonPipeline, RepositoryKind.TektonTask, RepositoryKind.TektonStepAction, + RepositoryKind.Bootc, ].includes(props.repoKind); return ( diff --git a/web/src/layout/controlPanel/repositories/Modal.tsx b/web/src/layout/controlPanel/repositories/Modal.tsx index 5039095033..5d608c75db 100644 --- a/web/src/layout/controlPanel/repositories/Modal.tsx +++ b/web/src/layout/controlPanel/repositories/Modal.tsx @@ -548,6 +548,17 @@ const RepositoryModal = (props: Props) => { ); break; + case RepositoryKind.Bootc: + link = ( + + Bootable containers + + ); + break; } if (isUndefined(link)) return; @@ -582,6 +593,7 @@ const RepositoryModal = (props: Props) => { case RepositoryKind.MesheryDesign: case RepositoryKind.OpenCost: case RepositoryKind.RadiusRecipe: + case RepositoryKind.Bootc: return ( <>

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

{ RepositoryKind.MesheryDesign, RepositoryKind.OpenCost, RepositoryKind.RadiusRecipe, + RepositoryKind.Bootc, ].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 a4a5dd2e79..d880665863 100644 --- a/web/src/layout/controlPanel/repositories/__snapshots__/Modal.test.tsx.snap +++ b/web/src/layout/controlPanel/repositories/__snapshots__/Modal.test.tsx.snap @@ -69,6 +69,11 @@ exports[`Repository Modal - repositories section creates snapshot 1`] = ` > Backstage plugins +
+
+ -
-
+ +
- -
+ +
- -
+ +
- -
{ await waitFor(() => expect(API.getStats).toHaveBeenCalledTimes(1)); const links = await screen.findAllByRole('button'); - expect(links).toHaveLength(30); + expect(links).toHaveLength(31); expect(links[2]).toHaveProperty('href', 'https://github.com/artifacthub/hub'); expect(links[3]).toHaveProperty('href', 'https://cloud-native.slack.com/channels/artifact-hub'); @@ -139,29 +139,30 @@ describe('Home index', () => { // Packages expect(links[6]).toHaveProperty('href', 'https://argoproj.github.io/argo-workflows/'); expect(links[7]).toHaveProperty('href', 'https://backstage.io/plugins'); - expect(links[8]).toHaveProperty('href', 'https://opencontainers.org/'); - expect(links[9]).toHaveProperty('href', 'https://coredns.io/'); - expect(links[10]).toHaveProperty('href', 'https://falco.org/'); - expect(links[11]).toHaveProperty('href', 'https://headlamp.dev/'); - expect(links[12]).toHaveProperty('href', 'https://helm.sh/'); - expect(links[13]).toHaveProperty('href', 'https://www.inspektor-gadget.io/'); - expect(links[14]).toHaveProperty('href', 'https://kcl-lang.io/'); - expect(links[15]).toHaveProperty('href', 'https://keda.sh/'); - expect(links[16]).toHaveProperty('href', 'https://keptn.sh/'); - expect(links[17]).toHaveProperty('href', 'https://github.com/knative/client'); - expect(links[18]).toHaveProperty('href', 'https://krew.sigs.k8s.io/'); - expect(links[19]).toHaveProperty('href', 'https://kubearmor.io/'); - expect(links[20]).toHaveProperty('href', 'https://www.kubewarden.io/'); - expect(links[21]).toHaveProperty('href', 'https://www.kyverno.io/'); - expect(links[22]).toHaveProperty('href', 'https://meshery.io/'); - 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://radapp.io/'); - expect(links[27]).toHaveProperty('href', 'https://tekton.dev/'); - expect(links[28]).toHaveProperty('href', 'https://tinkerbell.org/'); - - expect(links[29]).toHaveProperty('href', 'https://www.cncf.io/projects/'); + expect(links[8]).toHaveProperty('href', 'https://containers.github.io/bootable/'); + expect(links[9]).toHaveProperty('href', 'https://opencontainers.org/'); + expect(links[10]).toHaveProperty('href', 'https://coredns.io/'); + expect(links[11]).toHaveProperty('href', 'https://falco.org/'); + expect(links[12]).toHaveProperty('href', 'https://headlamp.dev/'); + expect(links[13]).toHaveProperty('href', 'https://helm.sh/'); + expect(links[14]).toHaveProperty('href', 'https://www.inspektor-gadget.io/'); + expect(links[15]).toHaveProperty('href', 'https://kcl-lang.io/'); + expect(links[16]).toHaveProperty('href', 'https://keda.sh/'); + expect(links[17]).toHaveProperty('href', 'https://keptn.sh/'); + expect(links[18]).toHaveProperty('href', 'https://github.com/knative/client'); + expect(links[19]).toHaveProperty('href', 'https://krew.sigs.k8s.io/'); + expect(links[20]).toHaveProperty('href', 'https://kubearmor.io/'); + expect(links[21]).toHaveProperty('href', 'https://www.kubewarden.io/'); + expect(links[22]).toHaveProperty('href', 'https://www.kyverno.io/'); + expect(links[23]).toHaveProperty('href', 'https://meshery.io/'); + expect(links[24]).toHaveProperty('href', 'https://github.com/operator-framework'); + expect(links[25]).toHaveProperty('href', 'https://www.openpolicyagent.org/'); + expect(links[26]).toHaveProperty('href', 'https://www.opencost.io/'); + expect(links[27]).toHaveProperty('href', 'https://radapp.io/'); + expect(links[28]).toHaveProperty('href', 'https://tekton.dev/'); + expect(links[29]).toHaveProperty('href', 'https://tinkerbell.org/'); + + expect(links[30]).toHaveProperty('href', 'https://www.cncf.io/projects/'); }); }); }); diff --git a/web/src/layout/home/index.tsx b/web/src/layout/home/index.tsx index 0a51ff8012..f6ab7fea06 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, OpenCost plugins and Radius recipes. + designs, OpenCost plugins, Radius recipes and Bootable Containers.
{
+ +
+ +
+ Bootable containers +
+
+
{
+ +
@@ -292,8 +306,6 @@ const HomeView = () => {
- -
{
+ +
@@ -346,8 +360,6 @@ const HomeView = () => {
- -
@@ -396,6 +408,8 @@ const HomeView = () => {
+ +
{
- -
{
+ +
{
- -
diff --git a/web/src/layout/package/Details.tsx b/web/src/layout/package/Details.tsx index 03de8c8477..3900e88e88 100644 --- a/web/src/layout/package/Details.tsx +++ b/web/src/layout/package/Details.tsx @@ -176,6 +176,7 @@ const Details = (props: Props) => { case RepositoryKind.MesheryDesign: case RepositoryKind.OpenCost: case RepositoryKind.RadiusRecipe: + case RepositoryKind.Bootc: return ( <> {props.package.appVersion && ( diff --git a/web/src/layout/package/RecommendedPackages/index.tsx b/web/src/layout/package/RecommendedPackages/index.tsx index 0eac232114..922086dece 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|radius|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|bootc|container)\/([a-z0-9-]+)\/([a-z0-9-]+)$/; // eslint-disable-line const prepareRecommendations = (recommendations?: Recommendation[]): RecommendedPackage[] => { const list: RecommendedPackage[] = []; diff --git a/web/src/types.ts b/web/src/types.ts index 9f6bbf516e..9b3cb7c4bb 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -28,6 +28,7 @@ export enum RepositoryKind { MesheryDesign, OpenCost, RadiusRecipe, + Bootc, } export enum PackageCategory { diff --git a/web/src/utils/data.tsx b/web/src/utils/data.tsx index 69154279a8..b96b5c2477 100644 --- a/web/src/utils/data.tsx +++ b/web/src/utils/data.tsx @@ -105,6 +105,15 @@ export const REPOSITORY_KINDS: RepoKindDef[] = [ icon: , active: true, }, + { + kind: RepositoryKind.Bootc, + label: 'bootc', + name: 'Bootable containers', + singular: 'Bootable container', + plural: 'Bootable containers', + icon: , + active: true, + }, { kind: RepositoryKind.Container, label: 'container', @@ -831,7 +840,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|radius|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|bootc|container)\//; export const HOME_ROUTES = [ '/verify-email', diff --git a/web/src/utils/getInstallMethods.ts b/web/src/utils/getInstallMethods.ts index 81043e5f64..72617f187f 100644 --- a/web/src/utils/getInstallMethods.ts +++ b/web/src/utils/getInstallMethods.ts @@ -117,6 +117,7 @@ const getInstallMethods = (props: PackageInfo): InstallMethodOutput => { case RepositoryKind.MesheryDesign: case RepositoryKind.OpenCost: case RepositoryKind.RadiusRecipe: + case RepositoryKind.Bootc: if (isUndefined(pkg.install)) { output.errorMessage = 'This package does not include installation instructions yet.'; hasError = true; diff --git a/web/src/utils/repoKind.ts b/web/src/utils/repoKind.ts index 170a20c437..7a8103ce86 100644 --- a/web/src/utils/repoKind.ts +++ b/web/src/utils/repoKind.ts @@ -56,6 +56,8 @@ const getRepoKind = (repoName: string): RepositoryKind | null => { return RepositoryKind.OpenCost; case 'radius': return RepositoryKind.RadiusRecipe; + case 'bootc': + return RepositoryKind.Bootc; default: return null; } @@ -117,6 +119,8 @@ const getRepoKindName = (repoKind: RepositoryKind): string | null => { return 'opencost'; case RepositoryKind.RadiusRecipe: return 'radius'; + case RepositoryKind.Bootc: + return 'bootc'; default: return null; } diff --git a/widget/src/layout/Widget.tsx b/widget/src/layout/Widget.tsx index 5f3f09d203..3a2b55bf10 100644 --- a/widget/src/layout/Widget.tsx +++ b/widget/src/layout/Widget.tsx @@ -103,6 +103,8 @@ const getRepoKindName = (repoKind: RepositoryKind): string | null => { return 'opencost'; case RepositoryKind.RadiusRecipe: return 'radius'; + case RepositoryKind.Bootc: + return 'bootc'; default: return null; } diff --git a/widget/src/layout/common/Image.tsx b/widget/src/layout/common/Image.tsx index 3761dfadc8..4b29fe6eb8 100644 --- a/widget/src/layout/common/Image.tsx +++ b/widget/src/layout/common/Image.tsx @@ -82,6 +82,8 @@ const Image = (props: Props) => { return '/static/media/placeholder_pkg_opencost.png'; case RepositoryKind.RadiusRecipe: return '/static/media/placeholder_pkg_radius.png'; + case RepositoryKind.Bootc: + return '/static/media/placeholder_pkg_bootc.png'; default: return PLACEHOLDER_SRC; } diff --git a/widget/src/layout/common/RepositoryIcon.tsx b/widget/src/layout/common/RepositoryIcon.tsx index 1e89fc0dbf..164c367f4d 100644 --- a/widget/src/layout/common/RepositoryIcon.tsx +++ b/widget/src/layout/common/RepositoryIcon.tsx @@ -39,6 +39,7 @@ const ICONS: IconsList = { [RepositoryKind.MesheryDesign]: , [RepositoryKind.OpenCost]: , [RepositoryKind.RadiusRecipe]: , + [RepositoryKind.Bootc]: , }; const RepositoryIcon = (props: Props) => ( diff --git a/widget/src/layout/common/RepositoryIconLabel.tsx b/widget/src/layout/common/RepositoryIconLabel.tsx index ee52a1e81e..aedc3177d3 100644 --- a/widget/src/layout/common/RepositoryIconLabel.tsx +++ b/widget/src/layout/common/RepositoryIconLabel.tsx @@ -124,6 +124,10 @@ const REPOSITORY_KINDS: RepoKindDef[] = [ kind: RepositoryKind.RadiusRecipe, name: 'Radius recipe', }, + { + kind: RepositoryKind.Bootc, + name: 'Bootable container', + }, ]; const Wrapper = styled('span')` diff --git a/widget/src/layout/common/SVGIcons.tsx b/widget/src/layout/common/SVGIcons.tsx index afb1bba66d..a8f7829851 100644 --- a/widget/src/layout/common/SVGIcons.tsx +++ b/widget/src/layout/common/SVGIcons.tsx @@ -1289,6 +1289,30 @@ const SVGIcons = (props: Props) => ( ); + case 'bootc': + return ( + + {props.name} + + + + + + + + + + + + + ); default: return null; } diff --git a/widget/src/types.ts b/widget/src/types.ts index 91493548dc..757a7eb790 100644 --- a/widget/src/types.ts +++ b/widget/src/types.ts @@ -55,6 +55,7 @@ export enum RepositoryKind { MesheryDesign, OpenCost, RadiusRecipe, + Bootc, } export interface SearchResults {