For now we will hardcode the org.osbuild.local target, so we might as well fix this up front. We do not yet support any target types, but for testing purposes we claim to support 'tar', and we pass a noop tar pipeline to the worker. This makes introspecting the job-queu api using curl a bit more pleasant. Signed-off-by: Tom Gundersen <teg@jklm.no>
688 lines
20 KiB
Go
688 lines
20 KiB
Go
package weldr
|
||
|
||
import (
|
||
"encoding/json"
|
||
"log"
|
||
"net"
|
||
"net/http"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/julienschmidt/httprouter"
|
||
|
||
"osbuild-composer/internal/job"
|
||
"osbuild-composer/internal/rpmmd"
|
||
"osbuild-composer/internal/target"
|
||
)
|
||
|
||
type API struct {
|
||
store *store
|
||
pendingJobs chan<- job.Job
|
||
|
||
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) *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),
|
||
pendingJobs: jobs,
|
||
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"`
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
bp := blueprint{}
|
||
changed := false
|
||
found := api.store.getBlueprint(cr.BlueprintName, &bp, &changed) // TODO: what to do with changed?
|
||
|
||
uuid := "ffffffff-ffff–ffff-ffff-ffffffffffff" // TODO: generate
|
||
|
||
if found {
|
||
api.pendingJobs <- job.Job{
|
||
ComposeID: uuid,
|
||
Pipeline: bp.translateToPipeline(cr.ComposeType),
|
||
Targets: []target.Target{{
|
||
Name: "org.osbuild.local",
|
||
Options: target.LocalOptions{
|
||
Location: "/var/lib/osbuild-composer/" + uuid,
|
||
}},
|
||
},
|
||
}
|
||
} else {
|
||
statusResponseError(writer, http.StatusBadRequest, "blueprint does not exist")
|
||
return
|
||
}
|
||
|
||
statusResponseOK(writer)
|
||
}
|
||
|
||
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)
|
||
}
|