weldr: Update projects/source/info to support API v1

This commit changes the store.GetAllSources to distinguish between
getting the source by the Name field, or by the ID (the key to the map)
using GetAllSourcesByName and ...ByID.

SourceConfig.RepoConfig() now takes an id parameter because SourceConfig
only stores the Name, not the ID.

In weldr I split the sourceInfoHandler into 2 separate functions for v0
and v1 behavior, with the core of the old function refactored as
getSourceConfigs and used by both of them.

This also adds new structs for the SourceResponseV0 and SourceResponseV1
as well as helper functions for converting to/from store.SourceConfig
This commit is contained in:
Brian C. Lane 2020-04-30 13:58:51 -07:00 committed by Tom Gundersen
parent ddd2010815
commit 982d292a96
5 changed files with 193 additions and 27 deletions

View file

@ -45,7 +45,21 @@ func GetSourceInfoV0(socket *http.Client, sourceNames string) (map[string]weldr.
if resp != nil || err != nil { if resp != nil || err != nil {
return nil, resp, err return nil, resp, err
} }
var info weldr.SourceInfoV0 var info weldr.SourceInfoResponseV0
err = json.Unmarshal(body, &info)
if err != nil {
return nil, nil, err
}
return info.Sources, nil, nil
}
// GetSourceInfoV1 returns detailed information on the named sources
func GetSourceInfoV1(socket *http.Client, sourceNames string) (map[string]weldr.SourceConfigV1, *APIResponse, error) {
body, resp, err := GetRaw(socket, "GET", "/api/v1/projects/source/info/"+sourceNames)
if resp != nil || err != nil {
return nil, resp, err
}
var info weldr.SourceInfoResponseV1
err = json.Unmarshal(body, &info) err = json.Unmarshal(body, &info)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

View file

@ -319,7 +319,7 @@ func TestListSourcesV1(t *testing.T) {
require.Contains(t, list, "package-repo-2") require.Contains(t, list, "package-repo-2")
} }
// Get the source info // Get the source info using the v0 API
func TestGetSourceInfoV0(t *testing.T) { func TestGetSourceInfoV0(t *testing.T) {
source := ` source := `
name = "package-repo-info-v0" name = "package-repo-info-v0"
@ -348,6 +348,37 @@ func TestGetSourceInfoV0(t *testing.T) {
require.True(t, resp.Status, "DELETE source failed: %#v", resp) require.True(t, resp.Status, "DELETE source failed: %#v", resp)
} }
// Get the source info using the v1 API
func TestGetSourceInfoV1(t *testing.T) {
source := `
id = "package-repo-info-v1"
name = "repo for info test v1"
url = "file://REPO-PATH"
type = "yum-baseurl"
proxy = "https://proxy-url/"
check_ssl = true
check_gpg = true
gpgkey_urls = ["https://url/path/to/gpg-key"]
`
source = strings.Replace(source, "REPO-PATH", testState.repoDir, 1)
resp, err := PostTOMLSourceV1(testState.socket, source)
require.NoError(t, err, "POST source failed with a client error")
require.True(t, resp.Status, "POST source failed: %#v", resp)
info, resp, err := GetSourceInfoV1(testState.socket, "package-repo-info-v1")
require.NoError(t, err, "GET source failed with a client error")
require.Nil(t, resp, "GET source failed: %#v", resp)
require.Contains(t, info, "package-repo-info-v1", "No source info returned")
require.Equal(t, "repo for info test v1", info["package-repo-info-v1"].Name)
require.Equal(t, "file://"+testState.repoDir, info["package-repo-info-v1"].URL)
// TODO update for DeleteJSONSourceV1
resp, err = DeleteSourceV0(testState.socket, "package-repo-info-v1")
require.NoError(t, err, "DELETE source failed with a client error")
require.True(t, resp.Status, "DELETE source failed: %#v", resp)
}
func UploadUserDefinedSourcesV0(t *testing.T, sources []string) { func UploadUserDefinedSourcesV0(t *testing.T, sources []string) {
for i := range sources { for i := range sources {
source := strings.Replace(sources[i], "REPO-PATH", testState.repoDir, 1) source := strings.Replace(sources[i], "REPO-PATH", testState.repoDir, 1)

View file

@ -464,7 +464,22 @@ func (s *Store) GetSource(name string) *SourceConfig {
return &source return &source
} }
func (s *Store) GetAllSources() map[string]SourceConfig { // GetAllSourcesByName returns the sources using the repo name as the key
func (s *Store) GetAllSourcesByName() map[string]SourceConfig {
s.mu.RLock()
defer s.mu.RUnlock()
sources := make(map[string]SourceConfig)
for _, v := range s.sources {
sources[v.Name] = v
}
return sources
}
// GetAllSourcesByID returns the sources using the repo id as the key
func (s *Store) GetAllSourcesByID() map[string]SourceConfig {
s.mu.RLock() s.mu.RLock()
defer s.mu.RUnlock() defer s.mu.RUnlock()
@ -499,10 +514,10 @@ func NewSourceConfig(repo rpmmd.RepoConfig, system bool) SourceConfig {
return sc return sc
} }
func (s *SourceConfig) RepoConfig() rpmmd.RepoConfig { func (s *SourceConfig) RepoConfig(name string) rpmmd.RepoConfig {
var repo rpmmd.RepoConfig var repo rpmmd.RepoConfig
repo.Name = s.Name repo.Name = name
repo.IgnoreSSL = !s.CheckSSL repo.IgnoreSSL = !s.CheckSSL
if s.Type == "yum-baseurl" { if s.Type == "yum-baseurl" {

View file

@ -380,18 +380,9 @@ func (api *API) sourceEmptyInfoHandler(writer http.ResponseWriter, request *http
statusResponseError(writer, http.StatusNotFound, errors) statusResponseError(writer, http.StatusNotFound, errors)
} }
func (api *API) sourceInfoHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { // getSourceConfigs retrieves the list of sources from the system repos an store
if !verifyRequestVersion(writer, params, 0) { // TODO: version 1 API // Returning a list of store.SourceConfig entries indexed by the id of the source
return func (api *API) getSourceConfigs(params httprouter.Params) (map[string]store.SourceConfig, []responseError) {
}
// weldr uses a slightly different format than dnf to store repository
// configuration
type reply struct {
Sources map[string]store.SourceConfig `json:"sources"`
Errors []responseError `json:"errors"`
}
names := params.ByName("sources") names := params.ByName("sources")
sources := map[string]store.SourceConfig{} sources := map[string]store.SourceConfig{}
@ -399,7 +390,7 @@ func (api *API) sourceInfoHandler(writer http.ResponseWriter, request *http.Requ
// if names is "*" we want all sources // if names is "*" we want all sources
if names == "*" { if names == "*" {
sources = api.store.GetAllSources() sources = api.store.GetAllSourcesByID()
for _, repo := range api.repos { for _, repo := range api.repos {
sources[repo.Name] = store.NewSourceConfig(repo, true) sources[repo.Name] = store.NewSourceConfig(repo, true)
} }
@ -419,7 +410,7 @@ func (api *API) sourceInfoHandler(writer http.ResponseWriter, request *http.Requ
} }
// check if the source is in the store // check if the source is in the store
if source := api.store.GetSource(name); source != nil { if source := api.store.GetSource(name); source != nil {
sources[source.Name] = *source sources[name] = *source
} else { } else {
error := responseError{ error := responseError{
ID: "UnknownSource", ID: "UnknownSource",
@ -430,6 +421,37 @@ func (api *API) sourceInfoHandler(writer http.ResponseWriter, request *http.Requ
} }
} }
return sources, errors
}
// sourceInfoHandler routes the call to the correct API version handler
func (api *API) sourceInfoHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
versionString := params.ByName("version")
version, err := strconv.ParseUint(versionString, 10, 0)
if err != nil {
notFoundHandler(writer, nil)
return
}
switch version {
case 0:
api.sourceInfoHandlerV0(writer, request, params)
case 1:
api.sourceInfoHandlerV1(writer, request, params)
default:
notFoundHandler(writer, nil)
}
}
// sourceInfoHandlerV0 handles the API v0 response
func (api *API) sourceInfoHandlerV0(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
sources, errors := api.getSourceConfigs(params)
// V0 responses use the source name as the key
v0Sources := make(map[string]SourceConfigV0, len(sources))
for _, s := range sources {
v0Sources[s.Name] = NewSourceConfigV0(s)
}
q, err := url.ParseQuery(request.URL.RawQuery) q, err := url.ParseQuery(request.URL.RawQuery)
if err != nil { if err != nil {
errors := responseError{ errors := responseError{
@ -442,8 +464,8 @@ func (api *API) sourceInfoHandler(writer http.ResponseWriter, request *http.Requ
format := q.Get("format") format := q.Get("format")
if format == "json" || format == "" { if format == "json" || format == "" {
err := json.NewEncoder(writer).Encode(reply{ err := json.NewEncoder(writer).Encode(SourceInfoResponseV0{
Sources: sources, Sources: v0Sources,
Errors: errors, Errors: errors,
}) })
common.PanicOnError(err) common.PanicOnError(err)
@ -458,8 +480,48 @@ func (api *API) sourceInfoHandler(writer http.ResponseWriter, request *http.Requ
Msg: fmt.Sprintf("invalid format parameter: %s", format), Msg: fmt.Sprintf("invalid format parameter: %s", format),
} }
statusResponseError(writer, http.StatusBadRequest, errors) statusResponseError(writer, http.StatusBadRequest, errors)
}
}
// sourceInfoHandlerV1 handles the API v0 response
func (api *API) sourceInfoHandlerV1(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
sources, errors := api.getSourceConfigs(params)
// V1 responses use the source id as the key
v1Sources := make(map[string]SourceConfigV1, len(sources))
for id, s := range sources {
v1Sources[id] = NewSourceConfigV1(id, s)
}
q, err := url.ParseQuery(request.URL.RawQuery)
if err != nil {
errors := responseError{
ID: "InvalidChars",
Msg: fmt.Sprintf("invalid query string: %v", err),
}
statusResponseError(writer, http.StatusBadRequest, errors)
return return
} }
format := q.Get("format")
if format == "json" || format == "" {
err := json.NewEncoder(writer).Encode(SourceInfoResponseV1{
Sources: v1Sources,
Errors: errors,
})
common.PanicOnError(err)
} else if format == "toml" {
encoder := toml.NewEncoder(writer)
encoder.Indent = ""
err := encoder.Encode(sources)
common.PanicOnError(err)
} else {
errors := responseError{
ID: "InvalidChars",
Msg: fmt.Sprintf("invalid format parameter: %s", format),
}
statusResponseError(writer, http.StatusBadRequest, errors)
}
} }
// DecodeSourceConfigV0 parses a request.Body into a SourceConfigV0 // DecodeSourceConfigV0 parses a request.Body into a SourceConfigV0
@ -2391,8 +2453,8 @@ func getPkgNameGlob(pkg blueprint.Package) string {
// Returns all configured repositories (base + sources) as rpmmd.RepoConfig // Returns all configured repositories (base + sources) as rpmmd.RepoConfig
func (api *API) allRepositories() []rpmmd.RepoConfig { func (api *API) allRepositories() []rpmmd.RepoConfig {
repos := append([]rpmmd.RepoConfig{}, api.repos...) repos := append([]rpmmd.RepoConfig{}, api.repos...)
for _, source := range api.store.GetAllSources() { for id, source := range api.store.GetAllSourcesByID() {
repos = append(repos, source.RepoConfig()) repos = append(repos, source.RepoConfig(id))
} }
return repos return repos
} }

View file

@ -106,6 +106,7 @@ func (s *SourceInfoV0) SourceConfig(sourceName string) (ssc store.SourceConfig,
return si.SourceConfig(), true return si.SourceConfig(), true
} }
// SourceConfig interface defines the common functions needed to query the SourceConfigV0/V1 structs
type SourceConfig interface { type SourceConfig interface {
GetKey() string GetKey() string
GetName() string GetName() string
@ -113,6 +114,21 @@ type SourceConfig interface {
SourceConfig() store.SourceConfig SourceConfig() store.SourceConfig
} }
// NewSourceConfigV0 converts a store.SourceConfig to a SourceConfigV0
// The store does not support proxy and gpgkey_urls
func NewSourceConfigV0(s store.SourceConfig) SourceConfigV0 {
var sc SourceConfigV0
sc.Name = s.Name
sc.Type = s.Type
sc.URL = s.URL
sc.CheckGPG = s.CheckGPG
sc.CheckSSL = s.CheckSSL
sc.System = s.System
return sc
}
// SourceConfigV0 holds the source repository information // SourceConfigV0 holds the source repository information
type SourceConfigV0 struct { type SourceConfigV0 struct {
Name string `json:"name" toml:"name"` Name string `json:"name" toml:"name"`
@ -121,8 +137,8 @@ type SourceConfigV0 struct {
CheckGPG bool `json:"check_gpg" toml:"check_gpg"` CheckGPG bool `json:"check_gpg" toml:"check_gpg"`
CheckSSL bool `json:"check_ssl" toml:"check_ssl"` CheckSSL bool `json:"check_ssl" toml:"check_ssl"`
System bool `json:"system" toml:"system"` System bool `json:"system" toml:"system"`
Proxy string `json:"proxy" toml:"proxy"` Proxy string `json:"proxy,omitempty" toml:"proxy,omitempty"`
GPGUrls []string `json:"gpgkey_urls" toml:"gpgkey_urls"` GPGUrls []string `json:"gpgkey_urls,omitempty" toml:"gpgkey_urls,omitempty"`
} }
// Key return the key, .Name in this case // Key return the key, .Name in this case
@ -152,6 +168,28 @@ func (s SourceConfigV0) SourceConfig() (ssc store.SourceConfig) {
return ssc return ssc
} }
// SourceInfoResponseV0
type SourceInfoResponseV0 struct {
Sources map[string]SourceConfigV0 `json:"sources"`
Errors []responseError `json:"errors"`
}
// NewSourceConfigV1 converts a store.SourceConfig to a SourceConfigV1
// The store does not support proxy and gpgkey_urls
func NewSourceConfigV1(id string, s store.SourceConfig) SourceConfigV1 {
var sc SourceConfigV1
sc.ID = id
sc.Name = s.Name
sc.Type = s.Type
sc.URL = s.URL
sc.CheckGPG = s.CheckGPG
sc.CheckSSL = s.CheckSSL
sc.System = s.System
return sc
}
// SourceConfigV1 holds the source repository information // SourceConfigV1 holds the source repository information
type SourceConfigV1 struct { type SourceConfigV1 struct {
ID string `json:"id" toml:"id"` ID string `json:"id" toml:"id"`
@ -161,8 +199,8 @@ type SourceConfigV1 struct {
CheckGPG bool `json:"check_gpg" toml:"check_gpg"` CheckGPG bool `json:"check_gpg" toml:"check_gpg"`
CheckSSL bool `json:"check_ssl" toml:"check_ssl"` CheckSSL bool `json:"check_ssl" toml:"check_ssl"`
System bool `json:"system" toml:"system"` System bool `json:"system" toml:"system"`
Proxy string `json:"proxy" toml:"proxy"` Proxy string `json:"proxy,omitempty" toml:"proxy,omitempty"`
GPGUrls []string `json:"gpgkey_urls" toml:"gpgkey_urls"` GPGUrls []string `json:"gpgkey_urls,omitempty" toml:"gpgkey_urls,omitempty"`
} }
// Key returns the key, .ID in this case // Key returns the key, .ID in this case
@ -192,6 +230,12 @@ func (s SourceConfigV1) SourceConfig() (ssc store.SourceConfig) {
return ssc return ssc
} }
// SourceInfoResponseV1
type SourceInfoResponseV1 struct {
Sources map[string]SourceConfigV1 `json:"sources"`
Errors []responseError `json:"errors"`
}
// ProjectsListV0 is the response to /projects/list request // ProjectsListV0 is the response to /projects/list request
type ProjectsListV0 struct { type ProjectsListV0 struct {
Total uint `json:"total"` Total uint `json:"total"`