Respect mediatypes for deploy
authorsatotake <doublequotation@gmail.com>
Mon, 3 Aug 2020 17:06:18 +0000 (02:06 +0900)
committerGitHub <noreply@github.com>
Mon, 3 Aug 2020 17:06:18 +0000 (19:06 +0200)
Fixes #6861

deploy/deploy.go
deploy/deployConfig.go
deploy/deploy_test.go

index 9a38072a7bfe4e6c7537b55b259d5c790ae2ee88..f6b5b5785f2832999578f097cc724141a253ddc5 100644 (file)
@@ -33,6 +33,7 @@ import (
        "github.com/dustin/go-humanize"
        "github.com/gobwas/glob"
        "github.com/gohugoio/hugo/config"
+       "github.com/gohugoio/hugo/media"
        "github.com/pkg/errors"
        "github.com/spf13/afero"
        jww "github.com/spf13/jwalterweatherman"
@@ -51,6 +52,7 @@ type Deployer struct {
 
        target        *target          // the target to deploy to
        matchers      []*matcher       // matchers to apply to uploaded files
+       mediaTypes    media.Types      // Hugo's MediaType to guess ContentType
        ordering      []*regexp.Regexp // orders uploads
        quiet         bool             // true reduces STDOUT
        confirm       bool             // true enables confirmation before making changes
@@ -96,11 +98,13 @@ func New(cfg config.Provider, localFs afero.Fs) (*Deployer, error) {
                        return nil, fmt.Errorf("deployment target %q not found", targetName)
                }
        }
+
        return &Deployer{
                localFs:       localFs,
                target:        tgt,
                matchers:      dcfg.Matchers,
                ordering:      dcfg.ordering,
+               mediaTypes:    dcfg.mediaTypes,
                quiet:         cfg.GetBool("quiet"),
                confirm:       cfg.GetBool("confirm"),
                dryRun:        cfg.GetBool("dryRun"),
@@ -130,7 +134,7 @@ func (d *Deployer) Deploy(ctx context.Context) error {
        if d.target != nil {
                include, exclude = d.target.includeGlob, d.target.excludeGlob
        }
-       local, err := walkLocal(d.localFs, d.matchers, include, exclude)
+       local, err := walkLocal(d.localFs, d.matchers, include, exclude, d.mediaTypes)
        if err != nil {
                return err
        }
@@ -322,14 +326,15 @@ type localFile struct {
        // gzipped before upload.
        UploadSize int64
 
-       fs      afero.Fs
-       matcher *matcher
-       md5     []byte       // cache
-       gzipped bytes.Buffer // cached of gzipped contents if gzipping
+       fs         afero.Fs
+       matcher    *matcher
+       md5        []byte       // cache
+       gzipped    bytes.Buffer // cached of gzipped contents if gzipping
+       mediaTypes media.Types
 }
 
 // newLocalFile initializes a *localFile.
-func newLocalFile(fs afero.Fs, nativePath, slashpath string, m *matcher) (*localFile, error) {
+func newLocalFile(fs afero.Fs, nativePath, slashpath string, m *matcher, mt media.Types) (*localFile, error) {
        f, err := fs.Open(nativePath)
        if err != nil {
                return nil, err
@@ -340,6 +345,7 @@ func newLocalFile(fs afero.Fs, nativePath, slashpath string, m *matcher) (*local
                SlashPath:  slashpath,
                fs:         fs,
                matcher:    m,
+               mediaTypes: mt,
        }
        if m != nil && m.Gzip {
                // We're going to gzip the content. Do it once now, and cache the result
@@ -410,10 +416,13 @@ func (lf *localFile) ContentType() string {
        if lf.matcher != nil && lf.matcher.ContentType != "" {
                return lf.matcher.ContentType
        }
-       // TODO: Hugo has a MediaType and a MediaTypes list and also a concept
-       // of custom MIME types.
-       // Use 1) The matcher 2) Hugo's MIME types 3) TypeByExtension.
-       return mime.TypeByExtension(filepath.Ext(lf.NativePath))
+
+       ext := filepath.Ext(lf.NativePath)
+       if mimeType, found := lf.mediaTypes.GetFirstBySuffix(strings.TrimPrefix(ext, ".")); found {
+               return mimeType.Type()
+       }
+
+       return mime.TypeByExtension(ext)
 }
 
 // Force returns true if the file should be forced to re-upload based on the
@@ -457,7 +466,7 @@ func knownHiddenDirectory(name string) bool {
 
 // walkLocal walks the source directory and returns a flat list of files,
 // using localFile.SlashPath as the map keys.
-func walkLocal(fs afero.Fs, matchers []*matcher, include, exclude glob.Glob) (map[string]*localFile, error) {
+func walkLocal(fs afero.Fs, matchers []*matcher, include, exclude glob.Glob, mediaTypes media.Types) (map[string]*localFile, error) {
        retval := map[string]*localFile{}
        err := afero.Walk(fs, "", func(path string, info os.FileInfo, err error) error {
                if err != nil {
@@ -503,7 +512,7 @@ func walkLocal(fs afero.Fs, matchers []*matcher, include, exclude glob.Glob) (ma
                                break
                        }
                }
-               lf, err := newLocalFile(fs, path, slashpath, m)
+               lf, err := newLocalFile(fs, path, slashpath, m, mediaTypes)
                if err != nil {
                        return err
                }
index ecfabb7a4ebb4ba7bf1f92b0e010cb1be2da3922..cc2b15280a8e89e2f614b14d7fcac9e0931448a5 100644 (file)
@@ -20,6 +20,7 @@ import (
        "github.com/gobwas/glob"
        "github.com/gohugoio/hugo/config"
        hglob "github.com/gohugoio/hugo/hugofs/glob"
+       "github.com/gohugoio/hugo/media"
        "github.com/mitchellh/mapstructure"
 )
 
@@ -31,7 +32,8 @@ type deployConfig struct {
        Matchers []*matcher
        Order    []string
 
-       ordering []*regexp.Regexp // compiled Order
+       ordering   []*regexp.Regexp // compiled Order
+       mediaTypes media.Types
 }
 
 type target struct {
@@ -108,7 +110,12 @@ func (m *matcher) Matches(path string) bool {
 
 // decode creates a config from a given Hugo configuration.
 func decodeConfig(cfg config.Provider) (deployConfig, error) {
-       var dcfg deployConfig
+
+       var (
+               mediaTypesConfig []map[string]interface{}
+               dcfg             deployConfig
+       )
+
        if !cfg.IsSet(deploymentConfigKey) {
                return dcfg, nil
        }
@@ -134,5 +141,14 @@ func decodeConfig(cfg config.Provider) (deployConfig, error) {
                }
                dcfg.ordering = append(dcfg.ordering, re)
        }
+
+       if cfg.IsSet("mediaTypes") {
+               mediaTypesConfig = append(mediaTypesConfig, cfg.GetStringMap("mediaTypes"))
+       }
+
+       dcfg.mediaTypes, err = media.DecodeTypes(mediaTypesConfig...)
+       if err != nil {
+               return dcfg, err
+       }
        return dcfg, nil
 }
index 0ca1b3fac211251e252db405a8fa6c8752d6c1a8..0ae10b5394cececa07bc9798b45d4dde9ee9eb90 100644 (file)
@@ -28,6 +28,7 @@ import (
        "sort"
        "testing"
 
+       "github.com/gohugoio/hugo/media"
        "github.com/google/go-cmp/cmp"
        "github.com/google/go-cmp/cmp/cmpopts"
        "github.com/spf13/afero"
@@ -208,6 +209,7 @@ func TestFindDiffs(t *testing.T) {
 }
 
 func TestWalkLocal(t *testing.T) {
+
        tests := map[string]struct {
                Given  []string
                Expect []string
@@ -246,7 +248,7 @@ func TestWalkLocal(t *testing.T) {
                                        fd.Close()
                                }
                        }
-                       if got, err := walkLocal(fs, nil, nil, nil); err != nil {
+                       if got, err := walkLocal(fs, nil, nil, nil, media.DefaultTypes); err != nil {
                                t.Fatal(err)
                        } else {
                                expect := map[string]interface{}{}
@@ -287,6 +289,7 @@ func TestLocalFile(t *testing.T) {
                Description         string
                Path                string
                Matcher             *matcher
+               MediaTypesConfig    []map[string]interface{}
                WantContent         []byte
                WantSize            int64
                WantMD5             []byte
@@ -344,6 +347,18 @@ func TestLocalFile(t *testing.T) {
                        WantMD5:             gzMD5[:],
                        WantContentEncoding: "gzip",
                },
+               {
+                       Description: "Custom MediaType",
+                       Path:        "foo.hugo",
+                       MediaTypesConfig: []map[string]interface{}{
+                               {
+                                       "hugo/custom": map[string]interface{}{
+                                               "suffixes": []string{"hugo"}}}},
+                       WantContent:     contentBytes,
+                       WantSize:        contentLen,
+                       WantMD5:         contentMD5[:],
+                       WantContentType: "hugo/custom",
+               },
        }
 
        for _, tc := range tests {
@@ -352,7 +367,15 @@ func TestLocalFile(t *testing.T) {
                        if err := afero.WriteFile(fs, tc.Path, []byte(content), os.ModePerm); err != nil {
                                t.Fatal(err)
                        }
-                       lf, err := newLocalFile(fs, tc.Path, filepath.ToSlash(tc.Path), tc.Matcher)
+                       mediaTypes := media.DefaultTypes
+                       if len(tc.MediaTypesConfig) > 0 {
+                               mt, err := media.DecodeTypes(tc.MediaTypesConfig...)
+                               if err != nil {
+                                       t.Fatal(err)
+                               }
+                               mediaTypes = mt
+                       }
+                       lf, err := newLocalFile(fs, tc.Path, filepath.ToSlash(tc.Path), tc.Matcher, mediaTypes)
                        if err != nil {
                                t.Fatal(err)
                        }
@@ -543,6 +566,7 @@ func TestEndToEndSync(t *testing.T) {
                                localFs:    test.fs,
                                maxDeletes: -1,
                                bucket:     test.bucket,
+                               mediaTypes: media.DefaultTypes,
                        }
 
                        // Initial deployment should sync remote with local.
@@ -629,6 +653,7 @@ func TestMaxDeletes(t *testing.T) {
                                localFs:    test.fs,
                                maxDeletes: -1,
                                bucket:     test.bucket,
+                               mediaTypes: media.DefaultTypes,
                        }
 
                        // Sync remote with local.
@@ -702,7 +727,6 @@ func TestMaxDeletes(t *testing.T) {
 // TestIncludeExclude verifies that the include/exclude options for targets work.
 func TestIncludeExclude(t *testing.T) {
        ctx := context.Background()
-
        tests := []struct {
                Include string
                Exclude string
@@ -766,6 +790,7 @@ func TestIncludeExclude(t *testing.T) {
                                maxDeletes: -1,
                                bucket:     fsTest.bucket,
                                target:     tgt,
+                               mediaTypes: media.DefaultTypes,
                        }
 
                        // Sync remote with local.
@@ -826,6 +851,7 @@ func TestIncludeExcludeRemoteDelete(t *testing.T) {
                                localFs:    fsTest.fs,
                                maxDeletes: -1,
                                bucket:     fsTest.bucket,
+                               mediaTypes: media.DefaultTypes,
                        }
 
                        // Initial sync to get the files on the remote
@@ -865,6 +891,7 @@ func TestIncludeExcludeRemoteDelete(t *testing.T) {
 // In particular, MD5 hashes must be of the compressed content.
 func TestCompression(t *testing.T) {
        ctx := context.Background()
+
        tests, cleanup, err := initFsTests()
        if err != nil {
                t.Fatal(err)
@@ -877,9 +904,10 @@ func TestCompression(t *testing.T) {
                                t.Fatal(err)
                        }
                        deployer := &Deployer{
-                               localFs:  test.fs,
-                               bucket:   test.bucket,
-                               matchers: []*matcher{{Pattern: ".*", Gzip: true, re: regexp.MustCompile(".*")}},
+                               localFs:    test.fs,
+                               bucket:     test.bucket,
+                               matchers:   []*matcher{{Pattern: ".*", Gzip: true, re: regexp.MustCompile(".*")}},
+                               mediaTypes: media.DefaultTypes,
                        }
 
                        // Initial deployment should sync remote with local.
@@ -935,9 +963,10 @@ func TestMatching(t *testing.T) {
                                t.Fatal(err)
                        }
                        deployer := &Deployer{
-                               localFs:  test.fs,
-                               bucket:   test.bucket,
-                               matchers: []*matcher{{Pattern: "^subdir/aaa$", Force: true, re: regexp.MustCompile("^subdir/aaa$")}},
+                               localFs:    test.fs,
+                               bucket:     test.bucket,
+                               matchers:   []*matcher{{Pattern: "^subdir/aaa$", Force: true, re: regexp.MustCompile("^subdir/aaa$")}},
+                               mediaTypes: media.DefaultTypes,
                        }
 
                        // Initial deployment to sync remote with local.