From 296236d8db820ef567b7501308b9ad0406a8627d Mon Sep 17 00:00:00 2001 From: Oliver Seitz Date: Thu, 7 May 2020 02:07:07 +0200 Subject: [PATCH] #6 unified project setup, added graphql project, added repositories and tags endpoint --- ...rbourrocks_harbour_cmd_harbour_gateway.xml | 16 +++ cmd/harbour-gateway/app/cmd.go | 46 +++++++ cmd/harbour-gateway/main.go | 15 +++ deployments/registry/docker-compose.yml | 17 +++ go.mod | 3 +- go.sum | 6 +- pkg/apiclient/apiclient.go | 6 +- pkg/harbourgateway/configuration/config.go | 35 ++++++ pkg/harbourgateway/graphql/repository.go | 48 ++++++++ pkg/harbourgateway/graphql/tags.go | 67 ++++++++++ pkg/harbourgateway/handler/graphqlhandler.go | 38 ++++++ pkg/harbourgateway/model/repository.go | 7 ++ pkg/harbourgateway/model/tag.go | 8 ++ pkg/harbourgateway/server.go | 53 ++++++++ pkg/harbourgateway/traits/graphql.go | 28 +++++ pkg/harbouriam/handler/auth.go | 24 ++-- pkg/harbouriam/handler/docker.go | 37 +++--- pkg/harbouriam/handler/profile.go | 30 +++-- pkg/harbouriam/server.go | 38 +++--- pkg/harbourscm/handler/app.go | 37 +++--- pkg/harbourscm/handler/githubrepository.go | 22 ++-- pkg/harbourscm/handler/githubtoken.go | 2 +- pkg/harbourscm/handler/mainfest.go | 24 ++-- .../{handler => models}/appconfiguration.go | 2 +- .../{handler => models}/githubmanifest.go | 3 +- pkg/harbourscm/server.go | 26 ++-- pkg/httphandler/middleware.go | 17 +++ pkg/httphandler/traits/http.go | 114 ++++++++++++++++++ pkg/httphandler/traits/idtoken.go | 41 +++++++ pkg/httphandler/traits/middleware.go | 19 +++ pkg/redisconfig/traits.go | 24 ++++ pkg/registry/config.go | 21 ++++ pkg/registry/models/repositories.go | 6 + pkg/registry/models/tags.go | 7 ++ pkg/registry/paths.go | 31 +++++ scripts/run-registry.sh | 8 ++ 36 files changed, 803 insertions(+), 123 deletions(-) create mode 100644 .idea/runConfigurations/go_build_github_com_harbourrocks_harbour_cmd_harbour_gateway.xml create mode 100644 cmd/harbour-gateway/app/cmd.go create mode 100644 cmd/harbour-gateway/main.go create mode 100644 deployments/registry/docker-compose.yml create mode 100644 pkg/harbourgateway/configuration/config.go create mode 100644 pkg/harbourgateway/graphql/repository.go create mode 100644 pkg/harbourgateway/graphql/tags.go create mode 100644 pkg/harbourgateway/handler/graphqlhandler.go create mode 100644 pkg/harbourgateway/model/repository.go create mode 100644 pkg/harbourgateway/model/tag.go create mode 100644 pkg/harbourgateway/server.go create mode 100644 pkg/harbourgateway/traits/graphql.go rename pkg/harbourscm/{handler => models}/appconfiguration.go (95%) rename pkg/harbourscm/{handler => models}/githubmanifest.go (95%) create mode 100644 pkg/httphandler/middleware.go create mode 100644 pkg/httphandler/traits/http.go create mode 100644 pkg/httphandler/traits/idtoken.go create mode 100644 pkg/httphandler/traits/middleware.go create mode 100644 pkg/redisconfig/traits.go create mode 100644 pkg/registry/config.go create mode 100644 pkg/registry/models/repositories.go create mode 100644 pkg/registry/models/tags.go create mode 100644 pkg/registry/paths.go create mode 100644 scripts/run-registry.sh diff --git a/.idea/runConfigurations/go_build_github_com_harbourrocks_harbour_cmd_harbour_gateway.xml b/.idea/runConfigurations/go_build_github_com_harbourrocks_harbour_cmd_harbour_gateway.xml new file mode 100644 index 0000000..90226a0 --- /dev/null +++ b/.idea/runConfigurations/go_build_github_com_harbourrocks_harbour_cmd_harbour_gateway.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cmd/harbour-gateway/app/cmd.go b/cmd/harbour-gateway/app/cmd.go new file mode 100644 index 0000000..762af00 --- /dev/null +++ b/cmd/harbour-gateway/app/cmd.go @@ -0,0 +1,46 @@ +package app + +import ( + server "github.com/harbourrocks/harbour/pkg/harbourgateway" + "github.com/harbourrocks/harbour/pkg/harbourgateway/configuration" + "github.com/harbourrocks/harbour/pkg/logconfig" + "github.com/harbourrocks/harbour/pkg/redisconfig" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// NewGatewayServerCommand creates a *cobra.Command object with default parameters +func NewGatewayServerCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "harbour-gateway", + Long: `The harbour.rocks Gateway server manages +all incoming http requests.`, + RunE: func(cmd *cobra.Command, args []string) error { + + // load OIDC config + s := configuration.ParseViperConfig() + + // configure logging + l := logconfig.ParseViperConfig() + logconfig.ConfigureLog(l) + + logrus.Info("Harbour Gateway configured") + + // test redis connection + redisconfig.TestConnection(s.Redis) + + return server.RunGatewayServer(s) + }, + } + + return cmd +} + +func init() { + cobra.OnInitialize(initCobra) +} + +func initCobra() { + viper.AutomaticEnv() +} diff --git a/cmd/harbour-gateway/main.go b/cmd/harbour-gateway/main.go new file mode 100644 index 0000000..8716d73 --- /dev/null +++ b/cmd/harbour-gateway/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "os" + + "github.com/harbourrocks/harbour/cmd/harbour-gateway/app" +) + +func main() { + cmd := app.NewGatewayServerCommand() + + if err := cmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/deployments/registry/docker-compose.yml b/deployments/registry/docker-compose.yml new file mode 100644 index 0000000..f5f71d3 --- /dev/null +++ b/deployments/registry/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3.3' +services: + registry: + container_name: standalone-registry + image: registry:2 + ports: + - 5000:5000 +# environment: +# REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt +# REGISTRY_HTTP_TLS_KEY: /certs/domain.key +# REGISTRY_AUTH: htpasswd +# REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd +# REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm +# volumes: +# - /path/data:/var/lib/registry +# - /path/certs:/certs +# - /path/auth:/auth \ No newline at end of file diff --git a/go.mod b/go.mod index ce2085c..01e4522 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/coreos/go-oidc v2.2.1+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/go-redis/redis/v7 v7.2.0 + github.com/graphql-go/graphql v0.7.9 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/sirupsen/logrus v1.2.0 github.com/spf13/cobra v0.0.7 @@ -13,6 +14,6 @@ require ( github.com/stretchr/testify v1.5.1 // indirect golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect gopkg.in/square/go-jose.v2 v2.4.1 // indirect ) diff --git a/go.sum b/go.sum index 5fa4e83..51aefdd 100644 --- a/go.sum +++ b/go.sum @@ -30,7 +30,6 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U= github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs= github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -46,11 +45,14 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/graphql-go/graphql v0.7.9 h1:5Va/Rt4l5g3YjwDnid3vFfn43faaQBq7rMcIZ0VnV34= +github.com/graphql-go/graphql v0.7.9/go.mod h1:k6yrAYQaSP59DC5UVxbgxESlmVyojThKdORUqGDGmrI= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -179,10 +181,12 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y= gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/apiclient/apiclient.go b/pkg/apiclient/apiclient.go index cfea1e5..bc4cdaa 100644 --- a/pkg/apiclient/apiclient.go +++ b/pkg/apiclient/apiclient.go @@ -8,7 +8,7 @@ import ( ) // Get issues a GET request against the url. -//The response is unmarshaled into response +//The response is unmarshalled into response func Get(url string, response interface{}) (resp *http.Response, err error) { resp, err = http.Get(url) if err != nil { @@ -26,8 +26,8 @@ func Get(url string, response interface{}) (resp *http.Response, err error) { // Post issues a POST request against the url. // The POST payload is specified by body. If body is nil then no body is sent at all. -// The response is unmarshaled into response. -func Post(url string, response interface{}, body interface{}) (resp *http.Response, err error) { +// The response is unmarshalled into response. +func Post(url string, response interface{}, _ interface{}) (resp *http.Response, err error) { resp, err = http.Post(url, "application/json", nil) if err != nil { l. diff --git a/pkg/harbourgateway/configuration/config.go b/pkg/harbourgateway/configuration/config.go new file mode 100644 index 0000000..a04c882 --- /dev/null +++ b/pkg/harbourgateway/configuration/config.go @@ -0,0 +1,35 @@ +package configuration + +import ( + "github.com/harbourrocks/harbour/pkg/auth" + "github.com/harbourrocks/harbour/pkg/redisconfig" + "github.com/harbourrocks/harbour/pkg/registry" +) + +// Options defines all options available to configure the Gateway server. +type Options struct { + Redis redisconfig.RedisOptions + OIDCConfig auth.OIDCConfig + DockerRegistry registry.RegistryConfig +} + +// NewDefaultOptions returns the default options +func NewDefaultOptions() *Options { + s := Options{ + Redis: redisconfig.NewDefaultRedisOptions(), + OIDCConfig: auth.DefaultConfig(), + } + + return &s +} + +// ParseViperConfig tries to map a viper configuration +func ParseViperConfig() *Options { + s := NewDefaultOptions() + + s.OIDCConfig = auth.ParseViperConfig() + s.Redis = redisconfig.ParseViperConfig() + s.DockerRegistry = registry.ParseViperConfig() + + return s +} diff --git a/pkg/harbourgateway/graphql/repository.go b/pkg/harbourgateway/graphql/repository.go new file mode 100644 index 0000000..745988f --- /dev/null +++ b/pkg/harbourgateway/graphql/repository.go @@ -0,0 +1,48 @@ +package graphql + +import ( + "github.com/graphql-go/graphql" + "github.com/harbourrocks/harbour/pkg/apiclient" + "github.com/harbourrocks/harbour/pkg/harbourgateway/configuration" + "github.com/harbourrocks/harbour/pkg/harbourgateway/model" + "github.com/harbourrocks/harbour/pkg/registry/models" +) + +var repositoryListType = graphql.NewList(repositoryType) +var repositoryType = graphql.NewObject( + graphql.ObjectConfig{ + Name: "Repository", + Description: "All repositories of the registry.", + Fields: graphql.Fields{ + "name": &graphql.Field{ + Type: graphql.String, + Description: "The name of the docker registry repository. " + + "This can contain slashes", + }, + }, + }, +) + +func RepositoriesField(options configuration.Options) *graphql.Field { + return &graphql.Field{ + Type: repositoryListType, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + // query repositories from docker registry + var regRepositories models.Repositories + _, err := apiclient.Get(options.DockerRegistry.RepositoriesURL(), ®Repositories) + if err != nil { + return nil, err + } + + // map docker registry response to harbour response + var response = make([]model.Repository, len(regRepositories.Repositories)) + for i, repository := range regRepositories.Repositories { + response[i] = model.Repository{ + Name: repository, + } + } + + return response, err + }, + } +} diff --git a/pkg/harbourgateway/graphql/tags.go b/pkg/harbourgateway/graphql/tags.go new file mode 100644 index 0000000..cebb7fc --- /dev/null +++ b/pkg/harbourgateway/graphql/tags.go @@ -0,0 +1,67 @@ +package graphql + +import ( + "github.com/graphql-go/graphql" + "github.com/harbourrocks/harbour/pkg/apiclient" + "github.com/harbourrocks/harbour/pkg/harbourgateway/configuration" + "github.com/harbourrocks/harbour/pkg/harbourgateway/model" + "github.com/harbourrocks/harbour/pkg/registry/models" +) + +var tagListType = graphql.NewList(tagType) +var tagType = graphql.NewObject( + graphql.ObjectConfig{ + Name: "Tag", + Fields: graphql.Fields{ + "name": &graphql.Field{ + Type: graphql.String, + Description: "The name of the tag. The tag combined with the repository " + + "is a unique identifier for an image", + }, + "repository": &graphql.Field{ + Type: repositoryType, + Description: "The repository of the tag.", + }, + }, + }, +) + +func TagsField(options configuration.Options) *graphql.Field { + return &graphql.Field{ + Type: tagListType, + Description: "All tags of a specified repository.", + Args: graphql.FieldConfigArgument{ + "repository": &graphql.ArgumentConfig{ + Type: graphql.String, + Description: "Defines the repository of which the tags should be returned.", + }, + }, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + // parse query parameter + repositoryQuery, isOK := p.Args["repository"].(string) + if !isOK { + return nil, nil + } + + // query tags of repository from docker registry + var regTags models.Tags + _, err := apiclient.Get(options.DockerRegistry.RepositoryTagsURL(repositoryQuery), ®Tags) + if err != nil { + return nil, err + } + + // map docker registry response to harbour response + var response = make([]model.Tag, len(regTags.Tags)) + for i, repository := range regTags.Tags { + response[i] = model.Tag{ + Name: repository, + Repository: model.Repository{ + Name: regTags.Name, + }, + } + } + + return response, err + }, + } +} diff --git a/pkg/harbourgateway/handler/graphqlhandler.go b/pkg/harbourgateway/handler/graphqlhandler.go new file mode 100644 index 0000000..785a46b --- /dev/null +++ b/pkg/harbourgateway/handler/graphqlhandler.go @@ -0,0 +1,38 @@ +package handler + +import ( + "encoding/json" + "fmt" + "github.com/graphql-go/graphql" + traits2 "github.com/harbourrocks/harbour/pkg/harbourgateway/traits" + "github.com/harbourrocks/harbour/pkg/httphandler/traits" +) + +func executeQuery(query string, schema graphql.Schema) *graphql.Result { + result := graphql.Do(graphql.Params{ + Schema: schema, + RequestString: query, + }) + + if len(result.Errors) > 0 { + fmt.Printf("wrong result, unexpected errors: %v", result.Errors) + } + + return result +} + +// GraphQLModel is specific for one handler +type GraphQLModel struct { + traits.HttpModel + traits.IdTokenModel + traits2.GraphQLModel +} + +func (h GraphQLModel) Handle() (err error) { + r := h.GetRequest() + w := h.GetResponse() + s := h.GetSchema() + + result := executeQuery(r.URL.Query().Get("query"), s) + return json.NewEncoder(w).Encode(result) +} diff --git a/pkg/harbourgateway/model/repository.go b/pkg/harbourgateway/model/repository.go new file mode 100644 index 0000000..ccad114 --- /dev/null +++ b/pkg/harbourgateway/model/repository.go @@ -0,0 +1,7 @@ +package model + +// Repository represents a repository on the docker registry +// A repository is identified by its name +type Repository struct { + Name string `json:"name"` +} diff --git a/pkg/harbourgateway/model/tag.go b/pkg/harbourgateway/model/tag.go new file mode 100644 index 0000000..3b2ec76 --- /dev/null +++ b/pkg/harbourgateway/model/tag.go @@ -0,0 +1,8 @@ +package model + +// Tag represents a tagged image in a specific repository +// A tag is identified by its name +type Tag struct { + Name string `json:"Name"` + Repository Repository `json:"repository"` +} diff --git a/pkg/harbourgateway/server.go b/pkg/harbourgateway/server.go new file mode 100644 index 0000000..9a91261 --- /dev/null +++ b/pkg/harbourgateway/server.go @@ -0,0 +1,53 @@ +package server + +import ( + "fmt" + "github.com/graphql-go/graphql" + "github.com/harbourrocks/harbour/pkg/harbourgateway/configuration" + graphql2 "github.com/harbourrocks/harbour/pkg/harbourgateway/graphql" + "github.com/harbourrocks/harbour/pkg/harbourgateway/handler" + traits2 "github.com/harbourrocks/harbour/pkg/harbourgateway/traits" + "github.com/harbourrocks/harbour/pkg/httphandler" + "github.com/harbourrocks/harbour/pkg/httphandler/traits" + "github.com/sirupsen/logrus" + "net/http" +) + +// RunGatewayServer runs the Gateway server application +func RunGatewayServer(o *configuration.Options) error { + logrus.Info("Started Harbour Gateway server") + + var queryType = graphql.NewObject( + graphql.ObjectConfig{ + Name: "Query", + Fields: graphql.Fields{ + "repositories": graphql2.RepositoriesField(*o), + "tags": graphql2.TagsField(*o), + }, + }) + + var schema, _ = graphql.NewSchema( + graphql.SchemaConfig{ + Query: queryType, + }, + ) + + http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { + logrus.Trace(r) + model := handler.GraphQLModel{} + traits.AddHttp(&model, r, w, o.OIDCConfig) + traits.AddIdToken(&model) + traits2.AddGraphQL(&model, schema) + if err := httphandler.ForceAuthenticated(&model); err != nil { + _ = model.Handle() + } + }) + + bindAddress := "127.0.0.1:5400" + logrus.Info(fmt.Sprintf("Listening on http://%s/", bindAddress)) + + err := http.ListenAndServe(bindAddress, nil) + logrus.Fatal(err) + + return err +} diff --git a/pkg/harbourgateway/traits/graphql.go b/pkg/harbourgateway/traits/graphql.go new file mode 100644 index 0000000..5e8a9fe --- /dev/null +++ b/pkg/harbourgateway/traits/graphql.go @@ -0,0 +1,28 @@ +package traits + +import ( + "github.com/graphql-go/graphql" +) + +// GraphQLTrait returns a schema +type GraphQLTrait interface { + GetSchema() graphql.Schema + SetSchema(graphql.Schema) +} + +// RequestModel holds the request +type GraphQLModel struct { + schema graphql.Schema +} + +func (m GraphQLModel) GetSchema() graphql.Schema { + return m.schema +} + +func (m *GraphQLModel) SetSchema(s graphql.Schema) { + m.schema = s +} + +func AddGraphQL(trait GraphQLTrait, s graphql.Schema) { + trait.SetSchema(s) +} diff --git a/pkg/harbouriam/handler/auth.go b/pkg/harbouriam/handler/auth.go index ad6459b..58bf79d 100644 --- a/pkg/harbouriam/handler/auth.go +++ b/pkg/harbouriam/handler/auth.go @@ -2,25 +2,27 @@ package handler import ( "github.com/harbourrocks/harbour/pkg/auth" - "github.com/harbourrocks/harbour/pkg/httphandler" - "github.com/harbourrocks/harbour/pkg/redisconfig" + "github.com/harbourrocks/harbour/pkg/httphandler/traits" "net/http" ) -// AuthHandler handles the authentication of an user -type AuthHandler struct { - httphandler.HttpHandler - redisconfig.RedisOptions +// AuthModel handles the authentication of an user +type AuthModel struct { + traits.HttpModel + traits.IdTokenModel } -// Test can be used to test authentication +// Handle can be used to test authentication // 401 is returned if authentication failed, 200 otherwise -func (a AuthHandler) Test() { - _, err := auth.HeaderAuth(a.Request, a.OIDCConfig) +func (a AuthModel) Handle() { + r := a.GetRequest() + w := a.GetResponse() + + _, err := auth.HeaderAuth(r, a.GetOidcConfig()) if err != nil { - a.Response.WriteHeader(http.StatusUnauthorized) + w.WriteHeader(http.StatusUnauthorized) } else { - a.Response.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusOK) } } diff --git a/pkg/harbouriam/handler/docker.go b/pkg/harbouriam/handler/docker.go index 289d3c4..bcd6e94 100644 --- a/pkg/harbouriam/handler/docker.go +++ b/pkg/harbouriam/handler/docker.go @@ -3,36 +3,35 @@ package handler import ( "encoding/base64" redis2 "github.com/harbourrocks/harbour/pkg/harbouriam/redis" - "github.com/harbourrocks/harbour/pkg/httphandler" + "github.com/harbourrocks/harbour/pkg/httphandler/traits" "github.com/harbourrocks/harbour/pkg/redisconfig" l "github.com/sirupsen/logrus" "golang.org/x/crypto/bcrypt" "net/http" ) -type DockerHandler struct { - httphandler.HttpHandler - redisconfig.RedisOptions -} - type DockerSetPassword struct { Password string `json:"password"` } -func (h DockerHandler) HandleSetPassword() { +// DockerModel is specific for one handler +type DockerModel struct { + traits.HttpModel + traits.IdTokenModel + redisconfig.RedisModel +} + +func (h DockerModel) Handle() { + w := h.GetResponse() + redisConfig := h.GetRedisConfig() + idToken := h.GetToken() + var model DockerSetPassword if err := h.ReadRequest(&model); err != nil { - h.Response.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) return // error logged in ReadRequest } - // extract userId from token - idToken, err := h.ExtractUser() - if err != nil { - h.Response.WriteHeader(http.StatusUnauthorized) - return - } - // validate password length, min 5 if len(model.Password) < 5 { _ = h.WriteErrorResponse(1000) @@ -43,7 +42,7 @@ func (h DockerHandler) HandleSetPassword() { passwordHashed, err := bcrypt.GenerateFromPassword([]byte(model.Password), 12) if err != nil { l.WithError(err).Error("Failed to hash password") - h.Response.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) return } @@ -52,12 +51,12 @@ func (h DockerHandler) HandleSetPassword() { l.Tracef("Hashed password %s", passwordBase64) // save to redis as 'docker-password' - client := redisconfig.OpenClient(h.RedisOptions) + client := redisconfig.OpenClient(redisConfig) if err := client.HSet(redis2.IamUserKey(idToken.Subject), "docker-password", passwordBase64).Err(); err != nil { l.WithError(err).Error("Failed to save docker-password") - h.Response.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) return } - h.Response.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusOK) } diff --git a/pkg/harbouriam/handler/profile.go b/pkg/harbouriam/handler/profile.go index b85c5bd..2fa6757 100644 --- a/pkg/harbouriam/handler/profile.go +++ b/pkg/harbouriam/handler/profile.go @@ -2,39 +2,37 @@ package handler import ( redis2 "github.com/harbourrocks/harbour/pkg/harbouriam/redis" - "github.com/harbourrocks/harbour/pkg/httphandler" + "github.com/harbourrocks/harbour/pkg/httphandler/traits" "github.com/harbourrocks/harbour/pkg/redisconfig" l "github.com/sirupsen/logrus" "net/http" ) -type ProfileHandler struct { - httphandler.HttpHandler - redisconfig.RedisOptions +type ProfileModel struct { + traits.HttpModel + traits.IdTokenModel + redisconfig.RedisModel } -// HandleRefreshProfile extracts the latest user information from an id token -func (h ProfileHandler) HandleRefreshProfile() { - // extract userId from token - idToken, err := h.ExtractUser() - if err != nil { - h.Response.WriteHeader(http.StatusUnauthorized) - return - } +// Handle extracts the latest user information from an id token +func (h ProfileModel) Handle() { + w := h.GetResponse() + redisConfig := h.GetRedisConfig() + idToken := h.GetToken() // save to redis as 'docker-password' - client := redisconfig.OpenClient(h.RedisOptions) - err = client.HSet(redis2.IamUserKey(idToken.Subject), + client := redisconfig.OpenClient(redisConfig) + err := client.HSet(redis2.IamUserKey(idToken.Subject), "email", idToken.Email, "preferred_username", idToken.PreferredUsername, "name", idToken.Name).Err() if err != nil { l.WithError(err).Error("Failed to save user information") - h.Response.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) return } l.Trace("User refreshed") - h.Response.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusOK) } diff --git a/pkg/harbouriam/server.go b/pkg/harbouriam/server.go index 8c64d9e..383bade 100644 --- a/pkg/harbouriam/server.go +++ b/pkg/harbouriam/server.go @@ -5,6 +5,8 @@ import ( "github.com/harbourrocks/harbour/pkg/harbouriam/configuration" "github.com/harbourrocks/harbour/pkg/harbouriam/handler" "github.com/harbourrocks/harbour/pkg/httphandler" + "github.com/harbourrocks/harbour/pkg/httphandler/traits" + "github.com/harbourrocks/harbour/pkg/redisconfig" "net/http" "net/url" "path" @@ -26,30 +28,36 @@ func RunIAMServer(o *configuration.Options) error { http.HandleFunc("/auth/test", func(w http.ResponseWriter, r *http.Request) { logrus.Trace(r) - // AuthHandler - authHandler := handler.AuthHandler{ - HttpHandler: httphandler.NewHttpHandler(r, w, o.OIDCConfig), - RedisOptions: o.Redis, - } - authHandler.Test() + model := handler.AuthModel{} + traits.AddHttp(&model, r, w, o.OIDCConfig) + traits.AddIdToken(&model) + + model.Handle() }) http.HandleFunc("/refresh", func(w http.ResponseWriter, r *http.Request) { logrus.Trace(r) - // AuthHandler - handler.ProfileHandler{ - HttpHandler: httphandler.NewHttpHandler(r, w, o.OIDCConfig), - RedisOptions: o.Redis, - }.HandleRefreshProfile() + model := handler.ProfileModel{} + traits.AddHttp(&model, r, w, o.OIDCConfig) + traits.AddIdToken(&model) + redisconfig.AddRedis(&model, o.Redis) + + if err := httphandler.ForceAuthenticated(&model); err != nil { + model.Handle() + } }) // DockerHandler http.HandleFunc("/docker/password", func(w http.ResponseWriter, r *http.Request) { logrus.Trace(r) - handler.DockerHandler{ - HttpHandler: httphandler.NewHttpHandler(r, w, o.OIDCConfig), - RedisOptions: o.Redis, - }.HandleSetPassword() + model := handler.DockerModel{} + traits.AddHttp(&model, r, w, o.OIDCConfig) + traits.AddIdToken(&model) + redisconfig.AddRedis(&model, o.Redis) + + if err := httphandler.ForceAuthenticated(&model); err != nil { + model.Handle() + } }) bindAddress := "127.0.0.1:5100" diff --git a/pkg/harbourscm/handler/app.go b/pkg/harbourscm/handler/app.go index cf3ab73..1a95293 100644 --- a/pkg/harbourscm/handler/app.go +++ b/pkg/harbourscm/handler/app.go @@ -4,24 +4,29 @@ import ( "fmt" "github.com/harbourrocks/harbour/pkg/apiclient" "github.com/harbourrocks/harbour/pkg/harbourscm/configuration" + "github.com/harbourrocks/harbour/pkg/harbourscm/models" "github.com/harbourrocks/harbour/pkg/harbourscm/redis" - "github.com/harbourrocks/harbour/pkg/httphandler" + "github.com/harbourrocks/harbour/pkg/httphandler/traits" "github.com/harbourrocks/harbour/pkg/redisconfig" l "github.com/sirupsen/logrus" "net/http" "time" ) -type AppHandler struct { - httphandler.HttpHandler - Config *configuration.Options +type AppModel struct { + traits.HttpModel + redisconfig.RedisModel } -func (h *AppHandler) Handle() { - code := h.Request.URL.Query().Get("code") +func (h *AppModel) Handle(config configuration.Options) { + w := h.GetResponse() + r := h.GetRequest() + redisConfig := h.GetRedisConfig() + + code := r.URL.Query().Get("code") if code == "" { l.Warningf("Invalid code from Github", code) - h.Response.WriteHeader(http.StatusBadRequest) + w.WriteHeader(http.StatusBadRequest) return } @@ -30,16 +35,16 @@ func (h *AppHandler) Handle() { // retrieve github app configuration // this also returns the private key of the app which is required to build access tokens conversionUrl := fmt.Sprintf("https://api.github.com/app-manifests/%s/conversions", code) - appConfiguration := AppConfiguration{} + appConfiguration := models.AppConfiguration{} _, err := apiclient.Post(conversionUrl, &appConfiguration, nil) if err != nil { // error was already logged in api client - h.Response.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) return } // save app configuration to redis - client := redisconfig.OpenClient(h.Config.Redis) + client := redisconfig.OpenClient(redisConfig) err = client.HSet(redis.GithubAppKey(appConfiguration.Id), "clientSecret", appConfiguration.ClientSecret, "clientId", appConfiguration.ClientId, @@ -49,21 +54,21 @@ func (h *AppHandler) Handle() { "id", appConfiguration.Id).Err() if err != nil { l.WithError(err).Error("Failed to persist github app config") - h.Response.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) return } // generate a access token to make sure everything works - if token, err := GenerateGithubToken(appConfiguration.Id, time.Minute*1, h.Config.Redis); err != nil { + if token, err := GenerateGithubToken(appConfiguration.Id, time.Minute*1, redisConfig); err != nil { l.WithError(err).Errorf("Failed to obtain access token") - h.Response.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) return } else { l.Tracef("AccessToken: %s", token) } // setup redirect headers - redirectUrl := fmt.Sprintf("%s/settings/vsc?app=github&id=%d", h.Config.HostUrl, appConfiguration.Id) - h.Response.Header().Set("Location", redirectUrl) - h.Response.WriteHeader(http.StatusTemporaryRedirect) + redirectUrl := fmt.Sprintf("%s/settings/vsc?app=github&id=%d", config.HostUrl, appConfiguration.Id) + w.Header().Set("Location", redirectUrl) + w.WriteHeader(http.StatusTemporaryRedirect) } diff --git a/pkg/harbourscm/handler/githubrepository.go b/pkg/harbourscm/handler/githubrepository.go index 449ec37..1f2f97a 100644 --- a/pkg/harbourscm/handler/githubrepository.go +++ b/pkg/harbourscm/handler/githubrepository.go @@ -1,26 +1,30 @@ package handler import ( - "github.com/harbourrocks/harbour/pkg/harbourscm/configuration" - "github.com/harbourrocks/harbour/pkg/httphandler" + "github.com/harbourrocks/harbour/pkg/httphandler/traits" + "github.com/harbourrocks/harbour/pkg/redisconfig" l "github.com/sirupsen/logrus" "net/http" "strconv" "time" ) -type GithubRepositoryHandler struct { - httphandler.HttpHandler - Config *configuration.Options +type GithubRepositoryModel struct { + traits.HttpModel + redisconfig.RedisModel } -func (h *GithubRepositoryHandler) Handle() { - appId, _ := strconv.Atoi(h.Request.URL.Query().Get("appId")) +func (h *GithubRepositoryModel) Handle() { + r := h.GetRequest() + w := h.GetResponse() + redisConfig := h.GetRedisConfig() + + appId, _ := strconv.Atoi(r.URL.Query().Get("appId")) // generate a access token to make sure everything works - if token, err := GenerateGithubToken(appId, time.Minute*1, h.Config.Redis); err != nil { + if token, err := GenerateGithubToken(appId, time.Minute*1, redisConfig); err != nil { l.WithError(err).Errorf("Failed to obtain access token") - h.Response.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) return } else { l.Tracef("AccessToken: %s", token) diff --git a/pkg/harbourscm/handler/githubtoken.go b/pkg/harbourscm/handler/githubtoken.go index ad695c7..3b8edf1 100644 --- a/pkg/harbourscm/handler/githubtoken.go +++ b/pkg/harbourscm/handler/githubtoken.go @@ -12,7 +12,7 @@ import ( // The token is generated by signing a self created jwt with the private key // shared with github once the app was created. // The private key was initially generated by github. -func GenerateGithubToken(appId int, validity time.Duration, redisOptions *redisconfig.RedisOptions) (tokenString string, err error) { +func GenerateGithubToken(appId int, validity time.Duration, redisOptions redisconfig.RedisOptions) (tokenString string, err error) { // resolve private key from redis client := redisconfig.OpenClient(redisOptions) keyBytes, err := client.HGet(redis.GithubAppKey(appId), "pem").Result() diff --git a/pkg/harbourscm/handler/mainfest.go b/pkg/harbourscm/handler/mainfest.go index 773ec85..386c14e 100644 --- a/pkg/harbourscm/handler/mainfest.go +++ b/pkg/harbourscm/handler/mainfest.go @@ -3,32 +3,32 @@ package handler import ( "fmt" "github.com/harbourrocks/harbour/pkg/harbourscm/configuration" - "github.com/harbourrocks/harbour/pkg/httphandler" + "github.com/harbourrocks/harbour/pkg/harbourscm/models" + "github.com/harbourrocks/harbour/pkg/httphandler/traits" ) -type ManifestHandler struct { - httphandler.HttpHandler - Config *configuration.Options +type ManifestModel struct { + traits.HttpModel } -func (h *ManifestHandler) Handle() { - hostUrl := h.Config.HostUrl +func (h *ManifestModel) Handle(config configuration.Options) { + hostUrl := config.HostUrl redirectUrl := fmt.Sprintf("%s/scm/github/app", hostUrl.String()) webhookUrl := fmt.Sprintf("%s/scm/github/hooks", hostUrl.String()) - manifest := GithubManifest{ + manifest := models.GithubManifest{ Name: "harbour.rocks", Url: hostUrl.String(), RedirectUrl: redirectUrl, - DefaultEvents: []GithubEventType{Push}, - HookAttributes: HookAttributes{ + DefaultEvents: []models.GithubEventType{models.Push}, + HookAttributes: models.HookAttributes{ Url: webhookUrl, Active: true, }, - DefaultPermissions: DefaultPermissions{ - Contents: Write, - Metadata: Read, + DefaultPermissions: models.DefaultPermissions{ + Contents: models.Write, + Metadata: models.Read, }, } diff --git a/pkg/harbourscm/handler/appconfiguration.go b/pkg/harbourscm/models/appconfiguration.go similarity index 95% rename from pkg/harbourscm/handler/appconfiguration.go rename to pkg/harbourscm/models/appconfiguration.go index cf0662b..7a1baef 100644 --- a/pkg/harbourscm/handler/appconfiguration.go +++ b/pkg/harbourscm/models/appconfiguration.go @@ -1,4 +1,4 @@ -package handler +package models // AppConfiguration is the app configuration from github type AppConfiguration struct { diff --git a/pkg/harbourscm/handler/githubmanifest.go b/pkg/harbourscm/models/githubmanifest.go similarity index 95% rename from pkg/harbourscm/handler/githubmanifest.go rename to pkg/harbourscm/models/githubmanifest.go index dd87cfc..ae09f94 100644 --- a/pkg/harbourscm/handler/githubmanifest.go +++ b/pkg/harbourscm/models/githubmanifest.go @@ -1,4 +1,4 @@ -package handler +package models type GithubManifest struct { Name string `json:"name"` @@ -27,6 +27,7 @@ const ( type GithubPermissionType string +//noinspection GoUnusedConst const ( NoAccess GithubPermissionType = "no access" Read GithubPermissionType = "read-only" diff --git a/pkg/harbourscm/server.go b/pkg/harbourscm/server.go index ec18e7d..e4801f9 100644 --- a/pkg/harbourscm/server.go +++ b/pkg/harbourscm/server.go @@ -4,7 +4,8 @@ import ( "fmt" "github.com/harbourrocks/harbour/pkg/harbourscm/configuration" "github.com/harbourrocks/harbour/pkg/harbourscm/handler" - "github.com/harbourrocks/harbour/pkg/httphandler" + "github.com/harbourrocks/harbour/pkg/httphandler/traits" + "github.com/harbourrocks/harbour/pkg/redisconfig" "github.com/sirupsen/logrus" "net/http" ) @@ -20,29 +21,20 @@ func RunSCMServer(o *configuration.Options) error { http.HandleFunc("/scm/github/manifest", func(w http.ResponseWriter, r *http.Request) { logrus.Trace(r) - h := handler.ManifestHandler{ - Config: o, - HttpHandler: httphandler.HttpHandler{ - Request: r, - Response: w, - }, - } + model := handler.ManifestModel{} + traits.AddHttp(&model, r, w, o.OIDCConfig) - h.Handle() + model.Handle(*o) }) http.HandleFunc("/scm/github/app", func(w http.ResponseWriter, r *http.Request) { logrus.Trace(r) - h := handler.AppHandler{ - Config: o, - HttpHandler: httphandler.HttpHandler{ - Request: r, - Response: w, - }, - } + model := handler.AppModel{} + traits.AddHttp(&model, r, w, o.OIDCConfig) + redisconfig.AddRedis(&model, o.Redis) - h.Handle() + model.Handle(*o) }) bindAddress := "0.0.0.0:5200" diff --git a/pkg/httphandler/middleware.go b/pkg/httphandler/middleware.go new file mode 100644 index 0000000..8d6768b --- /dev/null +++ b/pkg/httphandler/middleware.go @@ -0,0 +1,17 @@ +package httphandler + +import ( + "errors" + "github.com/harbourrocks/harbour/pkg/httphandler/traits" + "net/http" +) + +func ForceAuthenticated(model traits.IdTokenTrait) (err error) { + if model.GetToken() == nil { + r := model.GetResponse() + r.WriteHeader(http.StatusUnauthorized) + err = errors.New("StatusUnauthorized") + } + + return +} diff --git a/pkg/httphandler/traits/http.go b/pkg/httphandler/traits/http.go new file mode 100644 index 0000000..d916da5 --- /dev/null +++ b/pkg/httphandler/traits/http.go @@ -0,0 +1,114 @@ +package traits + +import ( + "encoding/json" + "github.com/harbourrocks/harbour/pkg/auth" + l "github.com/sirupsen/logrus" + "io/ioutil" + "net/http" +) + +// RequestTrait returns a pointer to the current request object +type HttpTrait interface { + GetRequest() *http.Request + GetResponse() http.ResponseWriter + GetOidcConfig() auth.OIDCConfig + SetHttp(*http.Request, http.ResponseWriter, auth.OIDCConfig) +} + +// RequestModel holds the request +type HttpModel struct { + request *http.Request + response http.ResponseWriter + oidcConfig auth.OIDCConfig +} + +func (m HttpModel) GetRequest() *http.Request { + return m.request +} + +func (m HttpModel) GetResponse() http.ResponseWriter { + return m.response +} + +func (m HttpModel) GetOidcConfig() auth.OIDCConfig { + return m.oidcConfig +} + +func (m *HttpModel) SetHttp(r *http.Request, w http.ResponseWriter, o auth.OIDCConfig) { + m.request = r + m.response = w + m.oidcConfig = o +} + +// WriteResponse marshals the interface to json and throws on error +// Errors are logged and should be handled by returning 500 +func (m *HttpModel) WriteResponse(v interface{}) (err error) { + w := m.GetResponse() + + var data []byte + + if l.GetLevel() == l.TraceLevel { + data, err = json.MarshalIndent(v, "", " ") + } else { + data, err = json.Marshal(v) + } + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if _, err = w.Write(data); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + return +} + +// ReadRequest Parses the request as JSON into the model parameter +// Errors are logged and should be handled by returning 500 +func (m *HttpModel) ReadRequest(model interface{}) (err error) { + r := m.GetRequest() + w := m.GetResponse() + + bytes, err := ioutil.ReadAll(r.Body) + if err != nil { + l.WithError(err).Error("Failed to read request") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + l.Tracef("Payload: %s", string(bytes)) + + err = json.Unmarshal(bytes, model) + if err != nil { + l.WithError(err).Error("Failed to read request") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + return +} + +// WriteErrorResponse writes the passed error code to the response +// is tries also to resolve a error message (not implemented) +// The status code is set to 400 BadRequest +// Errors are logged and should be handled by returning 500 +func (m *HttpModel) WriteErrorResponse(errorCode int) error { + w := m.GetResponse() + w.WriteHeader(http.StatusBadRequest) + + return m.WriteResponse(struct { + ErrorCode int `json:"errorCode"` + Message string `json:"message"` + }{ + ErrorCode: errorCode, + Message: "", + }) +} + +func AddHttp(trait HttpTrait, r *http.Request, w http.ResponseWriter, o auth.OIDCConfig) { + trait.SetHttp(r, w, o) +} diff --git a/pkg/httphandler/traits/idtoken.go b/pkg/httphandler/traits/idtoken.go new file mode 100644 index 0000000..10980b6 --- /dev/null +++ b/pkg/httphandler/traits/idtoken.go @@ -0,0 +1,41 @@ +package traits + +import ( + "github.com/harbourrocks/harbour/pkg/auth" +) + +// IdTokenTrait returns an idToken to the caller +type IdTokenTrait interface { + GetToken() *auth.IdToken + SetToken(t auth.IdToken) + HttpTrait +} + +// IdTokenModel holds the idToken +type IdTokenModel struct { + idToken *auth.IdToken +} + +func (m IdTokenModel) GetToken() *auth.IdToken { + return m.idToken +} + +func (m *IdTokenModel) SetToken(t auth.IdToken) { + m.idToken = &t +} + +func AddIdToken(trait IdTokenTrait) { + r := trait.GetRequest() + + token, err := auth.HeaderAuth(r, trait.GetOidcConfig()) + if err != nil { + return + } + + idToken, err := auth.IdTokenFromToken(token) + if err != nil { + return + } + + trait.SetToken(idToken) +} diff --git a/pkg/httphandler/traits/middleware.go b/pkg/httphandler/traits/middleware.go new file mode 100644 index 0000000..5fd5a6c --- /dev/null +++ b/pkg/httphandler/traits/middleware.go @@ -0,0 +1,19 @@ +package traits + +type MiddlewareHandler struct { + handler []func(interface{}) error +} + +func (h *MiddlewareHandler) AddMiddleware(m func(interface{}) error) { + h.handler = append(h.handler, m) +} + +func (h *MiddlewareHandler) Handle() (err error) { + for _, handler := range h.handler { + if err = handler(h); err != nil { + return + } + } + + return +} diff --git a/pkg/redisconfig/traits.go b/pkg/redisconfig/traits.go new file mode 100644 index 0000000..2350778 --- /dev/null +++ b/pkg/redisconfig/traits.go @@ -0,0 +1,24 @@ +package redisconfig + +// RedisTrait returns redis config +type RedisTrait interface { + GetRedisConfig() RedisOptions + SetRedisConfig(RedisOptions) +} + +// RequestModel holds the request +type RedisModel struct { + redisOptions RedisOptions +} + +func (m RedisModel) GetRedisConfig() RedisOptions { + return m.redisOptions +} + +func (m *RedisModel) SetRedisConfig(s RedisOptions) { + m.redisOptions = s +} + +func AddRedis(trait RedisTrait, s RedisOptions) { + trait.SetRedisConfig(s) +} diff --git a/pkg/registry/config.go b/pkg/registry/config.go new file mode 100644 index 0000000..54eea35 --- /dev/null +++ b/pkg/registry/config.go @@ -0,0 +1,21 @@ +package registry + +import ( + "github.com/spf13/viper" + "strings" +) + +// RegistryConfig is the required minimum docker registry connections +type RegistryConfig struct { + Url string +} + +// ParseViperConfig tries to map a viper configuration +func ParseViperConfig() RegistryConfig { + var s RegistryConfig + + s.Url = viper.GetString("REGISTRY_URL") + s.Url = strings.Trim(s.Url, " /") + + return s +} diff --git a/pkg/registry/models/repositories.go b/pkg/registry/models/repositories.go new file mode 100644 index 0000000..3fe7b1c --- /dev/null +++ b/pkg/registry/models/repositories.go @@ -0,0 +1,6 @@ +package models + +// Repositories is the response model of /v2/_catalog +type Repositories struct { + Repositories []string `json:"repositories"` +} diff --git a/pkg/registry/models/tags.go b/pkg/registry/models/tags.go new file mode 100644 index 0000000..99d14c4 --- /dev/null +++ b/pkg/registry/models/tags.go @@ -0,0 +1,7 @@ +package models + +// Tags is the response model of /v2//tags/list +type Tags struct { + Name string `json:"name"` + Tags []string `json:"tags"` +} diff --git a/pkg/registry/paths.go b/pkg/registry/paths.go new file mode 100644 index 0000000..1f526b0 --- /dev/null +++ b/pkg/registry/paths.go @@ -0,0 +1,31 @@ +package registry + +import "fmt" + +// RepositoriesURL returns the URL to query all repositories +// Includes a leading slash +func RepositoriesURL() string { + return "/v2/_catalog" +} + +// RepositoriesURL returns the URL to query all repositories +func (c RegistryConfig) RepositoriesURL() string { + return combine(c.Url, RepositoriesURL()) +} + +// RepositoryTagsURL returns the URL to query all tags of a repository +// Includes a leading slash +// Repository is identified by its name +func RepositoryTagsURL(repositoryName string) string { + return fmt.Sprintf("/v2/%s/tags/list", repositoryName) +} + +// RepositoryTagsURL returns the URL to query all tags of a repository +// Repository is identified by its name +func (c RegistryConfig) RepositoryTagsURL(repositoryName string) string { + return combine(c.Url, RepositoryTagsURL(repositoryName)) +} + +func combine(host, path string) string { + return fmt.Sprintf("%s%s", host, path) +} diff --git a/scripts/run-registry.sh b/scripts/run-registry.sh new file mode 100644 index 0000000..8b11bba --- /dev/null +++ b/scripts/run-registry.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +docker-compose \ + --file ./deployments/registry/docker-compose.yml \ + --project-name harbour \ + up \ + --force-recreate \ + --detach