Add some missing comments in backend code (#56)

Signed-off-by: Sergio Castaño Arteaga <tegioz@icloud.com>
This commit is contained in:
Sergio C. Arteaga 2020-01-30 12:53:01 +01:00 committed by GitHub
parent 3461232c6d
commit 10120bc29b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 123 additions and 18 deletions

View File

@ -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{})

View File

@ -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 {

View File

@ -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)

View File

@ -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)), '[]')

View File

@ -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)

View File

@ -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), '{}')

View File

@ -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)

View File

@ -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(

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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"`
}

View File

@ -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)

View File

@ -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"))

View File

@ -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()