Skip to content

Commit

Permalink
Add API endpoint for Helm exporter tool (#1544)
Browse files Browse the repository at this point in the history
Related to: sstarcher/helm-exporter#66

Signed-off-by: Sergio Castaño Arteaga <tegioz@icloud.com>
Signed-off-by: Cintia Sanchez Garcia <cynthiasg@icloud.com>
Co-authored-by: Sergio Castaño Arteaga <tegioz@icloud.com>
Co-authored-by: Cintia Sanchez Garcia <cynthiasg@icloud.com>
  • Loading branch information
tegioz and cynthia-sg committed Sep 13, 2021
1 parent 3f1c1f6 commit 64c123a
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 1 deletion.
1 change: 1 addition & 0 deletions database/migrations/functions/001_load_functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
{{ template "packages/are_all_containers_images_whitelisted.sql" }}
{{ template "packages/generate_package_tsdoc.sql" }}
{{ template "packages/get_harbor_replication_dump.sql" }}
{{ template "packages/get_helm_exporter_dump.sql" }}
{{ template "packages/get_package.sql" }}
{{ template "packages/get_package_changelog.sql" }}
{{ template "packages/get_package_summary.sql" }}
Expand Down
16 changes: 16 additions & 0 deletions database/migrations/functions/packages/get_helm_exporter_dump.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- get_helm_exporter_dump returns a json list with the latest version of all
-- packages of kind Helm available so that they can be used by Helm exporter.
create or replace function get_helm_exporter_dump()
returns setof json as $$
select coalesce(json_agg(json_build_object(
'name', p.name,
'version', p.latest_version,
'repository', json_build_object(
'name', r.name,
'url', r.url
)
)), '[]')
from package p
join repository r using (repository_id)
where r.repository_kind_id = 0;
$$ language sql;
111 changes: 111 additions & 0 deletions database/tests/functions/packages/get_helm_exporter_dump.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
-- Start transaction and plan tests
begin;
select plan(2);

-- Declare some variables
\set org1ID '00000000-0000-0000-0000-000000000001'
\set repo1ID '00000000-0000-0000-0000-000000000001'
\set repo2ID '00000000-0000-0000-0000-000000000002'
\set repo3ID '00000000-0000-0000-0000-000000000003'
\set package1ID '00000000-0000-0000-0000-000000000001'
\set package2ID '00000000-0000-0000-0000-000000000002'
\set package3ID '00000000-0000-0000-0000-000000000003'

-- No packages at this point
select is(
get_helm_exporter_dump()::jsonb,
'[]'::jsonb,
'No packages in db yet, empty dump expected'
);

-- Seed some data
insert into organization (organization_id, name, display_name, description, home_url)
values (:'org1ID', 'org1', 'Organization 1', 'Description 1', 'https://org1.com');
insert into repository (repository_id, name, display_name, url, repository_kind_id, organization_id)
values (:'repo1ID', 'repo1', 'Repo 1', 'https://repo1.com', 0, :'org1ID');
insert into repository (repository_id, name, display_name, url, repository_kind_id, organization_id)
values (:'repo2ID', 'repo2', 'Repo 2', 'https://repo2.com', 0, :'org1ID');
insert into repository (repository_id, name, display_name, url, repository_kind_id, organization_id)
values (:'repo3ID', 'repo3', 'Repo 3', 'https://repo3.com', 1, :'org1ID');
insert into package (
package_id,
name,
latest_version,
repository_id
) values (
:'package1ID',
'package1',
'1.0.0',
:'repo1ID'
);
insert into snapshot (
package_id,
version
) values (
:'package1ID',
'1.0.0'
);
insert into package (
package_id,
name,
latest_version,
repository_id
) values (
:'package2ID',
'package2',
'1.0.0',
:'repo2ID'
);
insert into snapshot (
package_id,
version
) values (
:'package2ID',
'1.0.0'
);
insert into package (
package_id,
name,
latest_version,
repository_id
) values (
:'package3ID',
'package3',
'1.0.0',
:'repo3ID'
);
insert into snapshot (
package_id,
version
) values (
:'package3ID',
'1.0.0'
);

-- Run some tests
select is(
get_helm_exporter_dump()::jsonb,
'[
{
"name": "package1",
"version": "1.0.0",
"repository": {
"name": "repo1",
"url": "https://repo1.com"
}
},
{
"name": "package2",
"version": "1.0.0",
"repository": {
"name": "repo2",
"url": "https://repo2.com"
}
}
]'::jsonb,
'Two packages expected in dump'
);

