diff --git a/cmd/generate-testdata/main.go b/cmd/generate-testdata/main.go index 913145b4..7d13ffa3 100644 --- a/cmd/generate-testdata/main.go +++ b/cmd/generate-testdata/main.go @@ -104,6 +104,8 @@ var ( "stream_alert_conditions": StreamAlertConditions{}, "outputs": Outputs{}, "stdout_output": Output{}, + "views": Views{}, + "view": View{}, } ) @@ -199,6 +201,14 @@ type ( EventDefinition struct { data graylog.EventDefinition } + + Views struct { + data graylog.Views + } + + View struct { + data graylog.View + } ) func (users Users) dump(input string) error { @@ -292,3 +302,11 @@ func (definitions EventDefinitions) dump(input string) error { func (definition EventDefinition) dump(input string) error { return dump(input, &definition.data) } + +func (v Views) dump(input string) error { + return dump(input, &v.data) +} + +func (v View) dump(input string) error { + return dump(input, &v.data) +} diff --git a/examples/v0.12/view.tf b/examples/v0.12/view.tf new file mode 100644 index 00000000..d1979e74 --- /dev/null +++ b/examples/v0.12/view.tf @@ -0,0 +1,12 @@ +resource "graylog_view" "test" { + title = "test" + description = "description" + summary = "summary" + # set appropriate search_id + # search_id = "5d9529b275d97f58f9539279" + # state = { + # "6971d00a-e605-43fb-b873-e4bca773d286" = { + # selected_fields = ["source", "message"] + # } + # } +} diff --git a/go.mod b/go.mod index f69fa813..8a02daf1 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,14 @@ require ( github.com/hashicorp/terraform v0.12.19 github.com/leodido/go-urn v1.2.0 // indirect github.com/mitchellh/mapstructure v1.1.2 + github.com/pkg/errors v0.8.1 github.com/sanity-io/litter v1.2.0 github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.4.0 github.com/suzuki-shunsuke/flute v0.7.0 github.com/suzuki-shunsuke/go-jsoneq v0.1.2 github.com/suzuki-shunsuke/go-ptr v1.0.0 + github.com/suzuki-shunsuke/go-set v6.0.0+incompatible // indirect github.com/suzuki-shunsuke/go-set/v6 v6.0.1 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v9 v9.31.0 diff --git a/go.sum b/go.sum index d363cfae..71f41b36 100644 --- a/go.sum +++ b/go.sum @@ -95,12 +95,9 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -108,7 +105,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -219,7 +215,6 @@ github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lusis/go-artifactory v0.0.0-20160115162124-7e4ce345df82/go.mod h1:y54tfGmO3NKssKveTEFFzH8C/akrSOy/iW9qEAUDV84= @@ -300,7 +295,6 @@ github.com/scylladb/go-set v1.0.2/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQw github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= @@ -320,12 +314,13 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/suzuki-shunsuke/flute v0.7.0 h1:DvDSCMIMiLlRj4AQPMeJ1NfHE3lG5yfs2LU0Dnf1+oc= github.com/suzuki-shunsuke/flute v0.7.0/go.mod h1:UZOMr3GyEuYSr7/zf0nHgaLP9ZhKDB+2pBeV1WFkohE= github.com/suzuki-shunsuke/go-cliutil v0.0.0-20181211154308-176f852d9bca/go.mod h1:Vq3NkhgmA9DT/2UZ08x/3A34xxvzQ/vTMABnTWKoMbY= -github.com/suzuki-shunsuke/go-graylog v2.5.0+incompatible h1:6G5hjnyxyLjc1GGksED+wcbQSTNmVzVhkthhVll6HyY= github.com/suzuki-shunsuke/go-jsoneq v0.1.1/go.mod h1:vbOEb6bPf8nD+QASKzxtQ/vfVGgAyfWHyItUaUTwJWk= github.com/suzuki-shunsuke/go-jsoneq v0.1.2 h1:A4czEbmFqSELTbrEtXVo4dSgfz2e2Z0y6G3OpExUML8= github.com/suzuki-shunsuke/go-jsoneq v0.1.2/go.mod h1:ETXAwfruZTqMMKDxc9CYoS34CNSsnzcdcVIAW3+RujI= github.com/suzuki-shunsuke/go-ptr v1.0.0 h1:sVR6ICMJbdCyOpxzrflHErkO8KmItPFDu0E0PoQR0W8= github.com/suzuki-shunsuke/go-ptr v1.0.0/go.mod h1:4WKv+CJynv3Veutqvmt7yPUqEsZp0vezqFxVRukjga0= +github.com/suzuki-shunsuke/go-set v6.0.0+incompatible h1:APxAhd/XeHGrrp3vR6a9Yiwgak+HlM+CVREvDAULM8w= +github.com/suzuki-shunsuke/go-set v6.0.0+incompatible/go.mod h1:CZob7MsjSLZlWDHWKeRvDX/Q2eWA6qqbx+FXdy2FgnM= github.com/suzuki-shunsuke/go-set/v6 v6.0.1 h1:JxqxB+UYnkMqskPZhLrOFLas6SY/Z6yMNOm9kOvwSDY= github.com/suzuki-shunsuke/go-set/v6 v6.0.1/go.mod h1:3dlTSl52oLMev8ZwgkNkGl3euc+Gm0ehBpfR4g+MdXY= github.com/suzuki-shunsuke/gomic v0.5.6/go.mod h1:GEDQnxOB07p3mTZG/MiuclfyfcqnNqp0rt9AHgIzs7Q= @@ -463,7 +458,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M= gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= diff --git a/graylog/client/endpoint/endpoint.go b/graylog/client/endpoint/endpoint.go index 5eca596c..7230f937 100644 --- a/graylog/client/endpoint/endpoint.go +++ b/graylog/client/endpoint/endpoint.go @@ -33,6 +33,7 @@ type Endpoints struct { ldapGroupRoleMapping string connectStreamsToPipeline string connectPipelinesToStream string + views string apiVersion string } @@ -96,6 +97,7 @@ func newEndpoints(endpoint, version string) (*Endpoints, error) { users: endpoint + "/users", grokPatterns: endpoint + "/system/grok", grokPatternsTest: endpoint + "/system/grok/test", + views: endpoint + "/views", apiVersion: version, }, nil } diff --git a/graylog/client/endpoint/view.go b/graylog/client/endpoint/view.go new file mode 100644 index 00000000..a5cfe848 --- /dev/null +++ b/graylog/client/endpoint/view.go @@ -0,0 +1,16 @@ +package endpoint + +// Views returns a View API's endpoint url. +func (ep *Endpoints) Views() string { + return ep.views +} + +// View returns a View API's endpoint url. +func (ep *Endpoints) View(id string) string { + return ep.views + "/" + id +} + +// ViewDefault returns a View API's endpoint url. +func (ep *Endpoints) ViewDefault(id string) string { + return ep.views + "/" + id + "/default" +} diff --git a/graylog/client/view.go b/graylog/client/view.go new file mode 100644 index 00000000..ee69b74e --- /dev/null +++ b/graylog/client/view.go @@ -0,0 +1,73 @@ +package client + +import ( + "context" + "errors" + + "github.com/suzuki-shunsuke/go-graylog/v11/graylog/graylog" +) + +// GetViews returns all views. +func (client *Client) GetViews( + ctx context.Context, +) (*graylog.Views, *ErrorInfo, error) { + viewsBody := &graylog.Views{} + ei, err := client.callGet( + ctx, client.Endpoints().Views(), nil, viewsBody) + return viewsBody, ei, err +} + +// GetView returns a given view. +func (client *Client) GetView( + ctx context.Context, id string, +) (*graylog.View, *ErrorInfo, error) { + if id == "" { + return nil, nil, errors.New("id is empty") + } + view := &graylog.View{} + ei, err := client.callGet(ctx, client.Endpoints().View(id), nil, view) + return view, ei, err +} + +// CreateView creates a view. +func (client *Client) CreateView( + ctx context.Context, view *graylog.View, +) (*ErrorInfo, error) { + // required: title search_id state + // allowed: state, search_id, owner, summary, title, created_at, id, description, requires, properties, dashboard_state + if view == nil { + return nil, errors.New("view is nil") + } + if view.State == nil { + view.State = map[string]graylog.ViewState{} + } + return client.callPost(ctx, client.Endpoints().Views(), view, view) +} + +// UpdateView updates a view. +func (client *Client) UpdateView( + ctx context.Context, view *graylog.View, +) (*ErrorInfo, error) { + if view == nil { + return nil, errors.New("view is nil") + } + if view.ID == "" { + return nil, errors.New("id is empty") + } + if view.State == nil { + view.State = map[string]graylog.ViewState{} + } + body := *view + body.ID = "" + return client.callPut(ctx, client.Endpoints().View(view.ID), &body, view) +} + +// DeleteView deletes a view. +func (client *Client) DeleteView( + ctx context.Context, id string, +) (*ErrorInfo, error) { + if id == "" { + return nil, errors.New("id is empty") + } + return client.callDelete(ctx, client.Endpoints().View(id), nil, nil) +} diff --git a/graylog/client/view_test.go b/graylog/client/view_test.go new file mode 100644 index 00000000..bd7432a2 --- /dev/null +++ b/graylog/client/view_test.go @@ -0,0 +1,58 @@ +package client + +import ( + "context" + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "github.com/suzuki-shunsuke/flute/flute" + + "github.com/suzuki-shunsuke/go-graylog/v11/graylog/testdata" +) + +func TestClient_GetViews(t *testing.T) { + ctx := context.Background() + + cl, err := NewClient("http://example.com/api", "admin", "admin") + require.Nil(t, err) + + buf, err := ioutil.ReadFile("../testdata/views.json") + require.Nil(t, err) + bodyStr := string(buf) + + cl.SetHTTPClient(&http.Client{ + Transport: &flute.Transport{ + T: t, + Services: []flute.Service{ + { + Endpoint: "http://example.com", + Routes: []flute.Route{ + { + Tester: &flute.Tester{ + Method: "GET", + Path: "/api/views", + PartOfHeader: http.Header{ + "Content-Type": []string{"application/json"}, + "X-Requested-By": []string{"go-graylog"}, + "Authorization": nil, + }, + }, + Response: &flute.Response{ + Base: http.Response{ + StatusCode: 200, + }, + BodyString: bodyStr, + }, + }, + }, + }, + }, + }, + }) + + views, _, err := cl.GetViews(ctx) + require.Nil(t, err) + require.Equal(t, testdata.Views, views) +} diff --git a/graylog/graylog/view.go b/graylog/graylog/view.go new file mode 100644 index 00000000..a5278a05 --- /dev/null +++ b/graylog/graylog/view.go @@ -0,0 +1,194 @@ +package graylog + +import ( + "encoding/json" + "errors" + "fmt" +) + +type ( + // Stream represents a steram. + View struct { + ID string `json:"id,omitempty"` + Title string `json:"title"` + Summary string `json:"summary"` + Description string `json:"description"` + SearchID string `json:"search_id"` + Owner string `json:"owner"` + CreatedAt string `json:"created_at,omitempty"` + // Properties []interface{} `json:"properties"` + // Requires map[string]interface{} `json:"requires"` + DashboardState DashboardState `json:"dashboard_state"` + State map[string]ViewState `json:"state"` + } + + ViewState struct { + SelectedFields []string `json:"selected_fields"` + Titles map[string]map[string]string `json:"titles"` + WidgetMapping map[string][]string `json:"widget_mapping"` + Widgets []ViewWidget `json:"widgets"` + Positions map[string]ViewWidgetPosition `json:"positions"` + // StatecMessageListID interface{} `json:"state_message_list_id"` + // Formatting interface{} `json:"formatting"` + } + + ViewWidget struct { + ID string `json:"id"` + // Filter interface{} `json:"filter"` + Config ViewWidgetConfig + } + + ViewWidgetConfig interface { + Type() string + } + + AggregationViewWidgetConfig struct { + RowPivots []ViewWidgetRowPivot `json:"row_pivots"` + Series []ViewWidgetSeries `json:"series"` + Visualization string `json:"visualization"` + Rollup bool `json:"rollup"` + // Sort []interface{} `json:"sort"` + // ColumnPivots []interface{} `json:"column_pivots"` + // VisualizationConfig interface{} `json:"visualization_config"` + // FormattingSettings interface{} `json:"formatting_settings"` + } + + MessagesViewWidgetConfig struct { + Fields []string `json:"fields"` + ShowMessageRow bool `json:"show_message_row"` + } + + ViewWidgetSeries struct { + Config ViewWidgetSeriesConfig `json:"config"` + Function string `json:"function"` + } + + ViewWidgetSeriesConfig struct { + // Name interface{} + } + + ViewWidgetRowPivot struct { + Field string `json:"field"` + Type string `json:"type"` + Config ViewWidgetRowPivotConfig `json:"config"` + } + + ViewWidgetRowPivotConfig struct { + Interval ViewWidgetRowPivotInterval + } + + ViewWidgetRowPivotInterval struct { + Type string `json:"type"` + // Scaling interface{} `json:"scalling" + } + + ViewWidgetPosition struct { + Width interface{} `json:"width"` // int or "Infinity" + Col int `json:"col"` + Row int `json:"row"` + Height int `json:"height"` + } + + DashboardState struct { + // Widgets interface{} `json:"widgets"` + // Positions interface{} `json:"positions"` + } + + // Views represents Get View API's response body. + Views struct { + Total int `json:"total"` + Page int `json:"page"` + PerPage int `json:"per_page"` + Count int `json:"count"` + Views []View `json:"views"` + } +) + +func (widget ViewWidget) Type() string { + return widget.Config.Type() +} + +func (widget AggregationViewWidgetConfig) Type() string { + return "aggregation" +} + +func (widget MessagesViewWidgetConfig) Type() string { + return "messages" +} + +// UnmarshalJSON unmarshals JSON into an alert condition. +func (widget *ViewWidget) UnmarshalJSON(b []byte) error { + errMsg := "failed to unmarshal JSON to view widget" + if widget == nil { + return errors.New(errMsg + ": view widget is nil") + } + type alias ViewWidget + a := struct { + Type string `json:"type"` + Config json.RawMessage `json:"config"` + *alias + }{ + alias: (*alias)(widget), + } + if err := json.Unmarshal(b, &a); err != nil { + return fmt.Errorf(errMsg+": %w", err) + } + switch a.Type { + case "aggregation": + p := AggregationViewWidgetConfig{} + if err := json.Unmarshal(a.Config, &p); err != nil { + return fmt.Errorf(errMsg+": %w", err) + } + widget.Config = p + return nil + case "messages": + p := MessagesViewWidgetConfig{} + if err := json.Unmarshal(a.Config, &p); err != nil { + return fmt.Errorf(errMsg+": %w", err) + } + widget.Config = p + return nil + } + // TODO + return nil +} + +// UnmarshalJSON unmarshals JSON into an alert condition. +func (position *ViewWidgetPosition) UnmarshalJSON(b []byte) error { + errMsg := "failed to unmarshal JSON to view widget position" + if position == nil { + return errors.New(errMsg + ": view widget position is nil") + } + type alias ViewWidgetPosition + + a := struct { + *alias + Width string `json:"width"` + }{ + alias: (*alias)(position), + } + if err := json.Unmarshal(b, &a); err == nil { + position.Width = a.Width + return nil + } + + c := struct { + *alias + Width json.Number `json:"width"` + }{ + alias: (*alias)(position), + } + if err := json.Unmarshal(b, &c); err != nil { + return fmt.Errorf(errMsg+": %w", err) + } + if i, err := c.Width.Int64(); err == nil { + position.Width = int(i) + return nil + } + if f, err := c.Width.Float64(); err == nil { + position.Width = f + return nil + } + // TODO + return nil +} diff --git a/graylog/terraform/provider.go b/graylog/terraform/provider.go index d41ea203..d12fdc68 100644 --- a/graylog/terraform/provider.go +++ b/graylog/terraform/provider.go @@ -61,6 +61,7 @@ func Provider() *schema.Provider { "graylog_stream": resourceStream(), "graylog_stream_rule": resourceStreamRule(), "graylog_user": resourceUser(), + "graylog_view": resourceView(), }, DataSourcesMap: map[string]*schema.Resource{ "graylog_index_set": dataSourceIndexSet(), diff --git a/graylog/terraform/resource_view.go b/graylog/terraform/resource_view.go new file mode 100644 index 00000000..3f33fe8a --- /dev/null +++ b/graylog/terraform/resource_view.go @@ -0,0 +1,253 @@ +package terraform + +import ( + "context" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/suzuki-shunsuke/go-graylog/v11/graylog/graylog" +) + +func resourceView() *schema.Resource { + return &schema.Resource{ + Create: resourceViewCreate, + Read: resourceViewRead, + Update: resourceViewUpdate, + Delete: resourceViewDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + // Required + "title": { + Type: schema.TypeString, + Required: true, + }, + + "search_id": { + Type: schema.TypeString, + Required: true, + }, + + // Optional + "state": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "selected_fields": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "titles": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + + "widget_mapping": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + + "widgets": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + }, + + // "aggregation_config": { + // Type: schema.TypeList, + // Optional: true, + // MaxItems: 1, + // MinItems: 1, + // Elem: &schema.Resource{ + // }, + // }, + }, + }, + }, + + "positions": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + // "width": { + // Type: schema.TypeInt, + // Optional: true, + // }, + "col": { + Type: schema.TypeInt, + Optional: true, + }, + "row": { + Type: schema.TypeInt, + Optional: true, + }, + "height": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + + "summary": { + Type: schema.TypeString, + Optional: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "owner": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "created_at": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + // "dashboard_state": { + // Type: schema.TypeList, + // Optional: true, + // MaxItems: 1, + // MinItems: 1, + // Elem: &schema.Resource{ + // Schema: map[string]*schema.Schema{}, + // }, + // }, + }, + } +} + +func newView(d *schema.ResourceData) (*graylog.View, error) { + return &graylog.View{ + Title: d.Get("title").(string), + Summary: d.Get("summary").(string), + Description: d.Get("description").(string), + SearchID: d.Get("search_id").(string), + Owner: d.Get("owner").(string), + CreatedAt: d.Get("created_at").(string), + ID: d.Id(), + }, nil +} + +func resourceViewCreate(d *schema.ResourceData, m interface{}) error { + ctx := context.Background() + cl, err := newClient(m) + if err != nil { + return err + } + view, err := newView(d) + if err != nil { + return err + } + + if _, err := cl.CreateView(ctx, view); err != nil { + return err + } + d.SetId(view.ID) + return nil +} + +func setView(d *schema.ResourceData, view *graylog.View, m interface{}) error { + if err := setStrToRD(d, "title", view.Title); err != nil { + return err + } + if err := setStrToRD(d, "summary", view.Summary); err != nil { + return err + } + if err := setStrToRD(d, "description", view.Description); err != nil { + return err + } + if err := setStrToRD(d, "search_id", view.SearchID); err != nil { + return err + } + if err := setStrToRD(d, "owner", view.Owner); err != nil { + return err + } + if err := setStrToRD(d, "created_at", view.CreatedAt); err != nil { + return err + } + + return nil +} + +func resourceViewRead(d *schema.ResourceData, m interface{}) error { + ctx := context.Background() + cl, err := newClient(m) + if err != nil { + return err + } + view, ei, err := cl.GetView(ctx, d.Id()) + if err != nil { + return handleGetResourceError(d, ei, err) + } + return setView(d, view, m.(*Config)) +} + +func resourceViewUpdate(d *schema.ResourceData, m interface{}) error { + ctx := context.Background() + cl, err := newClient(m) + if err != nil { + return err + } + view, err := newView(d) + if err != nil { + return err + } + if _, err := cl.UpdateView(ctx, view); err != nil { + return err + } + return nil +} + +func resourceViewDelete(d *schema.ResourceData, m interface{}) error { + ctx := context.Background() + cl, err := newClient(m) + if err != nil { + return err + } + if _, err := cl.DeleteView(ctx, d.Id()); err != nil { + return err + } + return nil +} diff --git a/graylog/testdata/view.json b/graylog/testdata/view.json new file mode 100644 index 00000000..11d25ac1 --- /dev/null +++ b/graylog/testdata/view.json @@ -0,0 +1,16 @@ +{ + "id": "5d9620e06df4af000d8ecaae", + "title": "test", + "summary": "", + "description": "", + "search_id": "5d9529b275d97f58f9539275", + "properties": [], + "requires": {}, + "state": {}, + "dashboard_state": { + "widgets": {}, + "positions": {} + }, + "owner": "admin", + "created_at": "2019-10-03T16:25:04.188Z" +} diff --git a/graylog/testdata/views.go b/graylog/testdata/views.go new file mode 100644 index 00000000..3b16872a --- /dev/null +++ b/graylog/testdata/views.go @@ -0,0 +1,121 @@ +package testdata + +import ( + "github.com/suzuki-shunsuke/go-graylog/v11/graylog/graylog" +) + +var ( + Views = &graylog.Views{ + Total: 1, + Page: 1, + PerPage: 50, + Count: 0, + Views: []graylog.View{ + { + ID: "5d9529c175d97f58f953927a", + Title: "test", + Summary: "", + Description: "", + SearchID: "5d9529b275d97f58f9539275", + State: map[string]graylog.ViewState{ + "6971d00a-e605-43fb-b873-e4bca773d286": { + SelectedFields: []string{ + "source", + "message", + }, + Titles: map[string]map[string]string{ + "widget": { + "038b9bca-4884-496f-b1ba-bc345ad4069e": "Message Count", + "c8986792-07e0-41fa-aded-cd19c96f2789": "All Messages", + }, + }, + Widgets: []graylog.ViewWidget{ + { + ID: "038b9bca-4884-496f-b1ba-bc345ad4069e", + Config: graylog.AggregationViewWidgetConfig{ + RowPivots: []graylog.ViewWidgetRowPivot{ + { + Field: "timestamp", + Type: "time", + Config: graylog.ViewWidgetRowPivotConfig{ + Interval: graylog.ViewWidgetRowPivotInterval{ + Type: "auto", + }, + }, + }, + }, + Series: []graylog.ViewWidgetSeries{ + { + Config: graylog.ViewWidgetSeriesConfig{}, + Function: "count()", + }, + }, + Visualization: "bar", + Rollup: true, + }, + }, + { + ID: "c8986792-07e0-41fa-aded-cd19c96f2789", + Config: graylog.MessagesViewWidgetConfig{ + Fields: []string{ + "timestamp", + "source", + }, + ShowMessageRow: true, + }, + }, + { + ID: "41c694c8-093c-4d67-be42-06390e1c61ba", + Config: graylog.AggregationViewWidgetConfig{ + RowPivots: []graylog.ViewWidgetRowPivot{}, + Series: []graylog.ViewWidgetSeries{ + { + Config: graylog.ViewWidgetSeriesConfig{}, + Function: "count()", + }, + }, + Visualization: "numeric", + Rollup: true, + }, + }, + }, + WidgetMapping: map[string][]string{ + "038b9bca-4884-496f-b1ba-bc345ad4069e": { + "9b8a4a7f-9f6e-4032-afa6-e25fe24bab40", + }, + "41c694c8-093c-4d67-be42-06390e1c61ba": { + "81419b6e-4d6d-4739-8883-5d34e5267091", + }, + "c8986792-07e0-41fa-aded-cd19c96f2789": { + "07599874-f01e-46ae-84c4-cf724e7b0524", + }, + }, + Positions: map[string]graylog.ViewWidgetPosition{ + "038b9bca-4884-496f-b1ba-bc345ad4069e": { + Width: "Infinity", + Col: 1, + Row: 5, + Height: 2, + }, + "41c694c8-093c-4d67-be42-06390e1c61ba": { + Width: 4, + Col: 1, + Row: 1, + Height: 4, + }, + "c8986792-07e0-41fa-aded-cd19c96f2789": { + Width: "Infinity", + Col: 1, + Row: 7, + Height: 6, + }, + }, + }, + }, + DashboardState: graylog.DashboardState{}, + Owner: "admin", + CreatedAt: "2019-10-02T22:49:53.181Z", + }, + }, + } +) diff --git a/graylog/testdata/views.json b/graylog/testdata/views.json new file mode 100644 index 00000000..abbd87f9 --- /dev/null +++ b/graylog/testdata/views.json @@ -0,0 +1,139 @@ +{ + "total": 1, + "page": 1, + "per_page": 50, + "count": 0, + "views": [ + { + "id": "5d9529c175d97f58f953927a", + "title": "test", + "summary": "", + "description": "", + "search_id": "5d9529b275d97f58f9539275", + "properties": [], + "requires": {}, + "state": { + "6971d00a-e605-43fb-b873-e4bca773d286": { + "selected_fields": [ + "source", + "message" + ], + "static_message_list_id": null, + "titles": { + "widget": { + "038b9bca-4884-496f-b1ba-bc345ad4069e": "Message Count", + "c8986792-07e0-41fa-aded-cd19c96f2789": "All Messages" + } + }, + "widgets": [ + { + "id": "038b9bca-4884-496f-b1ba-bc345ad4069e", + "type": "aggregation", + "filter": null, + "config": { + "row_pivots": [ + { + "field": "timestamp", + "type": "time", + "config": { + "interval": { + "type": "auto", + "scaling": null + } + } + } + ], + "column_pivots": [], + "series": [ + { + "config": { + "name": null + }, + "function": "count()" + } + ], + "sort": [], + "visualization": "bar", + "visualization_config": null, + "formatting_settings": null, + "rollup": true + } + }, + { + "id": "c8986792-07e0-41fa-aded-cd19c96f2789", + "type": "messages", + "filter": null, + "config": { + "fields": [ + "timestamp", + "source" + ], + "show_message_row": true + } + }, + { + "id": "41c694c8-093c-4d67-be42-06390e1c61ba", + "type": "aggregation", + "filter": null, + "config": { + "row_pivots": [], + "column_pivots": [], + "series": [ + { + "config": { + "name": "Message Count" + }, + "function": "count()" + } + ], + "sort": [], + "visualization": "numeric", + "visualization_config": null, + "formatting_settings": null, + "rollup": true + } + } + ], + "widget_mapping": { + "038b9bca-4884-496f-b1ba-bc345ad4069e": [ + "9b8a4a7f-9f6e-4032-afa6-e25fe24bab40" + ], + "c8986792-07e0-41fa-aded-cd19c96f2789": [ + "07599874-f01e-46ae-84c4-cf724e7b0524" + ], + "41c694c8-093c-4d67-be42-06390e1c61ba": [ + "81419b6e-4d6d-4739-8883-5d34e5267091" + ] + }, + "positions": { + "038b9bca-4884-496f-b1ba-bc345ad4069e": { + "col": 1, + "row": 5, + "height": 2, + "width": "Infinity" + }, + "c8986792-07e0-41fa-aded-cd19c96f2789": { + "col": 1, + "row": 7, + "height": 6, + "width": "Infinity" + }, + "41c694c8-093c-4d67-be42-06390e1c61ba": { + "col": 1, + "row": 1, + "height": 4, + "width": 4 + } + }, + "formatting": null + } + }, + "dashboard_state": { + "widgets": {}, + "positions": {} + }, + "owner": "admin", + "created_at": "2019-10-02T22:49:53.181Z" + } + ] +}