mirror of https://github.com/artifacthub/hub.git
Add some missing comments in backend code (#56)
Signed-off-by: Sergio Castaño Arteaga <tegioz@icloud.com>
This commit is contained in:
parent
3461232c6d
commit
10120bc29b
|
|
@ -14,17 +14,23 @@ import (
|
|||
"helm.sh/helm/v3/pkg/repo"
|
||||
)
|
||||
|
||||
// job represents a job for processing a given chart version in the provided
|
||||
// repository. Jobs are created by the dispatcher that will eventually be
|
||||
// handled by a worker.
|
||||
type job struct {
|
||||
repo *hub.ChartRepository
|
||||
chartVersion *repo.ChartVersion
|
||||
}
|
||||
|
||||
// dispatcher is in charge of generating jobs and dispatching them among the
|
||||
// available workers.
|
||||
type dispatcher struct {
|
||||
ctx context.Context
|
||||
hubApi *hub.Hub
|
||||
Queue chan *job
|
||||
}
|
||||
|
||||
// newDispatcher creates a new dispatcher instance.
|
||||
func newDispatcher(ctx context.Context, hubApi *hub.Hub) *dispatcher {
|
||||
return &dispatcher{
|
||||
ctx: ctx,
|
||||
|
|
@ -33,6 +39,7 @@ func newDispatcher(ctx context.Context, hubApi *hub.Hub) *dispatcher {
|
|||
}
|
||||
}
|
||||
|
||||
// run instructs the dispatcher to start processing the repositories provided.
|
||||
func (d *dispatcher) run(wg *sync.WaitGroup, reposNames []string) {
|
||||
defer wg.Done()
|
||||
|
||||
|
|
@ -62,6 +69,8 @@ func (d *dispatcher) run(wg *sync.WaitGroup, reposNames []string) {
|
|||
close(d.Queue)
|
||||
}
|
||||
|
||||
// getRepositories returns the details of the repositories provided. If no
|
||||
// repositories are provided, all available in the database will be used.
|
||||
func (d *dispatcher) getRepositories(names []string) ([]*hub.ChartRepository, error) {
|
||||
var repos []*hub.ChartRepository
|
||||
|
||||
|
|
@ -84,6 +93,9 @@ func (d *dispatcher) getRepositories(names []string) ([]*hub.ChartRepository, er
|
|||
return repos, nil
|
||||
}
|
||||
|
||||
// trackRepositoryCharts generates jobs for each of the chart versions found in
|
||||
// the given repository, provided that that version has not been already
|
||||
// processed and its digest has not changed.
|
||||
func (d *dispatcher) trackRepositoryCharts(wg *sync.WaitGroup, r *hub.ChartRepository) {
|
||||
defer wg.Done()
|
||||
|
||||
|
|
@ -117,6 +129,7 @@ func (d *dispatcher) trackRepositoryCharts(wg *sync.WaitGroup, r *hub.ChartRepos
|
|||
}
|
||||
}
|
||||
|
||||
// loadIndexFile downloads and parses the index file of the provided repository.
|
||||
func loadIndexFile(repoURL string) (*repo.IndexFile, error) {
|
||||
repoConfig := &repo.Entry{URL: repoURL}
|
||||
getters := getter.All(&cli.EnvSettings{})
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
// worker is in charge of handling jobs generated by the dispatcher.
|
||||
type worker struct {
|
||||
ctx context.Context
|
||||
id int
|
||||
|
|
@ -24,6 +25,7 @@ type worker struct {
|
|||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// newWorker creates a new worker instance.
|
||||
func newWorker(ctx context.Context, id int, hubApi *hub.Hub) *worker {
|
||||
return &worker{
|
||||
ctx: ctx,
|
||||
|
|
@ -36,6 +38,8 @@ func newWorker(ctx context.Context, id int, hubApi *hub.Hub) *worker {
|
|||
}
|
||||
}
|
||||
|
||||
// run instructs the worker to start handling jobs. It will keep running until
|
||||
// the jobs queue is closed or the context is done.
|
||||
func (w *worker) run(wg *sync.WaitGroup, queue chan *job) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
|
|
@ -64,6 +68,8 @@ func (w *worker) run(wg *sync.WaitGroup, queue chan *job) {
|
|||
}
|
||||
}
|
||||
|
||||
// handleJob handles the provided job. This involves downloading the chart
|
||||
// archive, extracting its contents and register the corresponding package.
|
||||
func (w *worker) handleJob(j *job) error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
|
@ -141,6 +147,7 @@ func (w *worker) handleJob(j *job) error {
|
|||
return w.hubApi.RegisterPackage(w.ctx, p)
|
||||
}
|
||||
|
||||
// getFile returns the file requested from the provided chart.
|
||||
func getFile(chart *chart.Chart, name string) *chart.File {
|
||||
for _, file := range chart.Files {
|
||||
if file.Name == name {
|
||||
|
|
|
|||
|
|
@ -16,12 +16,15 @@ import (
|
|||
"github.com/tegioz/hub/internal/hub"
|
||||
)
|
||||
|
||||
// handlers groups all the http handlers defined for the hub, including the
|
||||
// router in charge of sending requests to the right handler.
|
||||
type handlers struct {
|
||||
cfg *viper.Viper
|
||||
hubApi *hub.Hub
|
||||
router http.Handler
|
||||
}
|
||||
|
||||
// setupHandlers creates a new handlers instance.
|
||||
func setupHandlers(cfg *viper.Viper, hubApi *hub.Hub) *handlers {
|
||||
h := &handlers{
|
||||
cfg: cfg,
|
||||
|
|
@ -31,6 +34,8 @@ func setupHandlers(cfg *viper.Viper, hubApi *hub.Hub) *handlers {
|
|||
return h
|
||||
}
|
||||
|
||||
// setupRouter initializes the handlers router, defining all routes used within
|
||||
// the hub, as well as some essential middleware to handle panics, logging, etc.
|
||||
func (h *handlers) setupRouter() {
|
||||
r := httprouter.New()
|
||||
|
||||
|
|
@ -55,6 +60,8 @@ func (h *handlers) setupRouter() {
|
|||
}
|
||||
}
|
||||
|
||||
// getStats is an http handler used to get some stats about packages registered
|
||||
// in the hub database.
|
||||
func (h *handlers) getStats(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
jsonData, err := h.hubApi.GetStatsJSON(r.Context())
|
||||
if err != nil {
|
||||
|
|
@ -64,6 +71,7 @@ func (h *handlers) getStats(w http.ResponseWriter, r *http.Request, _ httprouter
|
|||
renderJSON(w, jsonData)
|
||||
}
|
||||
|
||||
// search is an http handler used to search for packages in the hub database.
|
||||
func (h *handlers) search(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
query := &hub.Query{
|
||||
Text: r.FormValue("q"),
|
||||
|
|
@ -76,6 +84,7 @@ func (h *handlers) search(w http.ResponseWriter, r *http.Request, _ httprouter.P
|
|||
renderJSON(w, jsonData)
|
||||
}
|
||||
|
||||
// getPackage is an http handler used to get a package details.
|
||||
func (h *handlers) getPackage(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
packageID := ps.ByName("package_id")
|
||||
jsonData, err := h.hubApi.GetPackageJSON(r.Context(), packageID)
|
||||
|
|
@ -90,6 +99,8 @@ func (h *handlers) getPackage(w http.ResponseWriter, r *http.Request, ps httprou
|
|||
renderJSON(w, jsonData)
|
||||
}
|
||||
|
||||
// getPackageVersion is an http handler used to get the details of a package
|
||||
// specific version.
|
||||
func (h *handlers) getPackageVersion(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
packageID := ps.ByName("package_id")
|
||||
version := ps.ByName("version")
|
||||
|
|
@ -108,17 +119,30 @@ func (h *handlers) getPackageVersion(w http.ResponseWriter, r *http.Request, ps
|
|||
renderJSON(w, jsonData)
|
||||
}
|
||||
|
||||
// serveIndex is an http handler that serves the index.html file.
|
||||
func (h *handlers) serveIndex(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate")
|
||||
http.ServeFile(w, r, path.Join(h.cfg.GetString("server.webBuildPath"), "index.html"))
|
||||
}
|
||||
|
||||
// serveFile is an http handler that serves the file provided.
|
||||
func (h *handlers) serveFile(name string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, path.Join(h.cfg.GetString("server.webBuildPath"), name))
|
||||
})
|
||||
}
|
||||
|
||||
// panicHandler is an http handler invoked when a panic occurs during a request.
|
||||
func panicHandler(w http.ResponseWriter, r *http.Request, err interface{}) {
|
||||
log.Error().
|
||||
Str("method", r.Method).
|
||||
Str("url", r.URL.String()).
|
||||
Bytes("stacktrace", debug.Stack()).
|
||||
Msgf("%v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// basicAuth is a middleware that provides basic auth support.
|
||||
func (h *handlers) basicAuth(next http.Handler) http.Handler {
|
||||
username := h.cfg.GetString("server.basicAuth.username")
|
||||
password := h.cfg.GetString("server.basicAuth.password")
|
||||
|
|
@ -136,15 +160,8 @@ func (h *handlers) basicAuth(next http.Handler) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
func panicHandler(w http.ResponseWriter, r *http.Request, err interface{}) {
|
||||
log.Error().
|
||||
Str("method", r.Method).
|
||||
Str("url", r.URL.String()).
|
||||
Bytes("stacktrace", debug.Stack()).
|
||||
Msgf("%v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// accessHandler is a middleware invoked for each request. At the moment it is
|
||||
// only used to log requests.
|
||||
func accessHandler() func(http.Handler) http.Handler {
|
||||
return hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
|
||||
log.Debug().
|
||||
|
|
@ -157,6 +174,8 @@ func accessHandler() func(http.Handler) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
// renderJSON is a helper to write the json data provided to the given http
|
||||
// response writer, setting the appropriate content type.
|
||||
func renderJSON(w http.ResponseWriter, jsonData []byte) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(jsonData)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
-- get_chart_repositories returns all available chart repositories as a json
|
||||
-- array.
|
||||
create or replace function get_chart_repositories()
|
||||
returns setof json as $$
|
||||
select coalesce(json_agg(row_to_json(chart_repository)), '[]')
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
-- get_chart_repository_by_name returns the repository identified by the name
|
||||
-- provided as a json object.
|
||||
create or replace function get_chart_repository_by_name(p_name text)
|
||||
returns setof json as $$
|
||||
select row_to_json(chart_repository)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
-- get_chart_repository_packages_digest returns the digest of all packages that
|
||||
-- belong to the chart repository identified by the id provided.
|
||||
create or replace function get_chart_repository_packages_digest(p_chart_repository_id uuid)
|
||||
returns setof json as $$
|
||||
select coalesce(json_object_agg(format('%s@%s', p.name, s.version), s.digest), '{}')
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
-- get_package returns the details of the package identified by the id provided
|
||||
-- as a json object.
|
||||
create or replace function get_package(p_package_id uuid)
|
||||
returns setof json as $$
|
||||
select get_package_version(p_package_id, p.latest_version)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
-- get_package_version returns the details of the package identified by the id
|
||||
-- and version provided as a json object.
|
||||
create or replace function get_package_version(p_package_id uuid, p_version text)
|
||||
returns setof json as $$
|
||||
select json_build_object(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
-- get_stats returns the number of packages and releases registered in the
|
||||
-- database as a json object.
|
||||
create or replace function get_stats()
|
||||
returns setof json as $$
|
||||
select json_build_object(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
-- register_package registers the provided package in the database. This
|
||||
-- involves registering or updating the package entity when needed, registering
|
||||
-- a snapshot for the package version and creating/updating/deleting the
|
||||
-- package maintainers as needed depending on the ones present in the latest
|
||||
-- package version.
|
||||
create or replace function register_package(p_pkg jsonb)
|
||||
returns void as $$
|
||||
declare
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
-- search_packages searchs packages in the database that match the criteria in
|
||||
-- the query provided.
|
||||
create or replace function search_packages(p_query jsonb)
|
||||
returns setof json as $$
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
-- semver_gte checks if the first semver provided is greater or equal than the
|
||||
-- second one.
|
||||
create or replace function semver_gte(p_v1 text, p_v2 text)
|
||||
returns boolean as $$
|
||||
declare
|
||||
|
|
|
|||
|
|
@ -3,65 +3,75 @@ package hub
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/jackc/pgconn"
|
||||
"github.com/jackc/pgx/v4"
|
||||
)
|
||||
|
||||
type DB interface {
|
||||
QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row
|
||||
Exec(ctx context.Context, sql string, arguments ...interface{}) (pgconn.CommandTag, error)
|
||||
}
|
||||
|
||||
// Hub provides an API to manage repositories, packages, etc.
|
||||
type Hub struct {
|
||||
db DB
|
||||
}
|
||||
|
||||
// New creates a new Hub instance.
|
||||
func New(db DB) *Hub {
|
||||
return &Hub{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// GetChartRepositoryByName returns the chart repository identified by the name
|
||||
// provided.
|
||||
func (h *Hub) GetChartRepositoryByName(ctx context.Context, name string) (*ChartRepository, error) {
|
||||
var r *ChartRepository
|
||||
err := h.dbQueryUnmarshal(ctx, &r, "select get_chart_repository_by_name($1)", name)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// GetChartRepositories returns all available chart repositories.
|
||||
func (h *Hub) GetChartRepositories(ctx context.Context) ([]*ChartRepository, error) {
|
||||
var r []*ChartRepository
|
||||
err := h.dbQueryUnmarshal(ctx, &r, "select get_chart_repositories()")
|
||||
return r, err
|
||||
}
|
||||
|
||||
// GetChartRepositoryPackagesDigest returns the digests for all packages in the
|
||||
// repository identified by the id provided.
|
||||
func (h *Hub) GetChartRepositoryPackagesDigest(ctx context.Context, chart_repository_id string) (map[string]string, error) {
|
||||
pd := make(map[string]string)
|
||||
err := h.dbQueryUnmarshal(ctx, &pd, "select get_chart_repository_packages_digest($1)", chart_repository_id)
|
||||
return pd, err
|
||||
}
|
||||
|
||||
// GetStatsJSON returns a json object describing the number of packages and
|
||||
// releases available in the database. The json object is built by the database.
|
||||
func (h *Hub) GetStatsJSON(ctx context.Context) ([]byte, error) {
|
||||
return h.dbQueryJSON(ctx, "select get_stats()")
|
||||
}
|
||||
|
||||
// SearchPackagesJSON returns a json object with the search results produced by
|
||||
// the query provided. The json object is built by the database.
|
||||
func (h *Hub) SearchPackagesJSON(ctx context.Context, query *Query) ([]byte, error) {
|
||||
queryJSON, _ := json.Marshal(query)
|
||||
return h.dbQueryJSON(ctx, "select search_packages($1)", queryJSON)
|
||||
}
|
||||
|
||||
// RegisterPackage registers the package provided in the database.
|
||||
func (h *Hub) RegisterPackage(ctx context.Context, pkg *Package) error {
|
||||
return h.dbExec(ctx, "select register_package($1)", pkg)
|
||||
}
|
||||
|
||||
// GetPackageJSON returns the package identified by the id provided as a json
|
||||
// object. The json object is built by the database.
|
||||
func (h *Hub) GetPackageJSON(ctx context.Context, packageID string) ([]byte, error) {
|
||||
return h.dbQueryJSON(ctx, "select get_package($1)", packageID)
|
||||
}
|
||||
|
||||
// GetPackageVersionJSON returns the package identified by the id and version
|
||||
// provided as a json object. The json object is built by the database.
|
||||
func (h *Hub) GetPackageVersionJSON(ctx context.Context, packageID, version string) ([]byte, error) {
|
||||
return h.dbQueryJSON(ctx, "select get_package_version($1, $2)", packageID, version)
|
||||
}
|
||||
|
||||
// dbQueryJSON is a helper that executes the query provided and returns a bytes
|
||||
// slice containing the json data returned from the database.
|
||||
func (h *Hub) dbQueryJSON(ctx context.Context, query string, args ...interface{}) ([]byte, error) {
|
||||
var jsonData []byte
|
||||
if err := h.db.QueryRow(ctx, query, args...).Scan(&jsonData); err != nil {
|
||||
|
|
@ -70,6 +80,8 @@ func (h *Hub) dbQueryJSON(ctx context.Context, query string, args ...interface{}
|
|||
return jsonData, nil
|
||||
}
|
||||
|
||||
// dbQueryUnmarshal is a helper that executes the query provided and unmarshals
|
||||
// the json data returned from the database into the value (v) provided.
|
||||
func (h *Hub) dbQueryUnmarshal(ctx context.Context, v interface{}, query string, args ...interface{}) error {
|
||||
jsonData, err := h.dbQueryJSON(ctx, query, args...)
|
||||
if err != nil {
|
||||
|
|
@ -81,6 +93,8 @@ func (h *Hub) dbQueryUnmarshal(ctx context.Context, v interface{}, query string,
|
|||
return nil
|
||||
}
|
||||
|
||||
// dbExec is a helper that executes the query provided encoding the argument as
|
||||
// json.
|
||||
func (h *Hub) dbExec(ctx context.Context, query string, arg interface{}) error {
|
||||
jsonArg, err := json.Marshal(arg)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
package hub
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgconn"
|
||||
"github.com/jackc/pgx/v4"
|
||||
)
|
||||
|
||||
// DB defines the methods the database handler must provide.
|
||||
type DB interface {
|
||||
QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row
|
||||
Exec(ctx context.Context, sql string, arguments ...interface{}) (pgconn.CommandTag, error)
|
||||
}
|
||||
|
||||
// ChartRepository represents a Helm chart repository.
|
||||
type ChartRepository struct {
|
||||
ChartRepositoryID string `json:"chart_repository_id"`
|
||||
Name string `json:"name"`
|
||||
|
|
@ -7,34 +21,45 @@ type ChartRepository struct {
|
|||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// OperatorProvider represents an entity that provides operators that can be
|
||||
// managed by the Operator Lifecycle Manager (part of the Operator Framework).
|
||||
type OperatorProvider struct {
|
||||
OperatorProviderID string `json:"operator_provider_id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// OperatorCategory represents a category an operator may belong to.
|
||||
type OperatorCategory struct {
|
||||
OperatorCategoryID string `json:"operator_category_id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Link represents a url associated with a package.
|
||||
type Link struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// Maintainer represents a package's maintainer.
|
||||
type Maintainer struct {
|
||||
MaintainerID string `json:"maintainer_id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// PackageKind represents the kind of a given package.
|
||||
type PackageKind int64
|
||||
|
||||
const (
|
||||
Chart PackageKind = 0
|
||||
// Chart represents a Helm chart.
|
||||
Chart PackageKind = 0
|
||||
|
||||
// Operator represents an operator that can be managed by the Operator
|
||||
// Lifecycle Manager (Operator Framework).
|
||||
Operator PackageKind = 1
|
||||
)
|
||||
|
||||
// Package represents a Kubernetes package.
|
||||
type Package struct {
|
||||
PackageID string `json:"package_id"`
|
||||
Kind PackageKind `json:"kind"`
|
||||
|
|
@ -55,6 +80,7 @@ type Package struct {
|
|||
OperatorProvider *OperatorProvider `json:"operator_provider"`
|
||||
}
|
||||
|
||||
// Query represents the query used when searching for packages.
|
||||
type Query struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// SetupConfig creates a new Viper instance to handle the configuration for a
|
||||
// particular cmd. Configuration can be provided in a config file or using env
|
||||
// variables. See configs folder for some examples.
|
||||
func SetupConfig(cmd string) (*viper.Viper, error) {
|
||||
cfg := viper.New()
|
||||
cfg.Set("cmd", cmd)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// SetupDB creates a database connection pool using the configuration provided.
|
||||
func SetupDB(cfg *viper.Viper) (*pgxpool.Pool, error) {
|
||||
// Setup pool config
|
||||
poolConfig, err := pgxpool.ParseConfig(cfg.GetString("db.url"))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// SetupLogger configures the global logger using the configuration provided.
|
||||
func SetupLogger(cfg *viper.Viper, fields map[string]interface{}) error {
|
||||
// Add some context to global logger
|
||||
log.Logger = log.With().Fields(fields).Logger()
|
||||
|
|
|
|||
Loading…
Reference in New Issue