-- Finish tests and rollback transaction
select * from finish();
rollback;
3 changes: 2 additions & 1 deletion database/tests/schema/schema.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-- Start transaction and plan tests
begin;
select plan(146);
select plan(147);

-- Check default_text_search_config is correct
select results_eq(
Expand Down Expand Up @@ -411,6 +411,7 @@ select has_function('user_belongs_to_organization');
select has_function('are_all_containers_images_whitelisted');
select has_function('generate_package_tsdoc');
select has_function('get_harbor_replication_dump');
select has_function('get_helm_exporter_dump');
select has_function('get_package');
select has_function('get_package_changelog');
select has_function('get_package_summary');
Expand Down
61 changes: 61 additions & 0 deletions docs/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2693,6 +2693,67 @@ paths:
$ref: "#/components/responses/TooManyRequests"
"500":
$ref: "#/components/responses/InternalServerError"
/helm-exporter:
get:
tags:
- Integrations
summary: Get Helm exporter dump
description: Get the latest version available of all charts listed in Artifact Hub.
operationId: getHelmExporterDump
responses:
"200":
description: ""
content:
application/json:
schema:
type: array
items:
type: object
required:
- name
- version
- repository
properties:
name:
type: string
nullable: false
version:
type: string
nullable: false
repository:
type: object
nullable: false
required:
- name
- url
properties:
name:
type: string
nullable: false
url:
type: string
format: uri
nullable: false
example:
- name: artifact-hub
version: 1.2.0
repository:
name: artifact-hub
url: https://artifacthub.github.io/helm-charts/
- name: kube-prometheus-stack
version: 18.0.6
repository:
url: https://prometheus-community.github.io/helm-charts
name: prometheus-community
- name: rabbitmq
version: 8.22.0
repository:
url: https://charts.bitnami.com/bitnami
name: bitnami
"429":
$ref: "#/components/responses/TooManyRequests"
"500":
$ref: "#/components/responses/InternalServerError"
components:
securitySchemes:
ApiKeyId:
Expand Down
8 changes: 8 additions & 0 deletions internal/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,14 @@ func (h *Handlers) setupRouter() {
// careful to not introduce breaking changes.
r.Get("/harbor-replication", h.Packages.GetHarborReplicationDump)
r.Get("/harborReplication", h.Packages.GetHarborReplicationDump) // Deprecated

// Helm exporter
//
// This endpoint is used by Helm exporter (*) to get the latest version
// available of all charts listed in Artifact Hub.
//
// (*) https://github.com/sstarcher/helm-exporter
r.Get("/helm-exporter", h.Packages.GetHelmExporterDump)
})

// Monocular compatible search API
Expand Down
13 changes: 13 additions & 0 deletions internal/handlers/pkg/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,19 @@ func (h *Handlers) GetHarborReplicationDump(w http.ResponseWriter, r *http.Reque
helpers.RenderJSON(w, dataJSON, 1*time.Hour, http.StatusOK)
}

// GetHelmExporterDump is an http handler used to get a summary of the latest
// version available of all packages of kind Helm in the hub database so that
// they can be used by Helm exporter.
func (h *Handlers) GetHelmExporterDump(w http.ResponseWriter, r *http.Request) {
dataJSON, err := h.pkgManager.GetHelmExporterDumpJSON(r.Context())
if err != nil {
h.logger.Error().Err(err).Str("method", "GetHelmExporterDump").Send()
helpers.RenderErrorJSON(w, err)
return
}
helpers.RenderJSON(w, dataJSON, 1*time.Hour, http.StatusOK)
}

// GetRandom is an http handler used to get some random packages from the hub
// database.
func (h *Handlers) GetRandom(w http.ResponseWriter, r *http.Request) {
Expand Down
37 changes: 37 additions & 0 deletions internal/handlers/pkg/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,43 @@ func TestGetHarborReplicationDump(t *testing.T) {
})
}

func TestGetHelmExporterDump(t *testing.T) {
t.Run("get helm exporter dump succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)

hw := newHandlersWrapper()
hw.pm.On("GetHelmExporterDumpJSON", r.Context()).Return([]byte("dataJSON"), nil)
hw.h.GetHelmExporterDump(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := ioutil.ReadAll(resp.Body)

assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(1*time.Hour), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})

t.Run("error getting helm exporter dump", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)

hw := newHandlersWrapper()
hw.pm.On("GetHelmExporterDumpJSON", r.Context()).Return(nil, tests.ErrFakeDB)
hw.h.GetHelmExporterDump(w, r)
resp := w.Result()
defer resp.Body.Close()

assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}

