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 {
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)
if err != nil {
return nil, nil, err

View file

@ -319,7 +319,7 @@ func TestListSourcesV1(t *testing.T) {
require.Contains(t, list, "package-repo-2")
}
// Get the source info
// Get the source info using the v0 API
func TestGetSourceInfoV0(t *testing.T) {
source := `
name = "package-repo-info-v0"
@ -348,6 +348,37 @@ func TestGetSourceInfoV0(t *testing.T) {
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) {
for i := range sources {
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
}
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()
defer s.mu.RUnlock()
@ -499,10 +514,10 @@ func NewSourceConfig(repo rpmmd.RepoConfig, system bool) SourceConfig {
return sc
}
func (s *SourceConfig) RepoConfig() rpmmd.RepoConfig {
func (s *SourceConfig) RepoConfig(name string) rpmmd.RepoConfig {
var repo rpmmd.RepoConfig
repo.Name = s.Name
repo.Name = name
repo.IgnoreSSL = !s.CheckSSL
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)
}
func (api *API) sourceInfoHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
if !verifyRequestVersion(writer, params, 0) { // TODO: version 1 API
return
}
// 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"`
}
// getSourceConfigs retrieves the list of sources from the system repos an store
// Returning a list of store.SourceConfig entries indexed by the id of the source
func (api *API) getSourceConfigs(params httprouter.Params) (map[string]store.SourceConfig, []responseError) {
names := params.ByName("sources")
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 == "*" {
sources = api.store.GetAllSources()
sources = api.store.GetAllSourcesByID()
for _, repo := range api.repos {
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
if source := api.store.GetSource(name); source != nil {
sources[source.Name] = *source
sources[name] = *source
} else {
error := responseError{
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)
if err != nil {
errors := responseError{
@ -442,8 +464,8 @@ func (api *API) sourceInfoHandler(writer http.ResponseWriter, request *http.Requ
format := q.Get("format")
if format == "json" || format == "" {
err := json.NewEncoder(writer).Encode(reply{
Sources: sources,
err := json.NewEncoder(writer).Encode(SourceInfoResponseV0{
Sources: v0Sources,
Errors: errors,
})
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),
}
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
}
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
@ -2391,8 +2453,8 @@ func getPkgNameGlob(pkg blueprint.Package) string {
// Returns all configured repositories (base + sources) as rpmmd.RepoConfig
func (api *API) allRepositories() []rpmmd.RepoConfig {
repos := append([]rpmmd.RepoConfig{}, api.repos...)
for _, source := range api.store.GetAllSources() {
repos = append(repos, source.RepoConfig())
for id, source := range api.store.GetAllSourcesByID() {
repos = append(repos, source.RepoConfig(id))
}
return repos
}

View file

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