Go doesn't really do variants, so we must somehow emulate it. The json objects we use are essentially tagged unions, with a `name` field in reverse domain name notation identifying the type and a type specific 'options' object. In Go we represent this by having an BarOptions interface, which implements a private method `isBarOptions()`, making sure that only types in the same package are able to implement it. Each type FooBar that should belong to the variant implements the interface, and a constructor `NewFooBar(options *FooBarOptions) *Bar` that makes sure the `name` field is set correctly. This would be enough to represent our types and marshal them into JSON, but unmarshalling would not work (json does not know about our tags, so would not know what concrete types to demarshal to). We therefore must also implement the Unmarshall interface for Bar, to select the right types for the Options field. We implement his logic for Target, Stage and Assembler. A handful of concrete types are also implemented, matching what osbuild supports. Signed-off-by: Tom Gundersen <teg@jklm.no>
684 lines
20 KiB
Go
684 lines
20 KiB
Go
package weldr
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
"osbuild-composer/internal/job"
|
|
"osbuild-composer/internal/rpmmd"
|
|
)
|
|
|
|
type API struct {
|
|
store *store
|
|
|
|
repo rpmmd.RepoConfig
|
|
packages rpmmd.PackageList
|
|
|
|
logger *log.Logger
|
|
router *httprouter.Router
|
|
}
|
|
|
|
func New(repo rpmmd.RepoConfig, packages rpmmd.PackageList, logger *log.Logger, initialState []byte, stateChannel chan<- []byte, jobs chan<- job.Job, jobStatus <-chan job.Status) *API {
|
|
// This needs to be shared with the worker API so that they can communicate with each other
|
|
// builds := make(chan queue.Build, 200)
|
|
api := &API{
|
|
store: newStore(initialState, stateChannel, jobs, jobStatus),
|
|
repo: repo,
|
|
packages: packages,
|
|
logger: logger,
|
|
}
|
|
|
|
// sample blueprint on first run
|
|
if initialState == nil {
|
|
api.store.pushBlueprint(blueprint{
|
|
Name: "example",
|
|
Description: "An Example",
|
|
Version: "1",
|
|
Packages: []blueprintPackage{{"httpd", "2.*"}},
|
|
Modules: []blueprintPackage{},
|
|
})
|
|
}
|
|
|
|
api.router = httprouter.New()
|
|
api.router.RedirectTrailingSlash = false
|
|
api.router.RedirectFixedPath = false
|
|
api.router.MethodNotAllowed = http.HandlerFunc(methodNotAllowedHandler)
|
|
api.router.NotFound = http.HandlerFunc(notFoundHandler)
|
|
|
|
api.router.GET("/api/status", api.statusHandler)
|
|
api.router.GET("/api/v0/projects/source/list", api.sourceListHandler)
|
|
api.router.GET("/api/v0/projects/source/info/:sources", api.sourceInfoHandler)
|
|
|
|
api.router.GET("/api/v0/modules/list", api.modulesListAllHandler)
|
|
api.router.GET("/api/v0/modules/list/:modules", api.modulesListHandler)
|
|
|
|
// these are the same, except that modules/info also includes dependencies
|
|
api.router.GET("/api/v0/modules/info/:modules", api.modulesInfoHandler)
|
|
api.router.GET("/api/v0/projects/info/:modules", api.modulesInfoHandler)
|
|
|
|
api.router.GET("/api/v0/blueprints/list", api.blueprintsListHandler)
|
|
api.router.GET("/api/v0/blueprints/info/:blueprints", api.blueprintsInfoHandler)
|
|
api.router.GET("/api/v0/blueprints/depsolve/:blueprints", api.blueprintsDepsolveHandler)
|
|
api.router.GET("/api/v0/blueprints/diff/:blueprint/:from/:to", api.blueprintsDiffHandler)
|
|
api.router.POST("/api/v0/blueprints/new", api.blueprintsNewHandler)
|
|
api.router.POST("/api/v0/blueprints/workspace", api.blueprintsWorkspaceHandler)
|
|
api.router.DELETE("/api/v0/blueprints/delete/:blueprint", api.blueprintDeleteHandler)
|
|
api.router.DELETE("/api/v0/blueprints/workspace/:blueprint", api.blueprintDeleteWorkspaceHandler)
|
|
|
|
api.router.POST("/api/v0/compose", api.composeHandler)
|
|
api.router.GET("/api/v0/compose/types", api.composeTypesHandler)
|
|
api.router.GET("/api/v0/compose/queue", api.composeQueueHandler)
|
|
api.router.GET("/api/v0/compose/finished", api.composeFinishedHandler)
|
|
api.router.GET("/api/v0/compose/failed", api.composeFailedHandler)
|
|
|
|
return api
|
|
}
|
|
|
|
func (api *API) Serve(listener net.Listener) error {
|
|
server := http.Server{Handler: api}
|
|
|
|
err := server.Serve(listener)
|
|
if err != nil && err != http.ErrServerClosed {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (api *API) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|
if api.logger != nil {
|
|
log.Println(request.Method, request.URL.Path)
|
|
}
|
|
|
|
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
api.router.ServeHTTP(writer, request)
|
|
}
|
|
|
|
func methodNotAllowedHandler(writer http.ResponseWriter, request *http.Request) {
|
|
writer.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
|
|
func notFoundHandler(writer http.ResponseWriter, request *http.Request) {
|
|
writer.WriteHeader(http.StatusNotFound)
|
|
}
|
|
|
|
func statusResponseOK(writer http.ResponseWriter) {
|
|
type reply struct {
|
|
Status bool `json:"status"`
|
|
}
|
|
|
|
writer.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(writer).Encode(reply{true})
|
|
}
|
|
|
|
func statusResponseError(writer http.ResponseWriter, code int, errors ...string) {
|
|
type reply struct {
|
|
Status bool `json:"status"`
|
|
Errors []string `json:"errors,omitempty"`
|
|
}
|
|
|
|
writer.WriteHeader(code)
|
|
json.NewEncoder(writer).Encode(reply{false, errors})
|
|
}
|
|
|
|
func (api *API) statusHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
|
|
type reply struct {
|
|
API uint `json:"api"`
|
|
DBSupported bool `json:"db_supported"`
|
|
DBVersion string `json:"db_version"`
|
|
SchemaVersion string `json:"schema_version"`
|
|
Backend string `json:"backend"`
|
|
Build string `json:"build"`
|
|
Messages []string `json:"messages"`
|
|
}
|
|
|
|
json.NewEncoder(writer).Encode(reply{
|
|
API: 1,
|
|
DBSupported: true,
|
|
DBVersion: "0",
|
|
SchemaVersion: "0",
|
|
Backend: "osbuild-composer",
|
|
Build: "devel",
|
|
Messages: make([]string, 0),
|
|
})
|
|
}
|
|
|
|
func (api *API) sourceListHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
|
|
type reply struct {
|
|
Sources []string `json:"sources"`
|
|
}
|
|
|
|
json.NewEncoder(writer).Encode(reply{
|
|
Sources: []string{api.repo.Id},
|
|
})
|
|
}
|
|
|
|
func (api *API) sourceInfoHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
|
// weldr uses a slightly different format than dnf to store repository
|
|
// configuration
|
|
type sourceConfig struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
URL string `json:"url"`
|
|
CheckGPG bool `json:"check_gpg"`
|
|
CheckSSL bool `json:"check_ssl"`
|
|
System bool `json:"system"`
|
|
}
|
|
type reply struct {
|
|
Sources map[string]sourceConfig `json:"sources"`
|
|
}
|
|
|
|
// we only have one repository
|
|
names := strings.Split(params.ByName("sources"), ",")
|
|
if names[0] != api.repo.Id && names[0] != "*" {
|
|
statusResponseError(writer, http.StatusBadRequest, "repository not found: "+names[0])
|
|
return
|
|
}
|
|
|
|
cfg := sourceConfig{
|
|
ID: api.repo.Id,
|
|
Name: api.repo.Name,
|
|
CheckGPG: true,
|
|
CheckSSL: true,
|
|
System: true,
|
|
}
|
|
|
|
if api.repo.BaseURL != "" {
|
|
cfg.URL = api.repo.BaseURL
|
|
cfg.Type = "yum-baseurl"
|
|
} else if api.repo.Metalink != "" {
|
|
cfg.URL = api.repo.Metalink
|
|
cfg.Type = "yum-metalink"
|
|
} else if api.repo.MirrorList != "" {
|
|
cfg.URL = api.repo.MirrorList
|
|
cfg.Type = "yum-mirrorlist"
|
|
}
|
|
|
|
json.NewEncoder(writer).Encode(reply{
|
|
Sources: map[string]sourceConfig{cfg.ID: cfg},
|
|
})
|
|
}
|
|
|
|
type modulesListModule struct {
|
|
Name string `json:"name"`
|
|
GroupType string `json:"group_type"`
|
|
}
|
|
|
|
type modulesListReply struct {
|
|
Total uint `json:"total"`
|
|
Offset uint `json:"offset"`
|
|
Limit uint `json:"limit"`
|
|
Modules []modulesListModule `json:"modules"`
|
|
}
|
|
|
|
func (api *API) modulesListAllHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
|
|
offset, limit, err := parseOffsetAndLimit(request.URL.Query())
|
|
if err != nil {
|
|
statusResponseError(writer, http.StatusBadRequest, "BadRequest: "+err.Error())
|
|
return
|
|
}
|
|
|
|
total := uint(len(api.packages))
|
|
start := min(offset, total)
|
|
n := min(limit, total-start)
|
|
|
|
modules := make([]modulesListModule, n)
|
|
for i := uint(0); i < n; i++ {
|
|
modules[i] = modulesListModule{api.packages[start+i].Name, "rpm"}
|
|
}
|
|
|
|
json.NewEncoder(writer).Encode(modulesListReply{
|
|
Total: total,
|
|
Offset: offset,
|
|
Limit: limit,
|
|
Modules: modules,
|
|
})
|
|
}
|
|
|
|
func (api *API) modulesListHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
|
names := strings.Split(params.ByName("modules"), ",")
|
|
if names[0] == "" || names[0] == "*" {
|
|
api.modulesListAllHandler(writer, request, params)
|
|
return
|
|
}
|
|
|
|
offset, limit, err := parseOffsetAndLimit(request.URL.Query())
|
|
if err != nil {
|
|
statusResponseError(writer, http.StatusBadRequest, "BadRequest: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// we don't support glob-matching, but cockpit-composer surrounds some
|
|
// queries with asterisks; this is crude, but solves that case
|
|
for i := range names {
|
|
names[i] = strings.ReplaceAll(names[i], "*", "")
|
|
}
|
|
|
|
modules := make([]modulesListModule, 0)
|
|
total := uint(0)
|
|
end := offset + limit
|
|
|
|
for _, pkg := range api.packages {
|
|
for _, name := range names {
|
|
if strings.Contains(pkg.Name, name) {
|
|
total++
|
|
if total > offset && total < end {
|
|
modules = append(modules, modulesListModule{pkg.Name, "rpm"})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
json.NewEncoder(writer).Encode(modulesListReply{
|
|
Total: total,
|
|
Offset: offset,
|
|
Limit: limit,
|
|
Modules: modules,
|
|
})
|
|
}
|
|
|
|
func (api *API) modulesInfoHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
|
type source struct {
|
|
License string `json:"license"`
|
|
Version string `json:"version"`
|
|
}
|
|
type build struct {
|
|
Arch string `json:"arch"`
|
|
BuildTime time.Time `json:"build_time"`
|
|
Epoch uint `json:"epoch"`
|
|
Release string `json:"release"`
|
|
Source source `json:"source"`
|
|
}
|
|
type project struct {
|
|
Name string `json:"name"`
|
|
Summary string `json:"summary"`
|
|
Description string `json:"description"`
|
|
Homepage string `json:"homepage"`
|
|
Builds []build `json:"builds"`
|
|
Dependencies []rpmmd.PackageSpec `json:"dependencies,omitempty"`
|
|
}
|
|
type projectsReply struct {
|
|
Projects []project `json:"projects"`
|
|
}
|
|
type modulesReply struct {
|
|
Modules []project `json:"modules"`
|
|
}
|
|
|
|
// handle both projects/info and modules/info, the latter includes dependencies
|
|
modulesRequested := strings.HasPrefix(request.URL.Path, "/api/v0/modules")
|
|
|
|
names := strings.Split(params.ByName("modules"), ",")
|
|
if names[0] == "" {
|
|
statusResponseError(writer, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
projects := make([]project, 0)
|
|
for _, name := range names {
|
|
first, n := api.packages.Search(name)
|
|
if n == 0 {
|
|
statusResponseError(writer, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// get basic info from the first package, but collect build
|
|
// information from all that have the same name
|
|
pkg := api.packages[first]
|
|
project := project{
|
|
Name: pkg.Name,
|
|
Summary: pkg.Summary,
|
|
Description: pkg.Description,
|
|
Homepage: pkg.URL,
|
|
}
|
|
|
|
project.Builds = make([]build, n)
|
|
for i, pkg := range api.packages[first : first+n] {
|
|
project.Builds[i] = build{
|
|
Arch: pkg.Arch,
|
|
BuildTime: pkg.BuildTime,
|
|
Epoch: pkg.Epoch,
|
|
Release: pkg.Release,
|
|
Source: source{pkg.License, pkg.Version},
|
|
}
|
|
}
|
|
|
|
if modulesRequested {
|
|
project.Dependencies, _ = rpmmd.Depsolve(pkg.Name)
|
|
}
|
|
|
|
projects = append(projects, project)
|
|
}
|
|
|
|
if modulesRequested {
|
|
json.NewEncoder(writer).Encode(modulesReply{projects})
|
|
} else {
|
|
json.NewEncoder(writer).Encode(projectsReply{projects})
|
|
}
|
|
}
|
|
|
|
func (api *API) blueprintsListHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
|
|
type reply struct {
|
|
Total uint `json:"total"`
|
|
Offset uint `json:"offset"`
|
|
Limit uint `json:"limit"`
|
|
Blueprints []string `json:"blueprints"`
|
|
}
|
|
|
|
offset, limit, err := parseOffsetAndLimit(request.URL.Query())
|
|
if err != nil {
|
|
statusResponseError(writer, http.StatusBadRequest, "BadRequest: "+err.Error())
|
|
return
|
|
}
|
|
|
|
names := api.store.listBlueprints()
|
|
total := uint(len(names))
|
|
offset = min(offset, total)
|
|
limit = min(limit, total-offset)
|
|
|
|
json.NewEncoder(writer).Encode(reply{
|
|
Total: total,
|
|
Offset: offset,
|
|
Limit: limit,
|
|
Blueprints: names[offset : offset+limit],
|
|
})
|
|
}
|
|
|
|
func (api *API) blueprintsInfoHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
|
type change struct {
|
|
Changed bool `json:"changed"`
|
|
Name string `json:"name"`
|
|
}
|
|
type reply struct {
|
|
Blueprints []blueprint `json:"blueprints"`
|
|
Changes []change `json:"changes"`
|
|
Errors []string `json:"errors"`
|
|
}
|
|
|
|
names := strings.Split(params.ByName("blueprints"), ",")
|
|
if names[0] == "" {
|
|
statusResponseError(writer, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
blueprints := []blueprint{}
|
|
changes := []change{}
|
|
for _, name := range names {
|
|
var blueprint blueprint
|
|
var changed bool
|
|
if !api.store.getBlueprint(name, &blueprint, &changed) {
|
|
statusResponseError(writer, http.StatusNotFound)
|
|
return
|
|
}
|
|
blueprints = append(blueprints, blueprint)
|
|
changes = append(changes, change{changed, blueprint.Name})
|
|
}
|
|
|
|
json.NewEncoder(writer).Encode(reply{
|
|
Blueprints: blueprints,
|
|
Changes: changes,
|
|
Errors: []string{},
|
|
})
|
|
}
|
|
|
|
func (api *API) blueprintsDepsolveHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
|
type entry struct {
|
|
Blueprint blueprint `json:"blueprint"`
|
|
Dependencies []rpmmd.PackageSpec `json:"dependencies"`
|
|
}
|
|
type reply struct {
|
|
Blueprints []entry `json:"blueprints"`
|
|
Errors []string `json:"errors"`
|
|
}
|
|
|
|
names := strings.Split(params.ByName("blueprints"), ",")
|
|
if names[0] == "" {
|
|
statusResponseError(writer, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
blueprints := []entry{}
|
|
for _, name := range names {
|
|
var blueprint blueprint
|
|
if !api.store.getBlueprint(name, &blueprint, nil) {
|
|
statusResponseError(writer, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
specs := make([]string, len(blueprint.Packages))
|
|
for i, pkg := range blueprint.Packages {
|
|
specs[i] = pkg.Name
|
|
// If a package has version "*" the package name suffix must be equal to "-*-*.*"
|
|
// Using just "-*" would find any other package containing the package name
|
|
if pkg.Version != "" && pkg.Version != "*" {
|
|
specs[i] += "-" + pkg.Version
|
|
} else if pkg.Version == "*" {
|
|
specs[i] += "-*-*.*"
|
|
}
|
|
}
|
|
dependencies, _ := rpmmd.Depsolve(specs...)
|
|
|
|
blueprints = append(blueprints, entry{blueprint, dependencies})
|
|
}
|
|
|
|
json.NewEncoder(writer).Encode(reply{
|
|
Blueprints: blueprints,
|
|
Errors: []string{},
|
|
})
|
|
}
|
|
|
|
func (api *API) blueprintsDiffHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
|
type pack struct {
|
|
Package blueprintPackage `json:"Package"`
|
|
}
|
|
|
|
type diff struct {
|
|
New *pack `json:"new"`
|
|
Old *pack `json:"old"`
|
|
}
|
|
|
|
type reply struct {
|
|
Diffs []diff `json:"diff"`
|
|
}
|
|
|
|
name := params.ByName("blueprint")
|
|
if len(name) == 0 {
|
|
statusResponseError(writer, http.StatusNotFound, "no blueprint name given")
|
|
return
|
|
}
|
|
fromCommit := params.ByName("from")
|
|
if len(fromCommit) == 0 || fromCommit != "NEWEST" {
|
|
statusResponseError(writer, http.StatusNotFound, "invalid from commit ID given")
|
|
return
|
|
}
|
|
toCommit := params.ByName("to")
|
|
if len(toCommit) == 0 || toCommit != "WORKSPACE" {
|
|
statusResponseError(writer, http.StatusNotFound, "invalid to commit ID given")
|
|
return
|
|
}
|
|
|
|
// Fetch old and new blueprint details from store and return error if not found
|
|
var oldBlueprint, newBlueprint blueprint
|
|
if !api.store.getBlueprintCommitted(name, &oldBlueprint) || !api.store.getBlueprint(name, &newBlueprint, nil) {
|
|
statusResponseError(writer, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
newSlice := newBlueprint.Packages
|
|
oldMap := make(map[string]blueprintPackage)
|
|
diffs := []diff{}
|
|
|
|
for _, oldPackage := range oldBlueprint.Packages {
|
|
oldMap[oldPackage.Name] = oldPackage
|
|
}
|
|
|
|
// For each package in new blueprint check if the old one contains it
|
|
for _, newPackage := range newSlice {
|
|
oldPackage, found := oldMap[newPackage.Name]
|
|
// If found remove from old packages map but otherwise create a diff with the added package
|
|
if found {
|
|
delete(oldMap, oldPackage.Name)
|
|
// Create a diff if the versions changed
|
|
if oldPackage.Version != newPackage.Version {
|
|
diffs = append(diffs, diff{Old: &pack{oldPackage}, New: &pack{newPackage}})
|
|
}
|
|
} else {
|
|
diffs = append(diffs, diff{Old: nil, New: &pack{newPackage}})
|
|
}
|
|
}
|
|
|
|
// All packages remaining in the old packages map have been removed in the new blueprint so create a diff
|
|
for _, oldPackage := range oldMap {
|
|
diffs = append(diffs, diff{Old: &pack{oldPackage}, New: nil})
|
|
}
|
|
|
|
json.NewEncoder(writer).Encode(reply{diffs})
|
|
}
|
|
|
|
func (api *API) blueprintsNewHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
|
|
contentType := request.Header["Content-Type"]
|
|
if len(contentType) != 1 || contentType[0] != "application/json" {
|
|
statusResponseError(writer, http.StatusUnsupportedMediaType, "blueprint must be json")
|
|
return
|
|
}
|
|
|
|
var blueprint blueprint
|
|
err := json.NewDecoder(request.Body).Decode(&blueprint)
|
|
if err != nil {
|
|
statusResponseError(writer, http.StatusBadRequest, "invalid blueprint: "+err.Error())
|
|
return
|
|
}
|
|
|
|
api.store.pushBlueprint(blueprint)
|
|
|
|
statusResponseOK(writer)
|
|
}
|
|
|
|
func (api *API) blueprintsWorkspaceHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
|
|
contentType := request.Header["Content-Type"]
|
|
if len(contentType) != 1 || contentType[0] != "application/json" {
|
|
statusResponseError(writer, http.StatusUnsupportedMediaType, "blueprint must be json")
|
|
return
|
|
}
|
|
|
|
var blueprint blueprint
|
|
err := json.NewDecoder(request.Body).Decode(&blueprint)
|
|
if err != nil {
|
|
statusResponseError(writer, http.StatusBadRequest, "invalid blueprint: "+err.Error())
|
|
return
|
|
}
|
|
|
|
api.store.pushBlueprintToWorkspace(blueprint)
|
|
|
|
statusResponseOK(writer)
|
|
}
|
|
|
|
func (api *API) blueprintDeleteHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
|
api.store.deleteBlueprint(params.ByName("blueprint"))
|
|
statusResponseOK(writer)
|
|
}
|
|
|
|
func (api *API) blueprintDeleteWorkspaceHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
|
api.store.deleteBlueprintFromWorkspace(params.ByName("blueprint"))
|
|
statusResponseOK(writer)
|
|
}
|
|
|
|
// Schedule new compose by first translating the appropriate blueprint into a pipeline and then
|
|
// pushing it into the channel for waiting builds.
|
|
func (api *API) composeHandler(writer http.ResponseWriter, httpRequest *http.Request, _ httprouter.Params) {
|
|
// https://weldr.io/lorax/pylorax.api.html#pylorax.api.v0.v0_compose_start
|
|
type ComposeRequest struct {
|
|
BlueprintName string `json:"blueprint_name"`
|
|
ComposeType string `json:"compose_type"`
|
|
Branch string `json:"branch"`
|
|
}
|
|
type ComposeReply struct {
|
|
BuildID uuid.UUID `json:"build_id"`
|
|
Status bool `json:"status"`
|
|
}
|
|
|
|
contentType := httpRequest.Header["Content-Type"]
|
|
if len(contentType) != 1 || contentType[0] != "application/json" {
|
|
statusResponseError(writer, http.StatusUnsupportedMediaType, "blueprint must be json")
|
|
return
|
|
}
|
|
|
|
var cr ComposeRequest
|
|
err := json.NewDecoder(httpRequest.Body).Decode(&cr)
|
|
if err != nil {
|
|
statusResponseError(writer, http.StatusBadRequest, "invalid request format: "+err.Error())
|
|
return
|
|
}
|
|
|
|
reply := ComposeReply{
|
|
BuildID: uuid.New(),
|
|
Status: true,
|
|
}
|
|
|
|
bp := blueprint{}
|
|
changed := false
|
|
found := api.store.getBlueprint(cr.BlueprintName, &bp, &changed) // TODO: what to do with changed?
|
|
|
|
if found {
|
|
api.store.addCompose(reply.BuildID, &bp, cr.ComposeType)
|
|
} else {
|
|
statusResponseError(writer, http.StatusBadRequest, "blueprint does not exist")
|
|
return
|
|
}
|
|
|
|
json.NewEncoder(writer).Encode(reply)
|
|
}
|
|
|
|
func (api *API) composeTypesHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
|
|
type composeType struct {
|
|
Name string `json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
var reply struct {
|
|
Types []composeType `json:"types"`
|
|
}
|
|
|
|
reply.Types = append(reply.Types, composeType{"tar", true})
|
|
|
|
json.NewEncoder(writer).Encode(reply)
|
|
}
|
|
|
|
func (api *API) composeQueueHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
|
|
var reply struct {
|
|
New []interface{} `json:"new"`
|
|
Run []interface{} `json:"run"`
|
|
}
|
|
|
|
reply.New = make([]interface{}, 0)
|
|
reply.Run = make([]interface{}, 0)
|
|
|
|
json.NewEncoder(writer).Encode(reply)
|
|
}
|
|
|
|
func (api *API) composeFinishedHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
|
|
var reply struct {
|
|
Finished []interface{} `json:"finished"`
|
|
}
|
|
|
|
reply.Finished = make([]interface{}, 0)
|
|
|
|
json.NewEncoder(writer).Encode(reply)
|
|
}
|
|
|
|
func (api *API) composeFailedHandler(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
|
|
var reply struct {
|
|
Failed []interface{} `json:"failed"`
|
|
}
|
|
|
|
reply.Failed = make([]interface{}, 0)
|
|
|
|
json.NewEncoder(writer).Encode(reply)
|
|
}
|