diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8b7da7a3a..b675146b5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -59,7 +59,7 @@ updates: interval: "weekly" ignore: - dependency-name: "node" - versions: ["19-alpine3.19", "20-alpine3.19"] + versions: ["19-alpine3.19", "20-alpine3.19", "21-alpine3.19"] - package-ecosystem: "docker" directory: "/cmd/scanner" diff --git a/README.md b/README.md index 3912b68ad..5e6b0ad08 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ At the moment, the following artifacts kinds are supported *(with plans to suppo - [CoreDNS plugins](https://coredns.io/) - [Falco configurations](https://falco.org/) - [Gatekeeper policies](https://open-policy-agent.github.io/gatekeeper/website/docs/) +- [Headlamp plugins](https://headlamp.dev) - [Helm charts](https://helm.sh/) - [Helm plugins](https://helm.sh/docs/topics/plugins/) - [KCL modules](https://kcl-lang.io) diff --git a/charts/artifact-hub/Chart.yaml b/charts/artifact-hub/Chart.yaml index c6f74c0d3..1a09e3286 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 Kubernetes packages. type: application -version: 1.16.1-4 +version: 1.16.1-5 appVersion: 1.16.0 kubeVersion: ">= 1.19.0-0" home: https://artifacthub.io @@ -28,6 +28,7 @@ keywords: - argo - kubearmor - kcl + - headlamp maintainers: - name: Sergio email: tegioz@icloud.com diff --git a/charts/artifact-hub/values.schema.json b/charts/artifact-hub/values.schema.json index 61e677575..2a61db4e5 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", + "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", "type": "array", "items": { "type": "string" diff --git a/cmd/ah/lint.go b/cmd/ah/lint.go index 06c80df56..c1a8fdaef 100644 --- a/cmd/ah/lint.go +++ b/cmd/ah/lint.go @@ -90,7 +90,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, helm, helm-plugin, kcl, keda-scaler, keptn, knative-client-plugin, krew, kubearmor, kubewarden, kyverno, olm, opa, tbaction, tekton-task, tekton-pipeline") + lintCmd.Flags().StringVarP(&opts.kind, "kind", "k", "helm", "repository kind: argo-template, backstage, coredns, falco, gatekeeper, headlamp, helm, helm-plugin, kcl, keda-scaler, keptn, knative-client-plugin, krew, kubearmor, kubewarden, kyverno, olm, opa, tbaction, tekton-task, tekton-pipeline") lintCmd.Flags().StringVarP(&opts.path, "path", "p", ".", "repository's packages path") return lintCmd } @@ -114,6 +114,7 @@ func lint(opts *lintOptions, out *output) error { hub.CoreDNS, hub.Falco, hub.Gatekeeper, + hub.Headlamp, hub.KCL, hub.KedaScaler, hub.Keptn, @@ -617,6 +618,7 @@ func (out *output) printPkgDetails(pkg *hub.Package) { hub.CoreDNS, hub.Falco, hub.Gatekeeper, + hub.Headlamp, hub.KCL, hub.KedaScaler, hub.Keptn, diff --git a/database/migrations/schema/053_headlamp_plugins.sql b/database/migrations/schema/053_headlamp_plugins.sql new file mode 100644 index 000000000..40153cc4a --- /dev/null +++ b/database/migrations/schema/053_headlamp_plugins.sql @@ -0,0 +1,5 @@ +insert into repository_kind values (21, 'Headlamp plugins'); + +---- create above / drop below ---- + +delete from repository_kind where repository_kind_id = 21; diff --git a/database/tests/schema/schema.sql b/database/tests/schema/schema.sql index 97b1bbf99..c59fa09a8 100644 --- a/database/tests/schema/schema.sql +++ b/database/tests/schema/schema.sql @@ -560,7 +560,8 @@ select results_eq( (17, 'Backstage plugins'), (18, 'Argo templates'), (19, 'KubeArmor policies'), - (20, 'KCL modules') + (20, 'KCL modules'), + (21, 'Headlamp plugins') $$, 'Repository kinds should exist' ); diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index dd4579496..a2082dba5 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -1299,6 +1299,30 @@ paths: $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalServerError" + "/packages/headlamp/{repoName}/{packageName}": + get: + tags: + - Packages + summary: Get package details + description: Get package details + operationId: getHeadlampPluginDetails + parameters: + - $ref: "#/components/parameters/RepoNameParam" + - $ref: "#/components/parameters/PackageNameParam" + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: "#/components/schemas/HeadlampPluginPackage" + "404": + $ref: "#/components/responses/NotFoundResponse" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalServerError" + "/packages/kcl/{repoName}/{packageName}": get: tags: @@ -1790,7 +1814,31 @@ paths: $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalServerError" - "/packages/KCL/{repoName}/{packageName}/{version}": + "/packages/headlamp/{repoName}/{packageName}/{version}": + get: + tags: + - Packages + summary: Get package version details + description: Get package version details + operationId: getHeadlampVersionDetails + parameters: + - $ref: "#/components/parameters/RepoNameParam" + - $ref: "#/components/parameters/PackageNameParam" + - $ref: "#/components/parameters/VersionParam" + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: "#/components/schemas/HeadlampPluginPackage" + "404": + $ref: "#/components/responses/NotFoundResponse" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalServerError" + "/packages/kcl/{repoName}/{packageName}/{version}": get: tags: - Packages @@ -3967,6 +4015,27 @@ components: example: "http://repo.url" HelmPluginPackage: $ref: "#/components/schemas/Package" + HeadlampPluginPackage: + allOf: + - $ref: "#/components/schemas/Package" + - type: object + properties: + data: + type: object + nullable: false + properties: + headlamp/plugin/archive-url: + type: string + example: "https://headlamp.dev/sample-plugin-url" + headlamp/plugin/distro-compat: + type: string + example: "in-cluster,web,docker-desktop" + headlamp/plugin/version-compat: + type: string + example: ">=1.2.3" + headlamp/plugin/archive-checksum: + type: string + example: "sha256:123456..." KCLPackage: $ref: "#/components/schemas/Package" KedaScalerPackage: @@ -4689,6 +4758,7 @@ components: - 18 - 19 - 20 + - 21 description: | Repository kind: * `0` - Helm charts @@ -4712,6 +4782,7 @@ components: * `18` - Argo templates * `19` - KubeArmor templates * `20` - KCL packages + * `21` - Headlamp plugins RepositoryKindParam: type: string enum: @@ -4736,6 +4807,7 @@ components: - argo-template - kubearmor - kcl + - headlamp description: | Repository kind name: * `helm` - Helm charts @@ -4759,6 +4831,7 @@ components: * `argo-template` - Argo templates * `kubearmor` - KubeArmor policies * `kcl` - KCL packages + * `headlamp` - Headlamp plugins RepositorySummary: type: object required: @@ -5299,6 +5372,7 @@ components: * `18` - Argo templates * `19` - KubeArmor templates * `20` - KCL packages + * `21` - Headlamp plugins PackageNameParam: in: path name: packageName diff --git a/docs/headlamp_annotations.md b/docs/headlamp_annotations.md new file mode 100644 index 000000000..5056940e1 --- /dev/null +++ b/docs/headlamp_annotations.md @@ -0,0 +1,32 @@ +# Headlamp annotations + +You can provide some extra information about your Headlamp plugins by using the `annotations` field in the [Artifact Hub package metadata file](https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-pkg.yml). + +## Supported annotations + +- **headlamp/plugin/archive-url** *(string, required)* + +Plugin archive tarball URL (e.g. "https://.../my-archive.tar.gz"). + +- **headlamp/plugin/archive-checksum** *(string, required)* + +Plugin archive tarball checksum (e.g. "sha256:MY_CHECKSUM"). + +- **headlamp/plugin/version-compat** *(string, optional)* + +Headlamp versions this plugin is compatible with (e.g. ">=1.2.3"). + +- **headlamp/plugin/distro-compat** *(string, optional)* + +Headlamp flavor this plugin is compatible with (e.g. one or more of app, in-cluster, web, docker-desktop, linux, windows, mac). + +## Example + +```yaml +... +annotations: + headlamp/plugin/archive-url: "https://.../my-archive.tar.gz" + headlamp/plugin/archive-checksum: "sha256:MY_CHECKSUM" + headlamp/plugin/version-compat: ">=1.2.3" + headlamp/plugin/distro-compat: "in-cluster,web,docker-desktop" +``` diff --git a/docs/headlamp_plugins_repositories.md b/docs/headlamp_plugins_repositories.md new file mode 100644 index 000000000..cd912ea24 --- /dev/null +++ b/docs/headlamp_plugins_repositories.md @@ -0,0 +1,49 @@ +## Headlamp plugins repositories + +Headlamp plugins 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 plugins packages and versions could look something like this: + +```sh +$ tree path/to/packages +path/to/packages +├── artifacthub-repo.yml +├── package1 +│ ├── 1.0.0 +│ │ ├── README.md +│ │ └── artifacthub-pkg.yml +│ └── 2.0.0 +│ ├── README.md +│ └── artifacthub-pkg.yml +└── package2 + └── 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 +└── package1 + ├── 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) and the [Headlamp annotations documentation](https://github.com/artifacthub/hub/blob/master/docs/headlamp_annotations.md) 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 plugins 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 0945e7285..92b31a9db 100644 --- a/docs/repositories.md +++ b/docs/repositories.md @@ -9,6 +9,7 @@ The following repositories kinds are supported at the moment: - [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) +- [Headlamp plugins repositories](https://github.com/artifacthub/hub/blob/master/docs/headlamp_plugins_repositories.md) - [Helm charts repositories](https://github.com/artifacthub/hub/blob/master/docs/helm_charts_repositories.md) - [Helm plugins repositories](https://github.com/artifacthub/hub/blob/master/docs/helm_plugins_repositories.md) - [KCL modules repositories](https://github.com/artifacthub/hub/blob/master/docs/kcl_modules_repositories.md) diff --git a/docs/www/content/_index.md b/docs/www/content/_index.md index a74eaf8b6..09fa1657a 100644 --- a/docs/www/content/_index.md +++ b/docs/www/content/_index.md @@ -13,14 +13,15 @@ The documentation is organized in the following topics: | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Repositories guide](/docs/topics/repositories) | The repositories guide explains how to add repositories to Artifact Hub, as well as other related concepts like Verified publisher or Ownership Claim. | | [Argo annotations](/docs/topics/annotations/argo) | Describes some custom annotations that allow enriching the existing metadata in Argo templates to improve users' experience in Artifact Hub. | +| [Headlamp annotations](/docs/topics/annotations/headlamp) | Describes some custom annotations that allow enriching the existing metadata in Headlamp plugins to improve users' experience in Artifact Hub. | | [Helm annotations](/docs/topics/annotations/helm) | Describes some custom annotations that allow enriching the existing metadata in Helm Charts to improve users' experience in Artifact Hub. | | [Keptn annotations](/docs/topics/annotations/keptn) | Describes some custom annotations that allow enriching the existing metadata in Keptn integrations to improve users' experience in Artifact Hub. | | [Krew annotations](/docs/topics/annotations/krew) | Describes some custom annotations that allow enriching the existing metadata in Krew kubectl plugins to improve users' experience in Artifact Hub. | | [Kubewarden annotations](/docs/topics/annotations/kubewarden) | Describes some custom annotations that allow enriching the existing metadata in Kubewarden policies to improve users' experience in Artifact Hub. | | [Kyverno annotations](/docs/topics/annotations/kyverno) | Describes some custom annotations that allow enriching the existing metadata in Kyverno policies to improve users' experience in Artifact Hub. | | [OLM annotations](/docs/topics/annotations/olm) | Describes some custom annotations that allow enriching the existing metadata in OLM operators to improve users' experience in Artifact Hub. | -| [Tekton annotations](/docs/topics/annotations/tekton) | Describes some custom annotations that allow enriching the existing metadata in Tekton tasks to improve users' experience in Artifact Hub. -| [Embedding artifacts](/docs/topics/embedding_artifacts) | Explains how to embed a single artifact or a group of them in other websites. | +| [Tekton annotations](/docs/topics/annotations/tekton) | Describes some custom annotations that allow enriching the existing metadata in Tekton tasks to improve users' experience in Artifact Hub. | +| [Embedding artifacts](/docs/topics/embedding_artifacts) | Explains how to embed a single artifact or a group of them in other websites. | | [Packages security report](/docs/topics/security_report) | Explains how packages are scanned for security vulnerabilities and the structure of the security report. | | [Authorization](/docs/topics/authorization) | Explains how the authorization mechanism that allows organizations to define what actions can be performed by their members works and how to set it up. | | [Architecture](/docs/topics/architecture) | Describes the components that form Artifact Hub, what each of them do and the layout of the source repository. | diff --git a/docs/www/headers/headlamp_annotations b/docs/www/headers/headlamp_annotations new file mode 100644 index 000000000..d12b9186c --- /dev/null +++ b/docs/www/headers/headlamp_annotations @@ -0,0 +1,6 @@ +--- +title: "Headlamp" +aliases: [ + "/headlamp_annotations", +] +--- diff --git a/docs/www/headers/headlamp_plugins_repositories b/docs/www/headers/headlamp_plugins_repositories new file mode 100644 index 000000000..9d95d4dee --- /dev/null +++ b/docs/www/headers/headlamp_plugins_repositories @@ -0,0 +1,6 @@ +--- +title: "Headlamp plugins" +aliases: [ + "/headlamp_plugins_repositories", +] +--- diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index caac8aedc..27ae13a76 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$}/{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$}/{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$}/{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$}/{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 26d19b696..805852056 100644 --- a/internal/handlers/pkg/handlers_test.go +++ b/internal/handlers/pkg/handlers_test.go @@ -2155,6 +2155,17 @@ func TestBuildURL(t *testing.T) { "2.0.0", baseURL + "/packages/kcl/repo1/pkg1/2.0.0", }, + { + &hub.Package{ + NormalizedName: "pkg1", + Repository: &hub.Repository{ + Kind: hub.Headlamp, + Name: "repo1", + }, + }, + "2.0.0", + baseURL + "/packages/headlamp/repo1/pkg1/2.0.0", + }, } for _, tc := range testCases { tc := tc diff --git a/internal/hub/repo.go b/internal/hub/repo.go index 5edc7967c..44f8566cf 100644 --- a/internal/hub/repo.go +++ b/internal/hub/repo.go @@ -109,6 +109,9 @@ const ( // KCL represents a repository with KCL modules. KCL RepositoryKind = 20 + + // Headlamp represents a repository with Headlamp plugins. + Headlamp RepositoryKind = 21 ) // GetKindName returns the name of the provided repository kind. @@ -126,6 +129,8 @@ func GetKindName(kind RepositoryKind) string { return "falco" case Gatekeeper: return "gatekeeper" + case Headlamp: + return "headlamp" case Helm: return "helm" case HelmPlugin: @@ -177,6 +182,8 @@ func GetKindFromName(kind string) (RepositoryKind, error) { return Falco, nil case "gatekeeper": return Gatekeeper, nil + case "headlamp": + return Headlamp, nil case "helm": return Helm, nil case "helm-plugin": diff --git a/internal/pkg/metadata.go b/internal/pkg/metadata.go index f510c2a82..049abc933 100644 --- a/internal/pkg/metadata.go +++ b/internal/pkg/metadata.go @@ -29,6 +29,13 @@ var ( // ErrInvalidMetadata indicates that the metadata provided is not valid. ErrInvalidMetadata = errors.New("invalid metadata") + // headlampRequiredAnnotations represents a list of annotations that must + // be present in Headlamp plugins packages. + headlampRequiredAnnotations = []string{ + "headlamp/plugin/archive-url", + "headlamp/plugin/archive-checksum", + } + // validChangeKinds is the list of valid kinds that a pkg change can use. validChangeKinds = []string{ "added", @@ -119,49 +126,70 @@ func PreparePackageFromMetadata(md *hub.PackageMetadata) (*hub.Package, error) { func ValidatePackageMetadata(kind hub.RepositoryKind, md *hub.PackageMetadata) error { var errs *multierror.Error + // Version if md.Version == "" { errs = multierror.Append(errs, fmt.Errorf("%w: %s", ErrInvalidMetadata, "version not provided")) } else if _, err := semver.NewVersion(md.Version); err != nil { errs = multierror.Append(errs, fmt.Errorf("%w: %s: %w", ErrInvalidMetadata, "invalid version (semver expected)", err)) } + + // Name, display name and alternative name if md.Name == "" { errs = multierror.Append(errs, fmt.Errorf("%w: %s", ErrInvalidMetadata, "name not provided")) } + if md.DisplayName == "" { + errs = multierror.Append(errs, fmt.Errorf("%w: %s", ErrInvalidMetadata, "display name not provided")) + } if md.AlternativeName != "" && !strings.Contains(md.Name, md.AlternativeName) && !strings.Contains(md.AlternativeName, md.Name) { errs = multierror.Append(errs, fmt.Errorf("%w: %s", ErrInvalidMetadata, "invalid alternative name (must be a subset or superset of the name)")) } + + // Category if md.Category != "" { if _, err := hub.PackageCategoryFromName(md.Category); err != nil { errs = multierror.Append(errs, fmt.Errorf("%w: %w", ErrInvalidMetadata, err)) } } - if md.DisplayName == "" { - errs = multierror.Append(errs, fmt.Errorf("%w: %s", ErrInvalidMetadata, "display name not provided")) - } + + // Created at if md.CreatedAt == "" { errs = multierror.Append(errs, fmt.Errorf("%w: %s", ErrInvalidMetadata, "createdAt not provided")) } else if _, err := time.Parse(time.RFC3339, md.CreatedAt); err != nil { errs = multierror.Append(errs, fmt.Errorf("%w: %s: %w", ErrInvalidMetadata, "invalid createdAt (RFC3339 expected)", err)) } + + // Description if md.Description == "" { errs = multierror.Append(errs, fmt.Errorf("%w: %s", ErrInvalidMetadata, "description not provided")) + } + + // Maintainers for _, maintainer := range md.Maintainers { if maintainer.Email == "" { errs = multierror.Append(errs, fmt.Errorf("%w: %s", ErrInvalidMetadata, "maintainer email not provided")) } } + + // Changes for _, change := range md.Changes { if err := ValidateChange(change); err != nil { errs = multierror.Append(errs, fmt.Errorf("%w: %w", ErrInvalidMetadata, err)) } } + + // Containers images if err := ValidateContainersImages(kind, md.ContainersImages); err != nil { errs = multierror.Append(errs, fmt.Errorf("%w: %w", ErrInvalidMetadata, err)) } + // Annotations + if err := ValidateAnnotations(kind, md.Annotations); err != nil { + errs = multierror.Append(errs, fmt.Errorf("%w: %w", ErrInvalidMetadata, err)) + } + return errs.ErrorOrNil() } @@ -250,3 +278,21 @@ func ValidateContainersImages(kind hub.RepositoryKind, images []*hub.ContainerIm return errs.ErrorOrNil() } + +// ValidateAnnotations checks if the provided annotations are valid. +func ValidateAnnotations(kind hub.RepositoryKind, annotations map[string]string) error { + var errs *multierror.Error + + // Repository kind specific validation + switch kind { + case hub.Headlamp: + // Required annotations + for _, requiredAnnotation := range headlampRequiredAnnotations { + if _, ok := annotations[requiredAnnotation]; !ok { + errs = multierror.Append(errs, fmt.Errorf(`required annotation "%s" not provided`, requiredAnnotation)) + } + } + } + + return errs.ErrorOrNil() +} diff --git a/internal/repo/manager.go b/internal/repo/manager.go index 2c0f78534..049f9b5b5 100644 --- a/internal/repo/manager.go +++ b/internal/repo/manager.go @@ -84,6 +84,7 @@ var ( hub.CoreDNS, hub.Falco, hub.Gatekeeper, + hub.Headlamp, hub.Helm, hub.HelmPlugin, hub.KCL, @@ -283,6 +284,7 @@ func (m *Manager) ClaimOwnership(ctx context.Context, repoName, orgName string) hub.CoreDNS, hub.Falco, hub.Gatekeeper, + hub.Headlamp, hub.HelmPlugin, hub.KCL, hub.KedaScaler, @@ -461,6 +463,7 @@ func (m *Manager) locateMetadataFile(r *hub.Repository, basePath string) string hub.CoreDNS, hub.Falco, hub.Gatekeeper, + hub.Headlamp, hub.HelmPlugin, hub.KCL, hub.KedaScaler, @@ -823,6 +826,7 @@ func (m *Manager) validateURL(r *hub.Repository) error { hub.CoreDNS, hub.Falco, hub.Gatekeeper, + hub.Headlamp, hub.HelmPlugin, hub.KCL, hub.KedaScaler, diff --git a/internal/tracker/helpers.go b/internal/tracker/helpers.go index 738e59227..b84b48ab9 100644 --- a/internal/tracker/helpers.go +++ b/internal/tracker/helpers.go @@ -117,6 +117,7 @@ func SetupSource(i *hub.TrackerSourceInput) hub.TrackerSource { hub.Backstage, hub.CoreDNS, hub.Gatekeeper, + hub.Headlamp, hub.KCL, hub.KedaScaler, hub.Keptn, diff --git a/internal/tracker/tracker.go b/internal/tracker/tracker.go index 16ac6bcfb..1ce859eed 100644 --- a/internal/tracker/tracker.go +++ b/internal/tracker/tracker.go @@ -189,6 +189,7 @@ func (t *Tracker) cloneRepository() (string, string, error) { hub.CoreDNS, hub.Falco, hub.Gatekeeper, + hub.Headlamp, hub.HelmPlugin, hub.KCL, hub.KedaScaler, diff --git a/scripts/prepare-docs.sh b/scripts/prepare-docs.sh index 8fd930803..9d9b30ca0 100755 --- a/scripts/prepare-docs.sh +++ b/scripts/prepare-docs.sh @@ -12,6 +12,7 @@ cat docs/www/headers/container_images_repositories docs/container_images_reposit 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 cat docs/www/headers/gatekeeper_policies_repositories docs/gatekeeper_policies_repositories.md > docs/www/content/topics/repositories/gatekeeper-policies.md +cat docs/www/headers/headlamp_plugins_repositories docs/headlamp_plugins_repositories.md > docs/www/content/topics/repositories/headlamp-plugins.md cat docs/www/headers/helm_charts_repositories docs/helm_charts_repositories.md > docs/www/content/topics/repositories/helm-charts.md cat docs/www/headers/helm_plugins_repositories docs/helm_plugins_repositories.md > docs/www/content/topics/repositories/helm-plugins.md cat docs/www/headers/kcl_modules_repositories docs/kcl_modules_repositories.md > docs/www/content/topics/repositories/kcl-modules.md @@ -30,6 +31,7 @@ cat docs/www/headers/tinkerbell_actions_repositories docs/tinkerbell_actions_rep cat docs/www/headers/security_report docs/security_report.md > docs/www/content/topics/security_report.md cat docs/www/headers/cli docs/cli.md > docs/www/content/topics/cli.md cat docs/www/headers/argo_annotations docs/argo_annotations.md > docs/www/content/topics/annotations/argo.md +cat docs/www/headers/headlamp_annotations docs/headlamp_annotations.md > docs/www/content/topics/annotations/headlamp.md cat docs/www/headers/helm_annotations docs/helm_annotations.md > docs/www/content/topics/annotations/helm.md cat docs/www/headers/keptn_annotations docs/keptn_annotations.md > docs/www/content/topics/annotations/keptn.md cat docs/www/headers/krew_annotations docs/krew_annotations.md > docs/www/content/topics/annotations/krew.md diff --git a/web/public/static/media/headlamp-light.svg b/web/public/static/media/headlamp-light.svg new file mode 100644 index 000000000..cdf0a62fc --- /dev/null +++ b/web/public/static/media/headlamp-light.svg @@ -0,0 +1,5 @@ + + + diff --git a/web/public/static/media/headlamp.svg b/web/public/static/media/headlamp.svg new file mode 100644 index 000000000..72ce76dbb --- /dev/null +++ b/web/public/static/media/headlamp.svg @@ -0,0 +1,5 @@ + + diff --git a/web/public/static/media/headlamp_icon.png b/web/public/static/media/headlamp_icon.png new file mode 100644 index 000000000..353329bdb Binary files /dev/null and b/web/public/static/media/headlamp_icon.png differ diff --git a/web/public/static/media/placeholder_pkg_headlamp.png b/web/public/static/media/placeholder_pkg_headlamp.png new file mode 100644 index 000000000..7b6d9f83e Binary files /dev/null and b/web/public/static/media/placeholder_pkg_headlamp.png differ diff --git a/web/src/layout/common/ButtonCopyToClipboard.tsx b/web/src/layout/common/ButtonCopyToClipboard.tsx index 738dddb89..86479eea7 100644 --- a/web/src/layout/common/ButtonCopyToClipboard.tsx +++ b/web/src/layout/common/ButtonCopyToClipboard.tsx @@ -17,6 +17,7 @@ interface Props { icon?: JSX.Element; disabled?: boolean; label?: string; + title?: string; tooltipType?: 'normal' | 'light'; noTooltip?: boolean; onClick?: () => void; @@ -105,6 +106,7 @@ const ButtonCopyToClipboard = (props: Props) => { }} disabled={props.disabled} aria-label={props.label || 'Copy to clipboard'} + title={props.title} >
Discovering artifacts to use with CNCF projects can be difficult. If every CNCF project that needs to share artifacts creates its own Hub this creates a fair amount of repeat work for each project and a fractured experience for those trying to find the artifacts to consume. The Artifact Hub attempts to solve that by providing a single experience for consumers that any CNCF project can leverage. diff --git a/web/src/layout/home/index.test.tsx b/web/src/layout/home/index.test.tsx index b2c2a8eac..955566b49 100644 --- a/web/src/layout/home/index.test.tsx +++ b/web/src/layout/home/index.test.tsx @@ -125,7 +125,7 @@ describe('Home index', () => { await waitFor(() => expect(API.getStats).toHaveBeenCalledTimes(1)); const links = await screen.findAllByRole('button'); - expect(links).toHaveLength(25); + expect(links).toHaveLength(26); expect(links[2]).toHaveProperty('href', 'https://github.com/artifacthub/hub'); expect(links[3]).toHaveProperty('href', 'https://cloud-native.slack.com/channels/artifact-hub'); @@ -140,21 +140,22 @@ describe('Home index', () => { 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://helm.sh/'); - expect(links[12]).toHaveProperty('href', 'https://kcl-lang.io/'); - expect(links[13]).toHaveProperty('href', 'https://keda.sh/'); - expect(links[14]).toHaveProperty('href', 'https://keptn.sh/'); - expect(links[15]).toHaveProperty('href', 'https://github.com/knative/client'); - expect(links[16]).toHaveProperty('href', 'https://krew.sigs.k8s.io/'); - expect(links[17]).toHaveProperty('href', 'https://kubearmor.io/'); - expect(links[18]).toHaveProperty('href', 'https://www.kubewarden.io/'); - expect(links[19]).toHaveProperty('href', 'https://www.kyverno.io/'); - expect(links[20]).toHaveProperty('href', 'https://github.com/operator-framework'); - expect(links[21]).toHaveProperty('href', 'https://www.openpolicyagent.org/'); - expect(links[22]).toHaveProperty('href', 'https://tekton.dev/'); - expect(links[23]).toHaveProperty('href', 'https://tinkerbell.org/'); - - expect(links[24]).toHaveProperty('href', 'https://www.cncf.io/sandbox-projects/'); + expect(links[11]).toHaveProperty('href', 'https://headlamp.dev/'); + expect(links[12]).toHaveProperty('href', 'https://helm.sh/'); + expect(links[13]).toHaveProperty('href', 'https://kcl-lang.io/'); + expect(links[14]).toHaveProperty('href', 'https://keda.sh/'); + expect(links[15]).toHaveProperty('href', 'https://keptn.sh/'); + expect(links[16]).toHaveProperty('href', 'https://github.com/knative/client'); + expect(links[17]).toHaveProperty('href', 'https://krew.sigs.k8s.io/'); + expect(links[18]).toHaveProperty('href', 'https://kubearmor.io/'); + expect(links[19]).toHaveProperty('href', 'https://www.kubewarden.io/'); + expect(links[20]).toHaveProperty('href', 'https://www.kyverno.io/'); + expect(links[21]).toHaveProperty('href', 'https://github.com/operator-framework'); + expect(links[22]).toHaveProperty('href', 'https://www.openpolicyagent.org/'); + expect(links[23]).toHaveProperty('href', 'https://tekton.dev/'); + expect(links[24]).toHaveProperty('href', 'https://tinkerbell.org/'); + + expect(links[25]).toHaveProperty('href', 'https://www.cncf.io/sandbox-projects/'); }); }); }); diff --git a/web/src/layout/home/index.tsx b/web/src/layout/home/index.tsx index ec5d18756..fb3eda23d 100644 --- a/web/src/layout/home/index.tsx +++ b/web/src/layout/home/index.tsx @@ -232,7 +232,7 @@ const HomeView = () => { configurations, Open Policy Agent (OPA) and Gatekeeper policies, OLM operators, Tinkerbell actions, kubectl plugins, Tekton tasks and pipelines, KEDA scalers, CoreDNS plugins, Keptn integrations, container images, Kubewarden policies, Kyverno policies, Knative client, Backstage plugins, Argo - templates, KubeArmor policies and KCL modules. + templates, KubeArmor policies, KCL modules and Headlamp plugins.