diff --git a/cmd/osbuild-composer/composer.go b/cmd/osbuild-composer/composer.go index 359dbf5a1..1e8c7bdc0 100644 --- a/cmd/osbuild-composer/composer.go +++ b/cmd/osbuild-composer/composer.go @@ -87,7 +87,7 @@ func NewComposer(config *ComposerConfigFile, stateDir, cacheDir string) (*Compos } } - c.workers = worker.NewServer(c.logger, jobs, artifactsDir) + c.workers = worker.NewServer(c.logger, jobs, artifactsDir, config.Worker.BasePath) return &c, nil } diff --git a/cmd/osbuild-composer/config.go b/cmd/osbuild-composer/config.go index 899389f0d..c8e779b9c 100644 --- a/cmd/osbuild-composer/config.go +++ b/cmd/osbuild-composer/config.go @@ -35,6 +35,7 @@ type AWSConfig struct { type WorkerAPIConfig struct { AllowedDomains []string `toml:"allowed_domains"` CA string `toml:"ca"` + BasePath string `toml:"base_path"` PGHost string `toml:"pg_host" env:"PGHOST"` PGPort string `toml:"pg_port" env:"PGPORT"` PGDatabase string `toml:"pg_database" env:"PGDATABASE"` @@ -85,6 +86,7 @@ func GetDefaultConfig() *ComposerConfigFile { }, }, Worker: WorkerAPIConfig{ + BasePath: "/api/worker/v1", EnableTLS: true, EnableMTLS: true, EnableJWT: false, diff --git a/cmd/osbuild-composer/config_test.go b/cmd/osbuild-composer/config_test.go index b204766d0..67343242e 100644 --- a/cmd/osbuild-composer/config_test.go +++ b/cmd/osbuild-composer/config_test.go @@ -39,6 +39,7 @@ func TestDefaultConfig(t *testing.T) { }, defaultConfig.Koji) require.Equal(t, WorkerAPIConfig{ + BasePath: "/api/worker/v1", EnableTLS: true, EnableMTLS: true, EnableJWT: false, diff --git a/cmd/osbuild-worker/main.go b/cmd/osbuild-worker/main.go index 72ebed475..f8451b942 100644 --- a/cmd/osbuild-worker/main.go +++ b/cmd/osbuild-worker/main.go @@ -97,6 +97,7 @@ func main() { OAuthURL string `toml:"oauth_url"` OfflineTokenPath string `toml:"offline_token"` } `toml:"authentication"` + BasePath string `toml:"base_path"` } var unix bool flag.BoolVar(&unix, "unix", false, "Interpret 'address' as a path to a unix domain socket instead of a network address") @@ -126,6 +127,10 @@ func main() { logrus.Fatalf("Could not load config file '%s': %v", configFile, err) } + if config.BasePath == "" { + config.BasePath = "/api/worker/v1" + } + cacheDirectory, ok := os.LookupEnv("CACHE_DIRECTORY") if !ok { logrus.Fatal("CACHE_DIRECTORY is not set. Is the service file missing CacheDirectory=?") @@ -148,7 +153,7 @@ func main() { var client *worker.Client if unix { - client = worker.NewClientUnix(address) + client = worker.NewClientUnix(address, config.BasePath) } else { var token *string var oAuthURL *string @@ -189,7 +194,7 @@ func main() { } } - client, err = worker.NewClient(address, conf, token, oAuthURL) + client, err = worker.NewClient(address, conf, token, oAuthURL, config.BasePath) if err != nil { logrus.Fatalf("Error creating worker client: %v", err) } diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go index 33ba8d6d0..a19dfa2ad 100644 --- a/internal/cloudapi/v2/openapi.v2.gen.go +++ b/internal/cloudapi/v2/openapi.v2.gen.go @@ -535,8 +535,8 @@ var swaggerSpec = []string{ "nfFs89aubVxX1a0s3lWYd+TFVr6EuZcw9/cIc63YpAwaVwxZhTtNXFTiWyvEbJ6WtYKLS7PNlK6+BdrW", "OKrM09dEf6jrb3RwWbt5wcvmyILx4mZ/jZsZQ//7ORkuDQgnCcqYEGSWQGlNGzfbXRNhappMNCz/CsZI", "tnm5N1shfXS6HXW/DKCk+3tP/eGffIaXW/nioy8++hQfNWurpLVfli3T7effhZ3ituq6sJac9lZEKFIY", - "2AeOf8fM4VF11uVVo4kz9V43zkhHLRcxsX82hjNSFFe8ezfwmnJ/sM8KWZSH5i2soa4ziDZxIfECnshi", - "KvGC0EWb8M6VGkFavGD01jfr/w8AAP//IYFDcM0+AAA=", + "2AeOf8fM4VF11uVVo4kz9V43zkhHLRcxsX82hjPS1dVMoBvqwIPi4XH3buA1tfhgHxmyKA/Ny1jDS+cT", + "bVZC4gX8LoZTiReELtpsnkhHY02Lt47e+mb9/wEAAP//rQggIvc+AAA=", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/internal/mocks/rpmmd/fixtures.go b/internal/mocks/rpmmd/fixtures.go index 9f1fc2bc9..55ded9d0a 100644 --- a/internal/mocks/rpmmd/fixtures.go +++ b/internal/mocks/rpmmd/fixtures.go @@ -57,7 +57,7 @@ func createBaseWorkersFixture(tmpdir string) *worker.Server { if err != nil { panic(err) } - return worker.NewServer(nil, q, "") + return worker.NewServer(nil, q, "", "/api/worker/v1") } func createBaseDepsolveFixture() []rpmmd.PackageSpec { diff --git a/internal/worker/api/api.go b/internal/worker/api/api.go index 4a47ea9e1..8ec115b99 100644 --- a/internal/worker/api/api.go +++ b/internal/worker/api/api.go @@ -2,4 +2,5 @@ package api -const BasePath = "/api/worker/v1" +// default basepath, can be overwritten +var BasePath = "/api/worker/v1" diff --git a/internal/worker/api/errors.go b/internal/worker/api/errors.go index 3da988384..2c74543f6 100644 --- a/internal/worker/api/errors.go +++ b/internal/worker/api/errors.go @@ -8,8 +8,7 @@ import ( ) const ( - ErrorCodePrefix = "COMPOSER-WORKER-" - ErrorHREF = "/api/composer-worker/v1/errors" + ErrorCodePrefix = "IMAGE-BUILDER-WORKER-" ErrorUnsupportedMediaType ServiceErrorCode = 3 ErrorJobNotFound ServiceErrorCode = 5 @@ -122,7 +121,7 @@ func APIError(code ServiceErrorCode, serviceError *serviceError, c echo.Context) return &Error{ ObjectReference: ObjectReference{ - Href: fmt.Sprintf("%s/%d", ErrorHREF, se.code), + Href: fmt.Sprintf("%s/errors/%d", BasePath, se.code), Id: fmt.Sprintf("%d", se.code), Kind: "Error", }, diff --git a/internal/worker/client.go b/internal/worker/client.go index 9396809a1..b757fa65a 100644 --- a/internal/worker/client.go +++ b/internal/worker/client.go @@ -55,12 +55,14 @@ type job struct { dynamicArgs []json.RawMessage } -func NewClient(baseURL string, conf *tls.Config, offlineToken, oAuthURL *string) (*Client, error) { +func NewClient(baseURL string, conf *tls.Config, offlineToken, oAuthURL *string, basePath string) (*Client, error) { server, err := url.Parse(baseURL) if err != nil { return nil, err } + api.BasePath = basePath + server, err = server.Parse(api.BasePath + "/") if err != nil { panic(err) @@ -76,12 +78,14 @@ func NewClient(baseURL string, conf *tls.Config, offlineToken, oAuthURL *string) return &Client{server, requester, offlineToken, oAuthURL, nil, nil, &sync.Mutex{}}, nil } -func NewClientUnix(path string) *Client { +func NewClientUnix(path string, basePath string) *Client { server, err := url.Parse("http://localhost/") if err != nil { panic(err) } + api.BasePath = basePath + server, err = server.Parse(api.BasePath + "/") if err != nil { panic(err) diff --git a/internal/worker/server.go b/internal/worker/server.go index 8da96c2c7..cc1004213 100644 --- a/internal/worker/server.go +++ b/internal/worker/server.go @@ -41,12 +41,15 @@ type JobStatus struct { var ErrInvalidToken = errors.New("token does not exist") var ErrJobNotRunning = errors.New("job isn't running") -func NewServer(logger *log.Logger, jobs jobqueue.JobQueue, artifactsDir string) *Server { +func NewServer(logger *log.Logger, jobs jobqueue.JobQueue, artifactsDir string, basePath string) *Server { s := &Server{ jobs: jobs, logger: logger, artifactsDir: artifactsDir, } + + api.BasePath = basePath + go s.WatchHeartbeats() return s } diff --git a/internal/worker/server_test.go b/internal/worker/server_test.go index 8a75828d0..bfb3e1bba 100644 --- a/internal/worker/server_test.go +++ b/internal/worker/server_test.go @@ -21,12 +21,12 @@ import ( "github.com/osbuild/osbuild-composer/internal/worker" ) -func newTestServer(t *testing.T, tempdir string) *worker.Server { +func newTestServer(t *testing.T, tempdir string, basePath string) *worker.Server { q, err := fsjobqueue.New(tempdir) if err != nil { t.Fatalf("error creating fsjobqueue: %v", err) } - return worker.NewServer(nil, q, "") + return worker.NewServer(nil, q, "", basePath) } // Ensure that the status request returns OK. @@ -35,7 +35,7 @@ func TestStatus(t *testing.T) { require.NoError(t, err) defer os.RemoveAll(tempdir) - server := newTestServer(t, tempdir) + server := newTestServer(t, tempdir, "/api/worker/v1") handler := server.Handler() test.TestRoute(t, handler, false, "GET", "/api/worker/v1/status", ``, http.StatusOK, `{"status":"OK", "href": "/api/worker/v1/status", "kind":"Status"}`, "message", "id") } @@ -66,7 +66,39 @@ func TestErrors(t *testing.T) { defer os.RemoveAll(tempdir) for _, c := range cases { - server := newTestServer(t, tempdir) + server := newTestServer(t, tempdir, "/api/worker/v1") + handler := server.Handler() + test.TestRoute(t, handler, false, c.Method, c.Path, c.Body, c.ExpectedStatus, `{"kind":"Error"}`, "message", "href", "operation_id", "reason", "id", "code") + } +} + +func TestErrorsAlteredBasePath(t *testing.T) { + var cases = []struct { + Method string + Path string + Body string + ExpectedStatus int + }{ + // Bogus path + {"GET", "/api/image-builder-worker/v1/foo", ``, http.StatusNotFound}, + // Create job with invalid body + {"POST", "/api/image-builder-worker/v1/jobs", ``, http.StatusBadRequest}, + // Wrong method + {"GET", "/api/image-builder-worker/v1/jobs", ``, http.StatusMethodNotAllowed}, + // Update job with invalid ID + {"PATCH", "/api/image-builder-worker/v1/jobs/foo", `{"status":"FINISHED"}`, http.StatusBadRequest}, + // Update job that does not exist, with invalid body + {"PATCH", "/api/image-builder-worker/v1/jobs/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", ``, http.StatusBadRequest}, + // Update job that does not exist + {"PATCH", "/api/image-builder-worker/v1/jobs/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", `{"status":"FINISHED"}`, http.StatusNotFound}, + } + + tempdir, err := ioutil.TempDir("", "worker-tests-") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + for _, c := range cases { + server := newTestServer(t, tempdir, "/api/image-builder-worker/v1") handler := server.Handler() test.TestRoute(t, handler, false, c.Method, c.Path, c.Body, c.ExpectedStatus, `{"kind":"Error"}`, "message", "href", "operation_id", "reason", "id", "code") } @@ -90,7 +122,7 @@ func TestCreate(t *testing.T) { if err != nil { t.Fatalf("error creating osbuild manifest: %v", err) } - server := newTestServer(t, tempdir) + server := newTestServer(t, tempdir, "/api/worker/v1") handler := server.Handler() _, err = server.EnqueueOSBuild(arch.Name(), &worker.OSBuildJob{Manifest: manifest}) @@ -119,7 +151,7 @@ func TestCancel(t *testing.T) { if err != nil { t.Fatalf("error creating osbuild manifest: %v", err) } - server := newTestServer(t, tempdir) + server := newTestServer(t, tempdir, "/api/worker/v1") handler := server.Handler() jobId, err := server.EnqueueOSBuild(arch.Name(), &worker.OSBuildJob{Manifest: manifest}) @@ -160,7 +192,7 @@ func TestUpdate(t *testing.T) { if err != nil { t.Fatalf("error creating osbuild manifest: %v", err) } - server := newTestServer(t, tempdir) + server := newTestServer(t, tempdir, "/api/worker/v1") handler := server.Handler() jobId, err := server.EnqueueOSBuild(arch.Name(), &worker.OSBuildJob{Manifest: manifest}) @@ -176,7 +208,7 @@ func TestUpdate(t *testing.T) { test.TestRoute(t, handler, false, "PATCH", fmt.Sprintf("/api/worker/v1/jobs/%s", token), `{}`, http.StatusOK, fmt.Sprintf(`{"href":"/api/worker/v1/jobs/%s","id":"%s","kind":"UpdateJobResponse"}`, token, token)) test.TestRoute(t, handler, false, "PATCH", fmt.Sprintf("/api/worker/v1/jobs/%s", token), `{}`, http.StatusNotFound, - `{"href":"/api/composer-worker/v1/errors/5","code":"COMPOSER-WORKER-5","id":"5","kind":"Error","message":"Token not found","reason":"Token not found"}`, + `{"href":"/api/worker/v1/errors/5","code":"IMAGE-BUILDER-WORKER-5","id":"5","kind":"Error","message":"Token not found","reason":"Token not found"}`, "operation_id") } @@ -192,7 +224,7 @@ func TestArgs(t *testing.T) { tempdir, err := ioutil.TempDir("", "worker-tests-") require.NoError(t, err) defer os.RemoveAll(tempdir) - server := newTestServer(t, tempdir) + server := newTestServer(t, tempdir, "/api/worker/v1") job := worker.OSBuildJob{ Manifest: manifest, @@ -232,7 +264,7 @@ func TestUpload(t *testing.T) { if err != nil { t.Fatalf("error creating osbuild manifest: %v", err) } - server := newTestServer(t, tempdir) + server := newTestServer(t, tempdir, "/api/worker/v1") handler := server.Handler() jobID, err := server.EnqueueOSBuild(arch.Name(), &worker.OSBuildJob{Manifest: manifest}) @@ -248,6 +280,40 @@ func TestUpload(t *testing.T) { test.TestRoute(t, handler, false, "PUT", fmt.Sprintf("/api/worker/v1/jobs/%s/artifacts/foobar", token), `this is my artifact`, http.StatusOK, `?`) } +func TestUploadAlteredBasePath(t *testing.T) { + tempdir, err := ioutil.TempDir("", "worker-tests-") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + distroStruct := test_distro.New() + arch, err := distroStruct.GetArch(test_distro.TestArchName) + if err != nil { + t.Fatalf("error getting arch from distro: %v", err) + } + imageType, err := arch.GetImageType(test_distro.TestImageTypeName) + if err != nil { + t.Fatalf("error getting image type from arch: %v", err) + } + manifest, err := imageType.Manifest(nil, distro.ImageOptions{Size: imageType.Size(0)}, nil, nil, 0) + if err != nil { + t.Fatalf("error creating osbuild manifest: %v", err) + } + server := newTestServer(t, tempdir, "/api/image-builder-worker/v1") + handler := server.Handler() + + jobID, err := server.EnqueueOSBuild(arch.Name(), &worker.OSBuildJob{Manifest: manifest}) + require.NoError(t, err) + + j, token, typ, args, dynamicArgs, err := server.RequestJob(context.Background(), arch.Name(), []string{"osbuild"}) + require.NoError(t, err) + require.Equal(t, jobID, j) + require.Equal(t, "osbuild", typ) + require.NotNil(t, args) + require.Nil(t, dynamicArgs) + + test.TestRoute(t, handler, false, "PUT", fmt.Sprintf("/api/image-builder-worker/v1/jobs/%s/artifacts/foobar", token), `this is my artifact`, http.StatusOK, `?`) +} + func TestOAuth(t *testing.T) { tempdir, err := ioutil.TempDir("", "worker-tests-") require.NoError(t, err) @@ -255,7 +321,7 @@ func TestOAuth(t *testing.T) { q, err := fsjobqueue.New(tempdir) require.NoError(t, err) - workerServer := worker.NewServer(nil, q, tempdir) + workerServer := worker.NewServer(nil, q, tempdir, "/api/image-builder-worker/v1") handler := workerServer.Handler() workSrv := httptest.NewServer(handler) @@ -311,7 +377,7 @@ func TestOAuth(t *testing.T) { _, err = workerServer.EnqueueOSBuild(arch.Name(), &worker.OSBuildJob{Manifest: manifest}) require.NoError(t, err) - client, err := worker.NewClient(proxySrv.URL, nil, &offlineToken, &oauthSrv.URL) + client, err := worker.NewClient(proxySrv.URL, nil, &offlineToken, &oauthSrv.URL, "/api/image-builder-worker/v1") require.NoError(t, err) job, err := client.RequestJob([]string{"osbuild"}, arch.Name()) require.NoError(t, err)