diff --git a/dashboards/command.go b/dashboards/command.go index 4e31c8c2..201f7299 100644 --- a/dashboards/command.go +++ b/dashboards/command.go @@ -5,6 +5,9 @@ import ( "fmt" "os" + "golang.org/x/text/encoding/unicode" + "golang.org/x/text/transform" + "github.com/mackerelio/mackerel-client-go" "github.com/mackerelio/mkr/format" "github.com/mackerelio/mkr/logger" @@ -92,8 +95,10 @@ func doPushDashboard(c *cli.Context) error { f := c.String("file-path") src, err := os.Open(f) logger.DieIf(err) + fallback := unicode.UTF8.NewDecoder() + r := transform.NewReader(src, unicode.BOMOverride(fallback)) - dec := json.NewDecoder(src) + dec := json.NewDecoder(r) var dashboard mackerel.Dashboard err = dec.Decode(&dashboard) logger.DieIf(err) diff --git a/go.mod b/go.mod index afb07003..c8e43d28 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/yudai/gojsondiff v1.0.0 golang.org/x/oauth2 v0.15.0 golang.org/x/sync v0.5.0 + golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 ) @@ -62,7 +63,6 @@ require ( golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/monitors/command.go b/monitors/command.go index a848c044..52c05426 100644 --- a/monitors/command.go +++ b/monitors/command.go @@ -17,6 +17,8 @@ import ( "github.com/urfave/cli" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" + "golang.org/x/text/encoding/unicode" + "golang.org/x/text/transform" ) var Command = cli.Command{ @@ -102,7 +104,9 @@ func monitorLoadRules(optFilePath string) ([]mackerel.Monitor, error) { if err != nil { return nil, err } - return decodeMonitors(f) + fallback := unicode.UTF8.NewDecoder() + r := transform.NewReader(f, unicode.BOMOverride(fallback)) + return decodeMonitors(r) } // decodeMonitors decodes monitors JSON. diff --git a/monitors/command_test.go b/monitors/command_test.go index 45b25c10..f2064a5f 100644 --- a/monitors/command_test.go +++ b/monitors/command_test.go @@ -200,3 +200,37 @@ func TestDiffMonitorsWithScopes(t *testing.T) { t.Errorf("expected:\n%s\n, output:\n%s\n", expected, diff) } } + +func TestMonitorLoadRulesWithBOM(t *testing.T) { + // XXX: t.TempDir is better, but it will cause "TempDir RemoveAll cleanup: remove C:\...\monitors.json: The process cannot access the file because it is being used by another process." error on Windows + tmpFile, err := os.CreateTemp("", "") + if err != nil { + t.Errorf("should not raise error: %v", err) + } + defer os.Remove(tmpFile.Name()) + + json := `{"monitors": []}` + + _, err = tmpFile.WriteString(json) + if err != nil { + t.Errorf("should not raise error: %v", err) + } + _, err = monitorLoadRules(tmpFile.Name()) + if err != nil { + t.Error("should accept JSON content no BOM") + } + + utf8bom := "\xef\xbb\xbf" + _, err = tmpFile.Seek(0, 0) + if err != nil { + t.Errorf("should not raise error: %v", err) + } + _, err = tmpFile.WriteString(utf8bom + json) + if err != nil { + t.Errorf("should not raise error: %v", err) + } + _, err = monitorLoadRules(tmpFile.Name()) + if err != nil { + t.Error("should accept JSON content with BOM") + } +}