diff --git a/cli_test.go b/cli_test.go index 2dea655..3b5c197 100644 --- a/cli_test.go +++ b/cli_test.go @@ -12,20 +12,24 @@ import ( "net/http" "net/http/httptest" "fmt" + "decl/tests/tempdir" + "archive/tar" + "io" + "log" + "bytes" ) -var TempDir string +var TempDir tempdir.Path = "testcli" func TestMain(m *testing.M) { - var err error - TempDir, err = os.MkdirTemp("", "testcli") + err := TempDir.Create() if err != nil || TempDir == "" { - slog.Error("TestMain()", "error", err) - } - + slog.Error("Failed creating temp dir", "error", err) + } + rc := m.Run() - os.RemoveAll(TempDir) + TempDir.Remove() os.Exit(rc) } @@ -85,7 +89,7 @@ configurations: http_pass: bar ` - configPath := fmt.Sprintf("%s/testconfig.yaml", TempDir) + configPath := fmt.Sprintf("%s/testconfig.jx.yaml", TempDir) f, err := os.Create(configPath) assert.Nil(t, err) defer f.Close() @@ -99,3 +103,78 @@ configurations: assert.Nil(t, cliErr) slog.Info("TestConfigSource", "yaml", yaml) } + +func TestCliConfigCommand(t *testing.T) { + if _, e := os.Stat("./jx"); errors.Is(e, os.ErrNotExist) { + t.Skip("cli not built") + } + + yaml, cliErr := exec.Command("./jx", "config", "file://examples/config/file.cfg.jx.yaml").Output() + if cliErr != nil { + slog.Info("Debug CLI error", "error", cliErr, "stderr", cliErr.(*exec.ExitError).Stderr) + } + assert.Nil(t, cliErr) + slog.Info("TestConfigCommand", "yaml", yaml) + assert.NotEqual(t, "", string(yaml)) + assert.Greater(t, len(yaml), 0) +} + +func TestCliImportPackageCommand(t *testing.T) { + if _, e := os.Stat("./jx"); errors.Is(e, os.ErrNotExist) { + t.Skip("cli not built") + } + + yaml, cliErr := exec.Command("./jx", "import", "package://").Output() + if cliErr != nil { + slog.Info("Debug CLI error", "error", cliErr, "stderr", cliErr.(*exec.ExitError).Stderr) + } + assert.Nil(t, cliErr) + assert.NotEqual(t, "", string(yaml)) + assert.Greater(t, len(yaml), 0) +} + +func TestCliExportTar(t *testing.T) { + if _, e := os.Stat("./jx"); errors.Is(e, os.ErrNotExist) { + t.Skip("cli not built") + } + assert.Nil(t, TempDir.Mkdir("tar", 0755)) + assert.Nil(t, TempDir.CreateFile("tar/foo", "data")) + assert.Nil(t, TempDir.CreateFile("tar/bar", "data")) + + cmdArgs := []string{"import", "--output", "tar://-", fmt.Sprintf("file://%s/tar", TempDir)} + slog.Info("TestCliExportTar()", "cmd", cmdArgs) + cmd := exec.Command("./jx", cmdArgs...) + slog.Info("TestCliExportTar()", "cmd", cmd) + stderr, errerr := cmd.StderrPipe() + assert.Nil(t, errerr) + stdout, outerr := cmd.StdoutPipe() + assert.Nil(t, outerr) + + assert.Nil(t, cmd.Start()) + + errOutput, _ := io.ReadAll(stderr) + tarData, _ := io.ReadAll(stdout) + + assert.Nil(t, cmd.Wait()) + + slog.Info("TestCliExportTar()", "stderr", errOutput) + + assert.Greater(t, len(tarData), 0) + + tr := tar.NewReader(bytes.NewBuffer(tarData)) + + files := []string{fmt.Sprintf("%s/tar/foo", TempDir), fmt.Sprintf("%s/tar/bar", TempDir)} + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + log.Fatal(err) + } + assert.Contains(t, files, hdr.Name) + contents, err := io.ReadAll(tr) + assert.Nil(t, err) + assert.Equal(t, []byte("data"), contents) + } +} diff --git a/internal/builtin/builtin.go b/internal/builtin/builtin.go index 77ac10d..65305a4 100644 --- a/internal/builtin/builtin.go +++ b/internal/builtin/builtin.go @@ -34,15 +34,15 @@ func Load(uri folio.URI) (documents []data.Document, err error) { var extractor data.Converter var sourceResource data.Resource if extractor, err = folio.DocumentRegistry.ConverterTypes.New(string(uri)); err == nil { - slog.Info("Load() extractor", "error", err) + slog.Info("builtin.Load() extractor", "uri", uri, "error", err) targetDeclaration := folio.NewDeclaration() if err = targetDeclaration.NewResource((*string)(&uri)); err == nil { - slog.Info("Load() extract many", "resource", sourceResource, "error", err, "uri", uri, "extractor", extractor) + slog.Info("builtin.Load() extract many", "resource", sourceResource, "error", err, "uri", uri, "extractor", extractor) sourceResource = targetDeclaration.Attributes sourceResource.(data.FileResource).SetFS(documentFiles) documents, err = extractor.(data.ManyExtractor).ExtractMany(sourceResource, nil) - slog.Info("Load() extract many", "resource", sourceResource, "error", err) + slog.Info("builtin.Load() extract many", "resource", sourceResource, "error", err) } } return diff --git a/internal/config/schema.go b/internal/config/schema.go index a41df08..8a970d0 100644 --- a/internal/config/schema.go +++ b/internal/config/schema.go @@ -10,11 +10,19 @@ import ( "embed" "net/http" "log/slog" + "decl/internal/folio" ) //go:embed schemas/*.schema.json var schemaFiles embed.FS +var schemaFilesUri folio.URI = "file://config/schemas/*.schema.json" + +func init() { + folio.DocumentRegistry.Schemas[schemaFilesUri] = schemaFiles + folio.DocumentRegistry.DefaultSchema = schemaFilesUri +} + type Schema struct { schema gojsonschema.JSONLoader } @@ -40,6 +48,7 @@ func (s *Schema) Validate(source string) error { for _, err := range result.Errors() { schemaErrors.WriteString(err.String() + "\n") } + schemaErrors.WriteString(source) return errors.New(schemaErrors.String()) } return nil diff --git a/internal/types/types.go b/internal/types/types.go index c6ef400..a560f99 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -8,6 +8,8 @@ import ( "net/url" "strings" "path/filepath" + "log/slog" + "runtime/debug" ) /* @@ -21,17 +23,18 @@ var ( ErrInvalidProduct = errors.New("Invalid product") ) -//type Name[Registry any] string //`json:"type"` - type Factory[Product comparable] func(*url.URL) Product type RegistryTypeMap[Product comparable] map[string]Factory[Product] +//type Name[Registry any] string //`json:"type"` + type Types[Product comparable] struct { registry RegistryTypeMap[Product] + contentTypes RegistryTypeMap[Product] } func New[Product comparable]() *Types[Product] { - return &Types[Product]{registry: make(map[string]Factory[Product])} + return &Types[Product]{registry: make(map[string]Factory[Product]), contentTypes: make(map[string]Factory[Product])} } func (t *Types[Product]) Register(names []string, factory Factory[Product]) { @@ -40,15 +43,24 @@ func (t *Types[Product]) Register(names []string, factory Factory[Product]) { } } +func (t *Types[Product]) RegisterContentType(contenttypes []string, factory Factory[Product]) { + for _,name := range contenttypes { + t.contentTypes[name] = factory + } +} + func (t *Types[Product]) FromExtension(path string) (Factory[Product], error) { elements := strings.Split(path, ".") numberOfElements := len(elements) + slog.Info("Types[Product].FromExtension()", "path", path, "elements", elements, "types", t.contentTypes, "stacktrace", string(debug.Stack())) if numberOfElements > 2 { - if src := t.Get(strings.Join(elements[numberOfElements - 2: numberOfElements - 1], ".")); src != nil { + ext := strings.Join(elements[numberOfElements - 2: numberOfElements], ".") + slog.Info("Types[Product].FromExtension() - Lookup", "ext", ext, "stacktrace", string(debug.Stack())) + if src := t.GetContentType(ext); src != nil { return src, nil } } - if src := t.Get(elements[numberOfElements - 1]); src != nil { + if src := t.GetContentType(elements[numberOfElements - 1]); src != nil { return src, nil } return nil, fmt.Errorf("%w: %s", ErrUnknownType, path) @@ -57,10 +69,13 @@ func (t *Types[Product]) FromExtension(path string) (Factory[Product], error) { func (t *Types[Product]) New(uri string) (result Product, err error) { u, e := url.Parse(uri) if u == nil || e != nil { - err = fmt.Errorf("%w: %s", ErrUnknownType, e) + err = fmt.Errorf("%w: %s %s", ErrUnknownType, e, uri) return } + return t.NewFromParsedURI(u) +} +func (t *Types[Product]) NewFromParsedURI(u *url.URL) (result Product, err error) { if u.Scheme == "" { u.Scheme = "file" } @@ -68,13 +83,15 @@ func (t *Types[Product]) New(uri string) (result Product, err error) { path := filepath.Join(u.Hostname(), u.Path) if d, lookupErr := t.FromExtension(path); d != nil { return d(u), lookupErr + } else { + slog.Info("Types[Product].NewFromParsedURI() - FromExtension()", "uri", u, "path", path, "error", lookupErr, "stacktrace", string(debug.Stack())) } if r, ok := t.registry[u.Scheme]; ok { if result = r(u); result != any(nil) { return result, nil } else { - return result, fmt.Errorf("%w: factory failed creating %s", ErrInvalidProduct, uri) + return result, fmt.Errorf("%w: factory failed creating %s", ErrInvalidProduct, u.String()) } } err = fmt.Errorf("%w: %s", ErrUnknownType, u.Scheme) @@ -88,8 +105,15 @@ func (t *Types[Product]) Has(typename string) bool { return false } -func (t *Types[Product]) Get(typename string) Factory[Product] { - if d, ok := t.registry[typename]; ok { +func (t *Types[Product]) HasContentType(contenttype string) bool { + if _, ok := t.contentTypes[contenttype]; ok { + return true + } + return false +} + +func (t *Types[Product]) GetContentType(contenttype string) Factory[Product] { + if d, ok := t.contentTypes[contenttype]; ok { return d } return nil diff --git a/tests/mocks/container.go b/tests/mocks/container.go index 34a20a9..32b2faf 100644 --- a/tests/mocks/container.go +++ b/tests/mocks/container.go @@ -24,6 +24,7 @@ type MockContainerClient struct { InjectContainerStop func(context.Context, string, container.StopOptions) error InjectContainerWait func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) InjectImagePull func(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error) + InjectImagePush func(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error) InjectImageInspectWithRaw func(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) InjectImageRemove func(ctx context.Context, imageID string, options image.RemoveOptions) ([]image.DeleteResponse, error) InjectImageBuild func(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) @@ -42,6 +43,10 @@ func (m *MockContainerClient) ImagePull(ctx context.Context, refStr string, opti return m.InjectImagePull(ctx, refStr, options) } +func (m *MockContainerClient) ImagePush(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error) { + return m.InjectImagePush(ctx, image, options) +} + func (m *MockContainerClient) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { return m.InjectImageBuild(ctx, buildContext, options) }