func TestGetRandom(t *testing.T) {
t.Run("get random packages succeeded", func(t *testing.T) {
t.Parallel()
Expand Down
1 change: 1 addition & 0 deletions internal/hub/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ type PackageManager interface {
Get(ctx context.Context, input *GetPackageInput) (*Package, error)
GetChangeLogJSON(ctx context.Context, pkgID string) ([]byte, error)
GetHarborReplicationDumpJSON(ctx context.Context) ([]byte, error)
GetHelmExporterDumpJSON(ctx context.Context) ([]byte, error)
GetJSON(ctx context.Context, input *GetPackageInput) ([]byte, error)
GetRandomJSON(ctx context.Context) ([]byte, error)
GetSnapshotSecurityReportJSON(ctx context.Context, pkgID, version string) ([]byte, error)
Expand Down
7 changes: 7 additions & 0 deletions internal/pkg/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
const (
// Database queries
getHarborReplicationDumpDBQ = `select get_harbor_replication_dump()`
getHelmExporterDumpDBQ = `select get_helm_exporter_dump()`
getPkgDBQ = `select get_package($1::jsonb)`
getPkgChangeLogDBQ = `select get_package_changelog($1::uuid)`
getPkgStarsDBQ = `select get_package_stars($1::uuid, $2::uuid)`
Expand Down Expand Up @@ -81,6 +82,12 @@ func (m *Manager) GetHarborReplicationDumpJSON(ctx context.Context) ([]byte, err
return util.DBQueryJSON(ctx, m.db, getHarborReplicationDumpDBQ)
}

// GetHelmExporterDumpJSON returns a json list with the latest version of all
// packages of kind Helm available so that they can be used by Helm exporter.
func (m *Manager) GetHelmExporterDumpJSON(ctx context.Context) ([]byte, error) {
return util.DBQueryJSON(ctx, m.db, getHelmExporterDumpDBQ)
}

// GetJSON returns the package identified by the input provided as a json
// object. The json object is built by the database.
func (m *Manager) GetJSON(ctx context.Context, input *hub.GetPackageInput) ([]byte, error) {
Expand Down
28 changes: 28 additions & 0 deletions internal/pkg/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,34 @@ func TestGetHarborReplicationDumpJSON(t *testing.T) {
})
}

func TestGetHelmExporterDumpJSON(t *testing.T) {
ctx := context.Background()

t.Run("database query succeeded", func(t *testing.T) {
t.Parallel()
db := &tests.DBMock{}
db.On("QueryRow", ctx, getHelmExporterDumpDBQ).Return([]byte("dataJSON"), nil)
m := NewManager(db)

dataJSON, err := m.GetHelmExporterDumpJSON(ctx)
assert.NoError(t, err)
assert.Equal(t, []byte("dataJSON"), dataJSON)
db.AssertExpectations(t)
})

t.Run("database error", func(t *testing.T) {
t.Parallel()
db := &tests.DBMock{}
db.On("QueryRow", ctx, getHelmExporterDumpDBQ).Return(nil, tests.ErrFakeDB)
m := NewManager(db)

dataJSON, err := m.GetHelmExporterDumpJSON(ctx)
assert.Equal(t, tests.ErrFakeDB, err)
assert.Nil(t, dataJSON)
db.AssertExpectations(t)
})
}

func TestGetJSON(t *testing.T) {
ctx := context.Background()
input := &hub.GetPackageInput{
Expand Down
7 changes: 7 additions & 0 deletions internal/pkg/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ func (m *ManagerMock) GetHarborReplicationDumpJSON(ctx context.Context) ([]byte,
return data, args.Error(1)
}

// GetHelmExporterDumpJSON implements the PackageManager interface.
func (m *ManagerMock) GetHelmExporterDumpJSON(ctx context.Context) ([]byte, error) {
args := m.Called(ctx)
data, _ := args.Get(0).([]byte)
return data, args.Error(1)
}

// GetJSON implements the PackageManager interface.
func (m *ManagerMock) GetJSON(ctx context.Context, input *hub.GetPackageInput) ([]byte, error) {
args := m.Called(ctx, input)
Expand Down

0 comments on commit 64c123a

Please sign in to comment.