Skip to content

Commit

Permalink
Add support for bootable container images (#4262)
Browse files Browse the repository at this point in the history
Signed-off-by: Sergio Castaño Arteaga <[email protected]>
Signed-off-by: Cintia Sánchez García <[email protected]>
Co-authored-by: Sergio Castaño Arteaga <[email protected]>
Co-authored-by: Cintia Sanchez Garcia <[email protected]>
  • Loading branch information
tegioz and cynthia-sg authored Jan 27, 2025
1 parent 5a5a556 commit ebfcc1c
Show file tree
Hide file tree
Showing 44 changed files with 530 additions and 59 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)
Expand Down
3 changes: 2 additions & 1 deletion charts/artifact-hub/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -33,6 +33,7 @@ keywords:
- meshery
- opencost
- radius
- bootable containers
maintainers:
- name: Sergio
email: [email protected]
Expand Down
2 changes: 1 addition & 1 deletion charts/artifact-hub/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion cmd/ah/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -119,6 +119,7 @@ func lint(opts *lintOptions, out *output) error {
case
hub.ArgoTemplate,
hub.Backstage,
hub.Bootc,
hub.CoreDNS,
hub.Falco,
hub.Gatekeeper,
Expand Down Expand Up @@ -747,6 +748,7 @@ func (out *output) printPkgDetails(pkg *hub.Package) {
case
hub.ArgoTemplate,
hub.Backstage,
hub.Bootc,
hub.CoreDNS,
hub.Falco,
hub.Gatekeeper,
Expand Down
5 changes: 5 additions & 0 deletions database/migrations/schema/062_bootable_containers.sql
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 2 additions & 1 deletion database/tests/schema/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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'
);
Expand Down
59 changes: 59 additions & 0 deletions docs/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -5049,6 +5098,12 @@ components:
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
description: |
Repository kind:
* `0` - Helm charts
Expand Down Expand Up @@ -5078,6 +5133,7 @@ components:
* `24` - Meshery designs
* `25` - Opencost plugins
* `26` - Radius recipes
* `27` - Bootable containers
RepositoryKindParam:
type: string
enum:
Expand Down Expand Up @@ -5108,6 +5164,7 @@ components:
- meshery
- opencost
- radius
- bootc
description: |
Repository kind name:
* `helm` - Helm charts
Expand Down Expand Up @@ -5137,6 +5194,7 @@ components:
* `meshery` - Meshery designs
* `opencost` - Opencost plugins
* `radius` - Radius recipes
* `bootc` - Bootable containers
RepositorySummary:
type: object
required:
Expand Down Expand Up @@ -5683,6 +5741,7 @@ components:
* `24` - Meshery designs
* `25` - Opencost plugins
* `26` - Radius recipes
* `27` - Bootable containers
PackageNameParam:
in: path
name: packageName
Expand Down
51 changes: 51 additions & 0 deletions docs/bootable_containers_repositories.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions docs/repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions docs/www/headers/bootable_containers_repositories
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: "Bootable containers"
aliases: [
"/bootable_containers_repositories",
]
---
4 changes: 2 additions & 2 deletions internal/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
})
Expand Down
11 changes: 11 additions & 0 deletions internal/handlers/pkg/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 7 additions & 0 deletions internal/hub/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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":
Expand Down
35 changes: 35 additions & 0 deletions internal/pkg/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit ebfcc1c

Please sign in to comment.