From c315c237b54a03975d785bf0ef2a887154e6861f Mon Sep 17 00:00:00 2001 From: nwneisen Date: Wed, 4 Dec 2019 08:42:42 -0700 Subject: [PATCH] chore: remove sources and fix resulting breaks Signed-off-by: nwneisen --- chronograf/server/config.go | 4 + chronograf/server/mux.go | 48 - chronograf/server/permissions.go | 15 - chronograf/server/server.go | 7 - chronograf/server/sources.go | 1003 ------------- chronograf/server/sources_test.go | 2191 ----------------------------- http/chronograf_handler.go | 105 -- ui/mocks/dummyData.ts | 25 +- ui/src/dashboards/resources.ts | 48 - 9 files changed, 5 insertions(+), 3441 deletions(-) delete mode 100644 chronograf/server/sources.go delete mode 100644 chronograf/server/sources_test.go diff --git a/chronograf/server/config.go b/chronograf/server/config.go index 3747f9bff8b..c33fd2d52f9 100644 --- a/chronograf/server/config.go +++ b/chronograf/server/config.go @@ -12,6 +12,10 @@ type configLinks struct { Auth string `json:"auth"` // Auth link to the auth config endpoint } +type selfLinks struct { + Self string `json:"self"` // Self link mapping to this resource +} + type configResponse struct { Links configLinks `json:"links"` chronograf.Config diff --git a/chronograf/server/mux.go b/chronograf/server/mux.go index 2295ca7c866..606f8d0f83f 100644 --- a/chronograf/server/mux.go +++ b/chronograf/server/mux.go @@ -156,15 +156,6 @@ func NewMux(opts MuxOpts, service Service) http.Handler { router.PUT("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.UpdateMapping)) router.DELETE("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.RemoveMapping)) - // Sources - router.GET("/chronograf/v1/sources", EnsureViewer(service.Sources)) - router.POST("/chronograf/v1/sources", EnsureEditor(service.NewSource)) - - router.GET("/chronograf/v1/sources/:id", EnsureViewer(service.SourcesID)) - router.PATCH("/chronograf/v1/sources/:id", EnsureEditor(service.UpdateSource)) - router.DELETE("/chronograf/v1/sources/:id", EnsureEditor(service.RemoveSource)) - router.GET("/chronograf/v1/sources/:id/health", EnsureViewer(service.SourceHealth)) - // Source Proxy to Influx; Has gzip compression around the handler influx := gziphandler.GzipHandler(http.HandlerFunc(EnsureViewer(service.Influx))) router.Handler("POST", "/chronograf/v1/sources/:id/proxy", influx) @@ -190,22 +181,6 @@ func NewMux(opts MuxOpts, service Service) http.Handler { // All possible permissions for users in this source router.GET("/chronograf/v1/sources/:id/permissions", EnsureViewer(service.Permissions)) - // Users associated with the data source - router.GET("/chronograf/v1/sources/:id/users", EnsureAdmin(service.SourceUsers)) - router.POST("/chronograf/v1/sources/:id/users", EnsureAdmin(service.NewSourceUser)) - - router.GET("/chronograf/v1/sources/:id/users/:uid", EnsureAdmin(service.SourceUserID)) - router.DELETE("/chronograf/v1/sources/:id/users/:uid", EnsureAdmin(service.RemoveSourceUser)) - router.PATCH("/chronograf/v1/sources/:id/users/:uid", EnsureAdmin(service.UpdateSourceUser)) - - // Roles associated with the data source - router.GET("/chronograf/v1/sources/:id/roles", EnsureViewer(service.SourceRoles)) - router.POST("/chronograf/v1/sources/:id/roles", EnsureEditor(service.NewSourceRole)) - - router.GET("/chronograf/v1/sources/:id/roles/:rid", EnsureViewer(service.SourceRoleID)) - router.DELETE("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.RemoveSourceRole)) - router.PATCH("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.UpdateSourceRole)) - // Services are resources that chronograf proxies to router.GET("/chronograf/v1/sources/:id/services", EnsureViewer(service.Services)) router.POST("/chronograf/v1/sources/:id/services", EnsureEditor(service.NewService)) @@ -219,29 +194,6 @@ func NewMux(opts MuxOpts, service Service) http.Handler { router.PATCH("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyPatch)) router.DELETE("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyDelete)) - // Kapacitor - //router.GET("/chronograf/v1/sources/:id/kapacitors", EnsureViewer(service.Kapacitors)) - //router.POST("/chronograf/v1/sources/:id/kapacitors", EnsureEditor(service.NewKapacitor)) - - //router.GET("/chronograf/v1/sources/:id/kapacitors/:kid", EnsureViewer(service.KapacitorsID)) - //router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid", EnsureEditor(service.UpdateKapacitor)) - //router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid", EnsureEditor(service.RemoveKapacitor)) - - //// Kapacitor rules - //router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/rules", EnsureViewer(service.KapacitorRulesGet)) - //router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/rules", EnsureEditor(service.KapacitorRulesPost)) - - //router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureViewer(service.KapacitorRulesID)) - //router.PUT("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesPut)) - //router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesStatus)) - //router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesDelete)) - - //// Kapacitor Proxy - //router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureViewer(service.ProxyGet)) - //router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyPost)) - //router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyPatch)) - //router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyDelete)) - // Layouts router.GET("/chronograf/v1/layouts", EnsureViewer(service.Layouts)) router.GET("/chronograf/v1/layouts/:id", EnsureViewer(service.LayoutsID)) diff --git a/chronograf/server/permissions.go b/chronograf/server/permissions.go index 1df20baca98..145d4bcecc9 100644 --- a/chronograf/server/permissions.go +++ b/chronograf/server/permissions.go @@ -53,18 +53,3 @@ func (s *Service) Permissions(w http.ResponseWriter, r *http.Request) { } encodeJSON(w, http.StatusOK, res, s.Logger) } - -func validPermissions(perms *chronograf.Permissions) error { - if perms == nil { - return nil - } - for _, perm := range *perms { - if perm.Scope != chronograf.AllScope && perm.Scope != chronograf.DBScope { - return fmt.Errorf("invalid permission scope") - } - if perm.Scope == chronograf.DBScope && perm.Name == "" { - return fmt.Errorf("database scoped permission requires a name") - } - } - return nil -} diff --git a/chronograf/server/server.go b/chronograf/server/server.go index f647f30e7b8..d40c8de0d97 100644 --- a/chronograf/server/server.go +++ b/chronograf/server/server.go @@ -338,13 +338,6 @@ func (s *Server) Serve(ctx context.Context) error { service.Env = chronograf.Environment{ TelegrafSystemInterval: s.TelegrafSystemInterval, } - if err := service.HandleNewSources(ctx, s.NewSources); err != nil { - logger. - WithField("component", "server"). - WithField("new-sources", "invalid"). - Error(err) - return err - } if !validBasepath(s.Basepath) { err := fmt.Errorf("invalid basepath, must follow format \"/mybasepath\"") diff --git a/chronograf/server/sources.go b/chronograf/server/sources.go deleted file mode 100644 index 19a53e06d2f..00000000000 --- a/chronograf/server/sources.go +++ /dev/null @@ -1,1003 +0,0 @@ -package server - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/chronograf" - "github.com/influxdata/influxdb/chronograf/enterprise" - "github.com/influxdata/influxdb/chronograf/influx" - "github.com/influxdata/influxdb/chronograf/organizations" -) - -type sourceLinks struct { - Self string `json:"self"` // Self link mapping to this resource - Kapacitors string `json:"kapacitors"` // URL for kapacitors endpoint - Services string `json:"services"` // URL for services endpoint - Proxy string `json:"proxy"` // URL for proxy endpoint - Queries string `json:"queries"` // URL for the queries analysis endpoint - Write string `json:"write"` // URL for the write line-protocol endpoint - Permissions string `json:"permissions"` // URL for all allowed permissions for this source - Users string `json:"users"` // URL for all users associated with this source - Roles string `json:"roles,omitempty"` // URL for all users associated with this source - Databases string `json:"databases"` // URL for the databases contained within this source - Annotations string `json:"annotations"` // URL for the annotations of this source - Health string `json:"health"` // URL for source health -} - -type sourceResponse struct { - chronograf.Source - AuthenticationMethod string `json:"authentication"` - Links sourceLinks `json:"links"` -} - -type authenticationResponse struct { - ID int - AuthenticationMethod string -} - -func sourceAuthenticationMethod(ctx context.Context, src chronograf.Source) authenticationResponse { - ldapEnabled := false - if src.MetaURL != "" { - authorizer := influx.DefaultAuthorization(&src) - metaURL, err := url.Parse(src.MetaURL) - - if err == nil { - client := enterprise.NewMetaClient(metaURL, src.InsecureSkipVerify, authorizer) - config, err := client.GetLDAPConfig(ctx) - - if err == nil { - ldapEnabled = config.Structured.Enabled - } - } - } - - if ldapEnabled { - return authenticationResponse{ID: src.ID, AuthenticationMethod: "ldap"} - } else if src.Username != "" && src.Password != "" { - return authenticationResponse{ID: src.ID, AuthenticationMethod: "basic"} - } else if src.SharedSecret != "" { - return authenticationResponse{ID: src.ID, AuthenticationMethod: "shared"} - } else { - return authenticationResponse{ID: src.ID, AuthenticationMethod: "unknown"} - } -} - -func newSourceResponse(ctx context.Context, src chronograf.Source) sourceResponse { - // If telegraf is not set, we'll set it to the default value. - if src.Telegraf == "" { - src.Telegraf = "telegraf" - } - - authMethod := sourceAuthenticationMethod(ctx, src) - - // Omit the password and shared secret on response - src.Password = "" - src.SharedSecret = "" - - httpAPISrcs := "/chronograf/v1/sources" - res := sourceResponse{ - Source: src, - AuthenticationMethod: authMethod.AuthenticationMethod, - Links: sourceLinks{ - Self: fmt.Sprintf("%s/%d", httpAPISrcs, src.ID), - Kapacitors: fmt.Sprintf("%s/%d/kapacitors", httpAPISrcs, src.ID), - Services: fmt.Sprintf("%s/%d/services", httpAPISrcs, src.ID), - Proxy: fmt.Sprintf("%s/%d/proxy", httpAPISrcs, src.ID), - Queries: fmt.Sprintf("%s/%d/queries", httpAPISrcs, src.ID), - Write: fmt.Sprintf("%s/%d/write", httpAPISrcs, src.ID), - Permissions: fmt.Sprintf("%s/%d/permissions", httpAPISrcs, src.ID), - Users: fmt.Sprintf("%s/%d/users", httpAPISrcs, src.ID), - Databases: fmt.Sprintf("%s/%d/dbs", httpAPISrcs, src.ID), - Annotations: fmt.Sprintf("%s/%d/annotations", httpAPISrcs, src.ID), - Health: fmt.Sprintf("%s/%d/health", httpAPISrcs, src.ID), - }, - } - - // MetaURL is currently a string, but eventually, we'd like to change it - // to a slice. Checking len(src.MetaURL) is functionally equivalent to - // checking if it is equal to the empty string. - if src.Type == chronograf.InfluxEnterprise && len(src.MetaURL) != 0 { - res.Links.Roles = fmt.Sprintf("%s/%d/roles", httpAPISrcs, src.ID) - } - return res -} - -// NewSource adds a new valid source to the store -func (s *Service) NewSource(w http.ResponseWriter, r *http.Request) { - var src chronograf.Source - if err := json.NewDecoder(r.Body).Decode(&src); err != nil { - invalidJSON(w, s.Logger) - return - } - - ctx := r.Context() - defaultOrg, err := s.Store.Organizations(ctx).DefaultOrganization(ctx) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - if err := ValidSourceRequest(&src, defaultOrg.ID); err != nil { - invalidData(w, err, s.Logger) - return - } - - // By default the telegraf database will be telegraf - if src.Telegraf == "" { - src.Telegraf = "telegraf" - } - - dbType, err := s.tsdbType(ctx, &src) - if err != nil { - Error(w, http.StatusBadRequest, "Error contacting source", s.Logger) - return - } - - src.Type = dbType - if src, err = s.Store.Sources(ctx).Add(ctx, src); err != nil { - msg := fmt.Errorf("error storing source %v: %v", src, err) - unknownErrorWithMessage(w, msg, s.Logger) - return - } - - res := newSourceResponse(ctx, src) - location(w, res.Links.Self) - encodeJSON(w, http.StatusCreated, res, s.Logger) -} - -func (s *Service) tsdbType(ctx context.Context, src *chronograf.Source) (string, error) { - cli := &influx.Client{ - Logger: s.Logger, - } - - if err := cli.Connect(ctx, src); err != nil { - return "", err - } - return cli.Type(ctx) -} - -type getSourcesResponse struct { - Sources []sourceResponse `json:"sources"` -} - -// Sources returns all sources from the store. -func (s *Service) Sources(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - srcs, err := s.Store.Sources(ctx).All(ctx) - if err != nil { - Error(w, http.StatusInternalServerError, "Error loading sources", s.Logger) - return - } - - res := getSourcesResponse{ - Sources: make([]sourceResponse, len(srcs)), - } - - sources := make([]sourceResponse, 0) - for _, src := range srcs { - sources = append(sources, newSourceResponse(ctx, src)) - } - - res.Sources = sources - - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// SourcesID retrieves a source from the store -func (s *Service) SourcesID(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - src, err := s.Store.Sources(ctx).Get(ctx, id) - if err != nil { - notFound(w, id, s.Logger) - return - } - - res := newSourceResponse(ctx, src) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// RemoveSource deletes the source from the store -func (s *Service) RemoveSource(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - src := chronograf.Source{ID: id} - ctx := r.Context() - if err = s.Store.Sources(ctx).Delete(ctx, src); err != nil { - if err == chronograf.ErrSourceNotFound { - notFound(w, id, s.Logger) - } else { - unknownErrorWithMessage(w, err, s.Logger) - } - return - } - - // Remove all the associated kapacitors for this source - if err = s.removeSrcsKapa(ctx, id); err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - w.WriteHeader(http.StatusNoContent) -} - -// SourceHealth determines if the tsdb is running -func (s *Service) SourceHealth(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - // TODO(desa): add real support for a default source - if id == 0 { - w.WriteHeader(http.StatusNoContent) - return - } - - ctx := r.Context() - src, err := s.Store.Sources(ctx).Get(ctx, id) - if err != nil { - notFound(w, id, s.Logger) - return - } - - cli := &influx.Client{ - Logger: s.Logger, - } - - if err := cli.Connect(ctx, &src); err != nil { - Error(w, http.StatusBadRequest, "Error contacting source", s.Logger) - return - } - - if err := cli.Ping(ctx); err != nil { - Error(w, http.StatusBadRequest, "Error contacting source", s.Logger) - return - } - - w.WriteHeader(http.StatusNoContent) -} - -// removeSrcsKapa will remove all kapacitors and kapacitor rules from the stores. -// However, it will not remove the kapacitor tickscript from kapacitor itself. -func (s *Service) removeSrcsKapa(ctx context.Context, srcID int) error { - kapas, err := s.Store.Servers(ctx).All(ctx) - if err != nil { - return err - } - - // Filter the kapacitors to delete by matching the source id - deleteKapa := []int{} - for _, kapa := range kapas { - if kapa.SrcID == srcID { - deleteKapa = append(deleteKapa, kapa.ID) - } - } - - for _, kapaID := range deleteKapa { - kapa := chronograf.Server{ - ID: kapaID, - } - s.Logger.Debug("Deleting kapacitor resource id ", kapa.ID) - - if err := s.Store.Servers(ctx).Delete(ctx, kapa); err != nil { - return err - } - } - - return nil -} - -// UpdateSource handles incremental updates of a data source -func (s *Service) UpdateSource(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - src, err := s.Store.Sources(ctx).Get(ctx, id) - if err != nil { - notFound(w, id, s.Logger) - return - } - - var req chronograf.Source - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - src.Default = req.Default - src.InsecureSkipVerify = req.InsecureSkipVerify - if req.Name != "" { - src.Name = req.Name - } - if req.Password != "" { - src.Password = req.Password - } - if req.Username != "" { - src.Username = req.Username - } - if req.URL != "" { - src.URL = req.URL - } - // If the supplied MetaURL is different from the - // one supplied on the request, update the value - if req.MetaURL != src.MetaURL { - src.MetaURL = req.MetaURL - } - if req.Type != "" { - src.Type = req.Type - } - if req.Telegraf != "" { - src.Telegraf = req.Telegraf - } - src.DefaultRP = req.DefaultRP - - defaultOrg, err := s.Store.Organizations(ctx).DefaultOrganization(ctx) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - if err := ValidSourceRequest(&src, defaultOrg.ID); err != nil { - invalidData(w, err, s.Logger) - return - } - - dbType, err := s.tsdbType(ctx, &src) - if err != nil { - Error(w, http.StatusBadRequest, "Error contacting source", s.Logger) - return - } - src.Type = dbType - - if err := s.Store.Sources(ctx).Update(ctx, src); err != nil { - msg := fmt.Sprintf("Error updating source ID %d", id) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - encodeJSON(w, http.StatusOK, newSourceResponse(context.Background(), src), s.Logger) -} - -// ValidSourceRequest checks if name, url, type, and role are valid -func ValidSourceRequest(s *chronograf.Source, defaultOrgID string) error { - if s == nil { - return fmt.Errorf("source must be non-nil") - } - // Name and URL areq required - if s.URL == "" { - return fmt.Errorf("url required") - } - // Type must be influx or influx-enterprise - if s.Type != "" { - if s.Type != chronograf.InfluxDB && s.Type != chronograf.InfluxEnterprise && s.Type != chronograf.InfluxRelay { - return fmt.Errorf("invalid source type %s", s.Type) - } - } - - if s.Organization == "" { - s.Organization = defaultOrgID - } - - url, err := url.ParseRequestURI(s.URL) - if err != nil { - return fmt.Errorf("invalid source URI: %v", err) - } - if len(url.Scheme) == 0 { - return fmt.Errorf("invalid URL; no URL scheme defined") - } - - return nil -} - -// HandleNewSources parses and persists new sources passed in via server flag -func (s *Service) HandleNewSources(ctx context.Context, input string) error { - if input == "" { - return nil - } - - s.Logger.Error("--new-sources is deprecated and will be removed in a future version.") - - var srcsKaps []struct { - Source chronograf.Source `json:"influxdb"` - Kapacitor chronograf.Server `json:"kapacitor"` - } - if err := json.Unmarshal([]byte(input), &srcsKaps); err != nil { - s.Logger. - WithField("component", "server"). - WithField("NewSources", "invalid"). - Error(err) - return err - } - - ctx = context.WithValue(ctx, organizations.ContextKey, "default") - defaultOrg, err := s.Store.Organizations(ctx).DefaultOrganization(ctx) - if err != nil { - return err - } - - for _, sk := range srcsKaps { - if err := ValidSourceRequest(&sk.Source, defaultOrg.ID); err != nil { - return err - } - // Add any new sources and kapacitors as specified via server flag - if err := s.newSourceKapacitor(ctx, sk.Source, sk.Kapacitor); err != nil { - // Continue with server run even if adding NewSource fails - s.Logger. - WithField("component", "server"). - WithField("NewSource", "invalid"). - Error(err) - return err - } - } - return nil -} - -// newSourceKapacitor adds sources to BoltDB idempotently by name, as well as respective kapacitors -func (s *Service) newSourceKapacitor(ctx context.Context, src chronograf.Source, kapa chronograf.Server) error { - srcs, err := s.Store.Sources(ctx).All(ctx) - if err != nil { - return err - } - - for _, source := range srcs { - // If source already exists, do nothing - if source.Name == src.Name { - s.Logger. - WithField("component", "server"). - WithField("NewSource", source.Name). - Info("Source already exists") - return nil - } - } - - src, err = s.Store.Sources(ctx).Add(ctx, src) - if err != nil { - return err - } - - kapa.SrcID = src.ID - if _, err := s.Store.Servers(ctx).Add(ctx, kapa); err != nil { - return err - } - - return nil -} - -// NewSourceUser adds user to source -func (s *Service) NewSourceUser(w http.ResponseWriter, r *http.Request) { - var req sourceUserRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := req.ValidCreate(); err != nil { - invalidData(w, err, s.Logger) - return - } - - ctx := r.Context() - srcID, ts, err := s.sourcesSeries(ctx, w, r) - if err != nil { - return - } - - store := ts.Users(ctx) - user := &chronograf.User{ - Name: req.Username, - Passwd: req.Password, - Permissions: req.Permissions, - Roles: req.Roles, - } - - res, err := store.Add(ctx, user) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - su := newSourceUserResponse(srcID, res.Name).WithPermissions(res.Permissions) - if _, hasRoles := s.hasRoles(ctx, ts); hasRoles { - su.WithRoles(srcID, res.Roles) - } - location(w, su.Links.Self) - encodeJSON(w, http.StatusCreated, su, s.Logger) -} - -// SourceUsers retrieves all users from source. -func (s *Service) SourceUsers(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - srcID, ts, err := s.sourcesSeries(ctx, w, r) - if err != nil { - return - } - - store := ts.Users(ctx) - users, err := store.All(ctx) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - _, hasRoles := s.hasRoles(ctx, ts) - ur := make([]sourceUserResponse, len(users)) - for i, u := range users { - usr := newSourceUserResponse(srcID, u.Name).WithPermissions(u.Permissions) - if hasRoles { - usr.WithRoles(srcID, u.Roles) - } - ur[i] = *usr - } - - res := sourceUsersResponse{ - Users: ur, - } - - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// SourceUserID retrieves a user with ID from store. -// In InfluxDB, a User's Name is their UID, hence the semantic below. -func (s *Service) SourceUserID(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - uid := httprouter.GetParamFromContext(ctx, "uid") - - srcID, ts, err := s.sourcesSeries(ctx, w, r) - if err != nil { - return - } - store := ts.Users(ctx) - u, err := store.Get(ctx, chronograf.UserQuery{Name: &uid}) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - res := newSourceUserResponse(srcID, u.Name).WithPermissions(u.Permissions) - if _, hasRoles := s.hasRoles(ctx, ts); hasRoles { - res.WithRoles(srcID, u.Roles) - } - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// RemoveSourceUser removes the user from the InfluxDB source -func (s *Service) RemoveSourceUser(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - uid := httprouter.GetParamFromContext(ctx, "uid") - - _, store, err := s.sourceUsersStore(ctx, w, r) - if err != nil { - return - } - - if err := store.Delete(ctx, &chronograf.User{Name: uid}); err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - w.WriteHeader(http.StatusNoContent) -} - -// UpdateSourceUser changes the password or permissions of a source user -func (s *Service) UpdateSourceUser(w http.ResponseWriter, r *http.Request) { - var req sourceUserRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - if err := req.ValidUpdate(); err != nil { - invalidData(w, err, s.Logger) - return - } - - ctx := r.Context() - uid := httprouter.GetParamFromContext(ctx, "uid") - srcID, ts, err := s.sourcesSeries(ctx, w, r) - if err != nil { - return - } - - user := &chronograf.User{ - Name: uid, - Passwd: req.Password, - Permissions: req.Permissions, - Roles: req.Roles, - } - store := ts.Users(ctx) - - if err := store.Update(ctx, user); err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - u, err := store.Get(ctx, chronograf.UserQuery{Name: &uid}) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - res := newSourceUserResponse(srcID, u.Name).WithPermissions(u.Permissions) - if _, hasRoles := s.hasRoles(ctx, ts); hasRoles { - res.WithRoles(srcID, u.Roles) - } - location(w, res.Links.Self) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -func (s *Service) sourcesSeries(ctx context.Context, w http.ResponseWriter, r *http.Request) (int, chronograf.TimeSeries, error) { - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return 0, nil, err - } - - src, err := s.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, s.Logger) - return 0, nil, err - } - - ts, err := s.TimeSeries(src) - if err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return 0, nil, err - } - - if err = ts.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return 0, nil, err - } - return srcID, ts, nil -} - -func (s *Service) sourceUsersStore(ctx context.Context, w http.ResponseWriter, r *http.Request) (int, chronograf.UsersStore, error) { - srcID, ts, err := s.sourcesSeries(ctx, w, r) - if err != nil { - return 0, nil, err - } - - store := ts.Users(ctx) - return srcID, store, nil -} - -// hasRoles checks if the influx source has roles or not -func (s *Service) hasRoles(ctx context.Context, ts chronograf.TimeSeries) (chronograf.RolesStore, bool) { - store, err := ts.Roles(ctx) - if err != nil { - return nil, false - } - return store, true -} - -type sourceUserRequest struct { - Username string `json:"name,omitempty"` // Username for new account - Password string `json:"password,omitempty"` // Password for new account - Permissions chronograf.Permissions `json:"permissions,omitempty"` // Optional permissions - Roles []chronograf.Role `json:"roles,omitempty"` // Optional roles -} - -func (r *sourceUserRequest) ValidCreate() error { - if r.Username == "" { - return fmt.Errorf("username required") - } - if r.Password == "" { - return fmt.Errorf("password required") - } - return validPermissions(&r.Permissions) -} - -type sourceUsersResponse struct { - Users []sourceUserResponse `json:"users"` -} - -func (r *sourceUserRequest) ValidUpdate() error { - if r.Password == "" && r.Permissions == nil && r.Roles == nil { - return fmt.Errorf("no fields to update") - } - return validPermissions(&r.Permissions) -} - -type sourceUserResponse struct { - Name string // Username for new account - Permissions chronograf.Permissions // Account's permissions - Roles []sourceRoleResponse // Roles if source uses them - Links selfLinks // Links are URI locations related to user - hasPermissions bool - hasRoles bool -} - -func (u *sourceUserResponse) MarshalJSON() ([]byte, error) { - res := map[string]interface{}{ - "name": u.Name, - "links": u.Links, - } - if u.hasRoles { - res["roles"] = u.Roles - } - if u.hasPermissions { - res["permissions"] = u.Permissions - } - return json.Marshal(res) -} - -// newSourceUserResponse creates an HTTP JSON response for a user w/o roles -func newSourceUserResponse(srcID int, name string) *sourceUserResponse { - self := newSelfLinks(srcID, "users", name) - return &sourceUserResponse{ - Name: name, - Links: self, - } -} - -func (u *sourceUserResponse) WithPermissions(perms chronograf.Permissions) *sourceUserResponse { - u.hasPermissions = true - if perms == nil { - perms = make(chronograf.Permissions, 0) - } - u.Permissions = perms - return u -} - -// WithRoles adds roles to the HTTP JSON response for a user -func (u *sourceUserResponse) WithRoles(srcID int, roles []chronograf.Role) *sourceUserResponse { - u.hasRoles = true - rr := make([]sourceRoleResponse, len(roles)) - for i, role := range roles { - rr[i] = newSourceRoleResponse(srcID, &role) - } - u.Roles = rr - return u -} - -type selfLinks struct { - Self string `json:"self"` // Self link mapping to this resource -} - -func newSelfLinks(id int, parent, resource string) selfLinks { - httpAPISrcs := "/chronograf/v1/sources" - u := &url.URL{Path: resource} - encodedResource := u.String() - return selfLinks{ - Self: fmt.Sprintf("%s/%d/%s/%s", httpAPISrcs, id, parent, encodedResource), - } -} - -// NewSourceRole adds role to source -func (s *Service) NewSourceRole(w http.ResponseWriter, r *http.Request) { - var req sourceRoleRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := req.ValidCreate(); err != nil { - invalidData(w, err, s.Logger) - return - } - - ctx := r.Context() - srcID, ts, err := s.sourcesSeries(ctx, w, r) - if err != nil { - return - } - - roles, ok := s.hasRoles(ctx, ts) - if !ok { - Error(w, http.StatusNotFound, fmt.Sprintf("Source %d does not have role capability", srcID), s.Logger) - return - } - - if _, err := roles.Get(ctx, req.Name); err == nil { - Error(w, http.StatusBadRequest, fmt.Sprintf("Source %d already has role %s", srcID, req.Name), s.Logger) - return - } - - res, err := roles.Add(ctx, &req.Role) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - rr := newSourceRoleResponse(srcID, res) - location(w, rr.Links.Self) - encodeJSON(w, http.StatusCreated, rr, s.Logger) -} - -// UpdateSourceRole changes the permissions or users of a role -func (s *Service) UpdateSourceRole(w http.ResponseWriter, r *http.Request) { - var req sourceRoleRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - if err := req.ValidUpdate(); err != nil { - invalidData(w, err, s.Logger) - return - } - - ctx := r.Context() - srcID, ts, err := s.sourcesSeries(ctx, w, r) - if err != nil { - return - } - - roles, ok := s.hasRoles(ctx, ts) - if !ok { - Error(w, http.StatusNotFound, fmt.Sprintf("Source %d does not have role capability", srcID), s.Logger) - return - } - - rid := httprouter.GetParamFromContext(ctx, "rid") - req.Name = rid - - if err := roles.Update(ctx, &req.Role); err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - role, err := roles.Get(ctx, req.Name) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - rr := newSourceRoleResponse(srcID, role) - location(w, rr.Links.Self) - encodeJSON(w, http.StatusOK, rr, s.Logger) -} - -// SourceRoleID retrieves a role with ID from store. -func (s *Service) SourceRoleID(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - srcID, ts, err := s.sourcesSeries(ctx, w, r) - if err != nil { - return - } - - roles, ok := s.hasRoles(ctx, ts) - if !ok { - Error(w, http.StatusNotFound, fmt.Sprintf("Source %d does not have role capability", srcID), s.Logger) - return - } - - rid := httprouter.GetParamFromContext(ctx, "rid") - role, err := roles.Get(ctx, rid) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - rr := newSourceRoleResponse(srcID, role) - encodeJSON(w, http.StatusOK, rr, s.Logger) -} - -// SourceRoles retrieves all roles from the store -func (s *Service) SourceRoles(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - srcID, ts, err := s.sourcesSeries(ctx, w, r) - if err != nil { - return - } - - store, ok := s.hasRoles(ctx, ts) - if !ok { - Error(w, http.StatusNotFound, fmt.Sprintf("Source %d does not have role capability", srcID), s.Logger) - return - } - - roles, err := store.All(ctx) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - rr := make([]sourceRoleResponse, len(roles)) - for i, role := range roles { - rr[i] = newSourceRoleResponse(srcID, &role) - } - - res := struct { - Roles []sourceRoleResponse `json:"roles"` - }{rr} - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// RemoveSourceRole removes role from data source. -func (s *Service) RemoveSourceRole(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - srcID, ts, err := s.sourcesSeries(ctx, w, r) - if err != nil { - return - } - - roles, ok := s.hasRoles(ctx, ts) - if !ok { - Error(w, http.StatusNotFound, fmt.Sprintf("Source %d does not have role capability", srcID), s.Logger) - return - } - - rid := httprouter.GetParamFromContext(ctx, "rid") - if err := roles.Delete(ctx, &chronograf.Role{Name: rid}); err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - w.WriteHeader(http.StatusNoContent) -} - -// sourceRoleRequest is the format used for both creating and updating roles -type sourceRoleRequest struct { - chronograf.Role -} - -func (r *sourceRoleRequest) ValidCreate() error { - if r.Name == "" || len(r.Name) > 254 { - return fmt.Errorf("name is required for a role") - } - for _, user := range r.Users { - if user.Name == "" { - return fmt.Errorf("username required") - } - } - return validPermissions(&r.Permissions) -} - -func (r *sourceRoleRequest) ValidUpdate() error { - if len(r.Name) > 254 { - return fmt.Errorf("username too long; must be less than 254 characters") - } - for _, user := range r.Users { - if user.Name == "" { - return fmt.Errorf("username required") - } - } - return validPermissions(&r.Permissions) -} - -type sourceRoleResponse struct { - Users []*sourceUserResponse `json:"users"` - Name string `json:"name"` - Permissions chronograf.Permissions `json:"permissions"` - Links selfLinks `json:"links"` -} - -func newSourceRoleResponse(srcID int, res *chronograf.Role) sourceRoleResponse { - su := make([]*sourceUserResponse, len(res.Users)) - for i := range res.Users { - name := res.Users[i].Name - su[i] = newSourceUserResponse(srcID, name) - } - - if res.Permissions == nil { - res.Permissions = make(chronograf.Permissions, 0) - } - return sourceRoleResponse{ - Name: res.Name, - Permissions: res.Permissions, - Users: su, - Links: newSelfLinks(srcID, "roles", res.Name), - } -} diff --git a/chronograf/server/sources_test.go b/chronograf/server/sources_test.go deleted file mode 100644 index fbede81bcd0..00000000000 --- a/chronograf/server/sources_test.go +++ /dev/null @@ -1,2191 +0,0 @@ -package server - -import ( - "bytes" - "context" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "reflect" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/chronograf" - "github.com/influxdata/influxdb/chronograf/mocks" - "github.com/influxdata/httprouter" -) - -func Test_ValidSourceRequest(t *testing.T) { - type args struct { - source *chronograf.Source - defaultOrgID string - } - type wants struct { - err error - source *chronograf.Source - } - tests := []struct { - name string - args args - wants wants - }{ - { - name: "nil source", - args: args{}, - wants: wants{ - err: fmt.Errorf("source must be non-nil"), - }, - }, - { - name: "missing url", - args: args{ - source: &chronograf.Source{ - ID: 1, - Name: "I'm a really great source", - Type: chronograf.InfluxDB, - Username: "fancy", - Password: "i'm so", - SharedSecret: "supersecret", - MetaURL: "http://www.so.meta.com", - InsecureSkipVerify: true, - Default: true, - Telegraf: "telegraf", - Organization: "0", - }, - }, - wants: wants{ - err: fmt.Errorf("url required"), - }, - }, - { - name: "invalid source type", - args: args{ - source: &chronograf.Source{ - ID: 1, - Name: "I'm a really great source", - Type: "non-existent-type", - Username: "fancy", - Password: "i'm so", - SharedSecret: "supersecret", - URL: "http://www.any.url.com", - MetaURL: "http://www.so.meta.com", - InsecureSkipVerify: true, - Default: true, - Telegraf: "telegraf", - Organization: "0", - }, - }, - wants: wants{ - err: fmt.Errorf("invalid source type non-existent-type"), - }, - }, - { - name: "set organization to be default org if not specified", - args: args{ - defaultOrgID: "2", - source: &chronograf.Source{ - ID: 1, - Name: "I'm a really great source", - Type: chronograf.InfluxDB, - Username: "fancy", - Password: "i'm so", - SharedSecret: "supersecret", - URL: "http://www.any.url.com", - MetaURL: "http://www.so.meta.com", - InsecureSkipVerify: true, - Default: true, - Telegraf: "telegraf", - }, - }, - wants: wants{ - source: &chronograf.Source{ - ID: 1, - Name: "I'm a really great source", - Type: chronograf.InfluxDB, - Username: "fancy", - Password: "i'm so", - SharedSecret: "supersecret", - URL: "http://www.any.url.com", - MetaURL: "http://www.so.meta.com", - InsecureSkipVerify: true, - Default: true, - Organization: "2", - Telegraf: "telegraf", - }, - }, - }, - { - name: "bad url", - args: args{ - source: &chronograf.Source{ - ID: 1, - Name: "I'm a really great source", - Type: chronograf.InfluxDB, - Username: "fancy", - Password: "i'm so", - SharedSecret: "supersecret", - URL: "im a bad url", - MetaURL: "http://www.so.meta.com", - InsecureSkipVerify: true, - Organization: "0", - Default: true, - Telegraf: "telegraf", - }, - }, - wants: wants{ - err: fmt.Errorf("invalid source URI: parse im a bad url: invalid URI for request"), - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidSourceRequest(tt.args.source, tt.args.defaultOrgID) - if err == nil && tt.wants.err == nil { - if diff := cmp.Diff(tt.args.source, tt.wants.source); diff != "" { - t.Errorf("%q. ValidSourceRequest():\n-got/+want\ndiff %s", tt.name, diff) - } - return - } - if err.Error() != tt.wants.err.Error() { - t.Errorf("%q. ValidSourceRequest() = %q, want %q", tt.name, err, tt.wants.err) - } - }) - } -} - -func Test_newSourceResponse(t *testing.T) { - tests := []struct { - name string - src chronograf.Source - want sourceResponse - }{ - { - name: "Test empty telegraf", - src: chronograf.Source{ - ID: 1, - Telegraf: "", - }, - want: sourceResponse{ - Source: chronograf.Source{ - ID: 1, - Telegraf: "telegraf", - }, - AuthenticationMethod: "unknown", - Links: sourceLinks{ - Self: "/chronograf/v1/sources/1", - Services: "/chronograf/v1/sources/1/services", - Proxy: "/chronograf/v1/sources/1/proxy", - Queries: "/chronograf/v1/sources/1/queries", - Write: "/chronograf/v1/sources/1/write", - Kapacitors: "/chronograf/v1/sources/1/kapacitors", - Users: "/chronograf/v1/sources/1/users", - Permissions: "/chronograf/v1/sources/1/permissions", - Databases: "/chronograf/v1/sources/1/dbs", - Annotations: "/chronograf/v1/sources/1/annotations", - Health: "/chronograf/v1/sources/1/health", - }, - }, - }, - { - name: "Test non-default telegraf", - src: chronograf.Source{ - ID: 1, - Telegraf: "howdy", - }, - want: sourceResponse{ - Source: chronograf.Source{ - ID: 1, - Telegraf: "howdy", - }, - AuthenticationMethod: "unknown", - Links: sourceLinks{ - Self: "/chronograf/v1/sources/1", - Proxy: "/chronograf/v1/sources/1/proxy", - Services: "/chronograf/v1/sources/1/services", - Queries: "/chronograf/v1/sources/1/queries", - Write: "/chronograf/v1/sources/1/write", - Kapacitors: "/chronograf/v1/sources/1/kapacitors", - Users: "/chronograf/v1/sources/1/users", - Permissions: "/chronograf/v1/sources/1/permissions", - Databases: "/chronograf/v1/sources/1/dbs", - Annotations: "/chronograf/v1/sources/1/annotations", - Health: "/chronograf/v1/sources/1/health", - }, - }, - }, - } - for _, tt := range tests { - if got := newSourceResponse(context.Background(), tt.src); !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. newSourceResponse() = %v, want %v", tt.name, got, tt.want) - } - } -} - -func TestService_newSourceKapacitor(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - ServersStore chronograf.ServersStore - Logger chronograf.Logger - } - type args struct { - ctx context.Context - src chronograf.Source - kapa chronograf.Server - } - srcCount := 0 - srvCount := 0 - tests := []struct { - name string - fields fields - args args - wantSrc int - wantSrv int - wantErr bool - }{ - { - name: "Add when no existing sources", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return []chronograf.Source{}, nil - }, - AddF: func(ctx context.Context, src chronograf.Source) (chronograf.Source, error) { - srcCount++ - src.ID = srcCount - return src, nil - }, - }, - ServersStore: &mocks.ServersStore{ - AddF: func(ctx context.Context, srv chronograf.Server) (chronograf.Server, error) { - srvCount++ - return srv, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - src: chronograf.Source{ - Name: "Influx 1", - }, - kapa: chronograf.Server{ - Name: "Kapa 1", - }, - }, - wantSrc: 1, - wantSrv: 1, - }, - { - name: "Should not add if existing source", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return []chronograf.Source{ - { - Name: "Influx 1", - }, - }, nil - }, - AddF: func(ctx context.Context, src chronograf.Source) (chronograf.Source, error) { - srcCount++ - src.ID = srcCount - return src, nil - }, - }, - ServersStore: &mocks.ServersStore{ - AddF: func(ctx context.Context, srv chronograf.Server) (chronograf.Server, error) { - srvCount++ - return srv, nil - }, - }, - Logger: &mocks.TestLogger{}, - }, - args: args{ - ctx: context.Background(), - src: chronograf.Source{ - Name: "Influx 1", - }, - kapa: chronograf.Server{ - Name: "Kapa 1", - }, - }, - wantSrc: 0, - wantSrv: 0, - }, - { - name: "Error if All returns error", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return nil, fmt.Errorf("error") - }, - }, - Logger: &mocks.TestLogger{}, - }, - args: args{ - ctx: context.Background(), - }, - wantErr: true, - }, - { - name: "Error if Add returns error", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return []chronograf.Source{}, nil - }, - AddF: func(ctx context.Context, src chronograf.Source) (chronograf.Source, error) { - return chronograf.Source{}, fmt.Errorf("error") - }, - }, - Logger: &mocks.TestLogger{}, - }, - args: args{ - ctx: context.Background(), - }, - wantErr: true, - }, - { - name: "Error if kapa add is error", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return []chronograf.Source{}, nil - }, - AddF: func(ctx context.Context, src chronograf.Source) (chronograf.Source, error) { - srcCount++ - src.ID = srcCount - return src, nil - }, - }, - ServersStore: &mocks.ServersStore{ - AddF: func(ctx context.Context, srv chronograf.Server) (chronograf.Server, error) { - srvCount++ - return chronograf.Server{}, fmt.Errorf("error") - }, - }, - Logger: &mocks.TestLogger{}, - }, - args: args{ - ctx: context.Background(), - src: chronograf.Source{ - Name: "Influx 1", - }, - kapa: chronograf.Server{ - Name: "Kapa 1", - }, - }, - wantSrc: 1, - wantSrv: 1, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - srcCount = 0 - srvCount = 0 - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - ServersStore: tt.fields.ServersStore, - }, - Logger: tt.fields.Logger, - } - if err := h.newSourceKapacitor(tt.args.ctx, tt.args.src, tt.args.kapa); (err != nil) != tt.wantErr { - t.Errorf("Service.newSourceKapacitor() error = %v, wantErr %v", err, tt.wantErr) - } - if tt.wantSrc != srcCount { - t.Errorf("Service.newSourceKapacitor() count = %d, wantSrc %d", srcCount, tt.wantSrc) - } - }) - } -} - -func TestService_SourcesID(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - args args - fields fields - ID string - wantStatusCode int - wantContentType string - wantBody string - }{ - { - name: "Get source without defaultRP includes empty defaultRP in response", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", - nil, - ), - }, - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - ID: "1", - wantStatusCode: 200, - wantContentType: "application/json", - wantBody: `{"id":"1","name":"","url":"","default":false,"telegraf":"telegraf","organization":"","defaultRP":"","authentication":"unknown","links":{"self":"/chronograf/v1/sources/1","kapacitors":"/chronograf/v1/sources/1/kapacitors","services":"/chronograf/v1/sources/1/services","proxy":"/chronograf/v1/sources/1/proxy","queries":"/chronograf/v1/sources/1/queries","write":"/chronograf/v1/sources/1/write","permissions":"/chronograf/v1/sources/1/permissions","users":"/chronograf/v1/sources/1/users","databases":"/chronograf/v1/sources/1/dbs","annotations":"/chronograf/v1/sources/1/annotations","health":"/chronograf/v1/sources/1/health"}} -`, - }, - } - for _, tt := range tests { - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - Logger: tt.fields.Logger, - } - h.SourcesID(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - contentType := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatusCode { - t.Errorf("%q. SourcesID() = got %v, want %v", tt.name, resp.StatusCode, tt.wantStatusCode) - } - if tt.wantContentType != "" && contentType != tt.wantContentType { - t.Errorf("%q. SourcesID() = got %v, want %v", tt.name, contentType, tt.wantContentType) - } - if tt.wantBody != "" && string(body) != tt.wantBody { - t.Errorf("%q. SourcesID() =\ngot ***%v***\nwant ***%v***\n", tt.name, string(body), tt.wantBody) - } - - } -} -func TestService_UpdateSource(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - OrganizationsStore chronograf.OrganizationsStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - args args - fields fields - ID string - wantStatusCode int - wantContentType string - wantBody func(string) string - }{ - { - name: "Update source updates fields", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "PATCH", - "http://any.url", - nil), - }, - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - }, nil - }, - UpdateF: func(ctx context.Context, upd chronograf.Source) error { - return nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "pineapple_kingdom", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - ID: "1", - wantStatusCode: 200, - wantContentType: "application/json", - wantBody: func(url string) string { - return fmt.Sprintf(`{"id":"1","name":"marty","type":"influx","username":"bob","url":"%s","metaUrl":"http://murl","default":false,"telegraf":"murlin","organization":"1337","defaultRP":"pineapple","authentication":"basic","links":{"self":"/chronograf/v1/sources/1","kapacitors":"/chronograf/v1/sources/1/kapacitors","services":"/chronograf/v1/sources/1/services","proxy":"/chronograf/v1/sources/1/proxy","queries":"/chronograf/v1/sources/1/queries","write":"/chronograf/v1/sources/1/write","permissions":"/chronograf/v1/sources/1/permissions","users":"/chronograf/v1/sources/1/users","databases":"/chronograf/v1/sources/1/dbs","annotations":"/chronograf/v1/sources/1/annotations","health":"/chronograf/v1/sources/1/health"}} -`, url) - }, - }, - } - for _, tt := range tests { - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - OrganizationsStore: tt.fields.OrganizationsStore, - }, - Logger: tt.fields.Logger, - } - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNoContent) - w.Header().Set("X-Influxdb-Build", "ENT") - })) - defer ts.Close() - - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - tt.args.r.Body = ioutil.NopCloser( - bytes.NewReader([]byte( - fmt.Sprintf(`{"name":"marty","password":"the_lake","username":"bob","type":"influx","telegraf":"murlin","defaultRP":"pineapple","url":"%s","metaUrl":"http://murl"}`, ts.URL)), - ), - ) - h.UpdateSource(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - contentType := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatusCode { - t.Errorf("%q. UpdateSource() = got %v, want %v", tt.name, resp.StatusCode, tt.wantStatusCode) - } - if contentType != tt.wantContentType { - t.Errorf("%q. UpdateSource() = got %v, want %v", tt.name, contentType, tt.wantContentType) - } - wantBody := tt.wantBody(ts.URL) - if string(body) != wantBody { - t.Errorf("%q. UpdateSource() =\ngot ***%v***\nwant ***%v***\n", tt.name, string(body), wantBody) - } - } -} - -func TestService_NewSourceUser(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - ID string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "New user for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - UsersF: func(ctx context.Context) chronograf.UsersStore { - return &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - } - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return nil, fmt.Errorf("no roles") - }, - }, - }, - ID: "1", - wantStatus: http.StatusCreated, - wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/sources/1/users/marty"},"name":"marty","permissions":[]} -`, - }, - { - name: "New user for data source with roles", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - UsersF: func(ctx context.Context) chronograf.UsersStore { - return &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - } - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return nil, nil - }, - }, - }, - ID: "1", - wantStatus: http.StatusCreated, - wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/sources/1/users/marty"},"name":"marty","permissions":[],"roles":[]} -`, - }, - { - name: "Error adding user", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - UsersF: func(ctx context.Context) chronograf.UsersStore { - return &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return nil, fmt.Errorf("weight Has Nothing to Do With It") - }, - } - }, - }, - }, - ID: "1", - wantStatus: http.StatusBadRequest, - wantContentType: "application/json", - wantBody: `{"code":400,"message":"weight Has Nothing to Do With It"}`, - }, - { - name: "Failure connecting to user store", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return fmt.Errorf("my supervisor is Biff") - }, - }, - }, - ID: "1", - wantStatus: http.StatusBadRequest, - wantContentType: "application/json", - wantBody: `{"code":400,"message":"unable to connect to source 1: my supervisor is Biff"}`, - }, - { - name: "Failure getting source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{}, fmt.Errorf("no McFly ever amounted to anything in the history of Hill Valley") - }, - }, - }, - ID: "1", - wantStatus: http.StatusNotFound, - wantContentType: "application/json", - wantBody: `{"code":404,"message":"ID 1 not found"}`, - }, - { - name: "Bad ID", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - }, - ID: "BAD", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"error converting ID BAD"}`, - }, - { - name: "Bad name", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"password": "the_lake"}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - }, - ID: "BAD", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"username required"}`, - }, - { - name: "Bad JSON", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{password}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - }, - ID: "BAD", - wantStatus: http.StatusBadRequest, - wantContentType: "application/json", - wantBody: `{"code":400,"message":"unparsable JSON"}`, - }, - } - for _, tt := range tests { - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - } - - h.NewSourceUser(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. NewSourceUser() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. NewSourceUser() = %v, want %v", tt.name, content, tt.wantContentType) - } - if tt.wantBody != "" && string(body) != tt.wantBody { - t.Errorf("%q. NewSourceUser() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} - -func TestService_SourceUsers(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - ID string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "All users for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://local/chronograf/v1/sources/1", - nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return nil, fmt.Errorf("no roles") - }, - UsersF: func(ctx context.Context) chronograf.UsersStore { - return &mocks.UsersStore{ - AllF: func(ctx context.Context) ([]chronograf.User, error) { - return []chronograf.User{ - { - Name: "strickland", - Passwd: "discipline", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"READ"}, - }, - }, - }, - }, nil - }, - } - }, - }, - }, - ID: "1", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"users":[{"links":{"self":"/chronograf/v1/sources/1/users/strickland"},"name":"strickland","permissions":[{"scope":"all","allowed":["READ"]}]}]} -`, - }, - { - name: "All users for data source with roles", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://local/chronograf/v1/sources/1", - nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return nil, nil - }, - UsersF: func(ctx context.Context) chronograf.UsersStore { - return &mocks.UsersStore{ - AllF: func(ctx context.Context) ([]chronograf.User, error) { - return []chronograf.User{ - { - Name: "strickland", - Passwd: "discipline", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"READ"}, - }, - }, - }, - }, nil - }, - } - }, - }, - }, - ID: "1", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"users":[{"links":{"self":"/chronograf/v1/sources/1/users/strickland"},"name":"strickland","permissions":[{"scope":"all","allowed":["READ"]}],"roles":[]}]} -`, - }, - } - for _, tt := range tests { - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - } - - h.SourceUsers(tt.args.w, tt.args.r) - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. SourceUsers() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. SourceUsers() = %v, want %v", tt.name, content, tt.wantContentType) - } - if tt.wantBody != "" && string(body) != tt.wantBody { - t.Errorf("%q. SourceUsers() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} - -func TestService_SourceUserID(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - ID string - UID string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Single user for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://local/chronograf/v1/sources/1", - nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return nil, fmt.Errorf("no roles") - }, - UsersF: func(ctx context.Context) chronograf.UsersStore { - return &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - Name: "strickland", - Passwd: "discipline", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"READ"}, - }, - }, - }, nil - }, - } - }, - }, - }, - ID: "1", - UID: "strickland", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/sources/1/users/strickland"},"name":"strickland","permissions":[{"scope":"all","allowed":["READ"]}]} -`, - }, - { - name: "Single user for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://local/chronograf/v1/sources/1", - nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return nil, nil - }, - UsersF: func(ctx context.Context) chronograf.UsersStore { - return &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - Name: "strickland", - Passwd: "discipline", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"READ"}, - }, - }, - }, nil - }, - } - }, - }, - }, - ID: "1", - UID: "strickland", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/sources/1/users/strickland"},"name":"strickland","permissions":[{"scope":"all","allowed":["READ"]}],"roles":[]} -`, - }, - } - for _, tt := range tests { - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - } - - h.SourceUserID(tt.args.w, tt.args.r) - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. SourceUserID() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. SourceUserID() = %v, want %v", tt.name, content, tt.wantContentType) - } - if tt.wantBody != "" && string(body) != tt.wantBody { - t.Errorf("%q. SourceUserID() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} - -func TestService_RemoveSourceUser(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - ID string - UID string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Delete user for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://local/chronograf/v1/sources/1", - nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - UsersF: func(ctx context.Context) chronograf.UsersStore { - return &mocks.UsersStore{ - DeleteF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - } - }, - }, - }, - ID: "1", - UID: "strickland", - wantStatus: http.StatusNoContent, - }, - } - for _, tt := range tests { - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - } - h.RemoveSourceUser(tt.args.w, tt.args.r) - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. RemoveSourceUser() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. RemoveSourceUser() = %v, want %v", tt.name, content, tt.wantContentType) - } - if tt.wantBody != "" && string(body) != tt.wantBody { - t.Errorf("%q. RemoveSourceUser() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} - -func TestService_UpdateSourceUser(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - ID string - UID string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Update user password for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return nil, fmt.Errorf("no roles") - }, - UsersF: func(ctx context.Context) chronograf.UsersStore { - return &mocks.UsersStore{ - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - Name: "marty", - }, nil - }, - } - }, - }, - }, - ID: "1", - UID: "marty", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/sources/1/users/marty"},"name":"marty","permissions":[]} -`, - }, - { - name: "Update user password for data source with roles", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return nil, nil - }, - UsersF: func(ctx context.Context) chronograf.UsersStore { - return &mocks.UsersStore{ - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - Name: "marty", - }, nil - }, - } - }, - }, - }, - ID: "1", - UID: "marty", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/sources/1/users/marty"},"name":"marty","permissions":[],"roles":[]} -`, - }, - { - name: "Invalid update JSON", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "marty"}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - }, - ID: "1", - UID: "marty", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"no fields to update"}`, - }, - } - for _, tt := range tests { - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - } - h.UpdateSourceUser(tt.args.w, tt.args.r) - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. UpdateSourceUser() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. UpdateSourceUser() = %v, want %v", tt.name, content, tt.wantContentType) - } - if tt.wantBody != "" && string(body) != tt.wantBody { - t.Errorf("%q. UpdateSourceUser() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} - -func TestService_NewSourceRole(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - ID string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Bad JSON", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://server.local/chronograf/v1/sources/1/roles", - ioutil.NopCloser( - bytes.NewReader([]byte(`{BAD}`)))), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - }, - wantStatus: http.StatusBadRequest, - wantContentType: "application/json", - wantBody: `{"code":400,"message":"unparsable JSON"}`, - }, - { - name: "Invalid request", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://server.local/chronograf/v1/sources/1/roles", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": ""}`)))), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - }, - ID: "1", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"name is required for a role"}`, - }, - { - name: "Invalid source ID", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://server.local/chronograf/v1/sources/1/roles", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "newrole"}`)))), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - }, - ID: "BADROLE", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"error converting ID BADROLE"}`, - }, - { - name: "Source doesn't support roles", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://server.local/chronograf/v1/sources/1/roles", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "role"}`)))), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return nil, fmt.Errorf("roles not supported") - }, - }, - }, - ID: "1", - wantStatus: http.StatusNotFound, - wantContentType: "application/json", - wantBody: `{"code":404,"message":"Source 1 does not have role capability"}`, - }, - { - name: "Unable to add role to server", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://server.local/chronograf/v1/sources/1/roles", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "role"}`)))), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return &mocks.RolesStore{ - AddF: func(ctx context.Context, u *chronograf.Role) (*chronograf.Role, error) { - return nil, fmt.Errorf("server had and issue") - }, - GetF: func(ctx context.Context, name string) (*chronograf.Role, error) { - return nil, fmt.Errorf("no such role") - }, - }, nil - }, - }, - }, - ID: "1", - wantStatus: http.StatusBadRequest, - wantContentType: "application/json", - wantBody: `{"code":400,"message":"server had and issue"}`, - }, - { - name: "New role for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://server.local/chronograf/v1/sources/1/roles", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "biffsgang","users": [{"name": "match"},{"name": "skinhead"},{"name": "3-d"}]}`)))), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return &mocks.RolesStore{ - AddF: func(ctx context.Context, u *chronograf.Role) (*chronograf.Role, error) { - return u, nil - }, - GetF: func(ctx context.Context, name string) (*chronograf.Role, error) { - return nil, fmt.Errorf("no such role") - }, - }, nil - }, - }, - }, - ID: "1", - wantStatus: http.StatusCreated, - wantContentType: "application/json", - wantBody: `{"users":[{"links":{"self":"/chronograf/v1/sources/1/users/match"},"name":"match"},{"links":{"self":"/chronograf/v1/sources/1/users/skinhead"},"name":"skinhead"},{"links":{"self":"/chronograf/v1/sources/1/users/3-d"},"name":"3-d"}],"name":"biffsgang","permissions":[],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}} -`, - }, - } - for _, tt := range tests { - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - Logger: tt.fields.Logger, - } - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - - h.NewSourceRole(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. NewSourceRole() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. NewSourceRole() = %v, want %v", tt.name, content, tt.wantContentType) - } - if tt.wantBody != "" && string(body) != tt.wantBody { - t.Errorf("%q. NewSourceRole() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} - -func TestService_UpdateSourceRole(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - ID string - RoleID string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Update role for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://server.local/chronograf/v1/sources/1/roles", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "biffsgang","users": [{"name": "match"},{"name": "skinhead"},{"name": "3-d"}]}`)))), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return &mocks.RolesStore{ - UpdateF: func(ctx context.Context, u *chronograf.Role) error { - return nil - }, - GetF: func(ctx context.Context, name string) (*chronograf.Role, error) { - return &chronograf.Role{ - Name: "biffsgang", - Users: []chronograf.User{ - { - Name: "match", - }, - { - Name: "skinhead", - }, - { - Name: "3-d", - }, - }, - }, nil - }, - }, nil - }, - }, - }, - ID: "1", - RoleID: "biffsgang", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"users":[{"links":{"self":"/chronograf/v1/sources/1/users/match"},"name":"match"},{"links":{"self":"/chronograf/v1/sources/1/users/skinhead"},"name":"skinhead"},{"links":{"self":"/chronograf/v1/sources/1/users/3-d"},"name":"3-d"}],"name":"biffsgang","permissions":[],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}} -`, - }, - } - for _, tt := range tests { - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - Logger: tt.fields.Logger, - } - - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - - h.UpdateSourceRole(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. UpdateSourceRole() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. UpdateSourceRole() = %v, want %v", tt.name, content, tt.wantContentType) - } - if tt.wantBody != "" && string(body) != tt.wantBody { - t.Errorf("%q. UpdateSourceRole() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} - -func TestService_SourceRoleID(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - ID string - RoleID string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Get role for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://server.local/chronograf/v1/sources/1/roles/biffsgang", - nil), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return &mocks.RolesStore{ - GetF: func(ctx context.Context, name string) (*chronograf.Role, error) { - return &chronograf.Role{ - Name: "biffsgang", - Permissions: chronograf.Permissions{ - { - Name: "grays_sports_almanac", - Scope: "DBScope", - Allowed: chronograf.Allowances{ - "ReadData", - }, - }, - }, - Users: []chronograf.User{ - { - Name: "match", - }, - { - Name: "skinhead", - }, - { - Name: "3-d", - }, - }, - }, nil - }, - }, nil - }, - }, - }, - ID: "1", - RoleID: "biffsgang", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"users":[{"links":{"self":"/chronograf/v1/sources/1/users/match"},"name":"match"},{"links":{"self":"/chronograf/v1/sources/1/users/skinhead"},"name":"skinhead"},{"links":{"self":"/chronograf/v1/sources/1/users/3-d"},"name":"3-d"}],"name":"biffsgang","permissions":[{"scope":"DBScope","name":"grays_sports_almanac","allowed":["ReadData"]}],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}} -`, - }, - } - for _, tt := range tests { - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - Logger: tt.fields.Logger, - } - - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - - h.SourceRoleID(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. SourceRoleID() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. SourceRoleID() = %v, want %v", tt.name, content, tt.wantContentType) - } - if tt.wantBody != "" && string(body) != tt.wantBody { - t.Errorf("%q. SourceRoleID() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} - -func TestService_RemoveSourceRole(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - ID string - RoleID string - wantStatus int - }{ - { - name: "remove role for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://server.local/chronograf/v1/sources/1/roles/biffsgang", - nil), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return &mocks.RolesStore{ - DeleteF: func(context.Context, *chronograf.Role) error { - return nil - }, - }, nil - }, - }, - }, - ID: "1", - RoleID: "biffsgang", - wantStatus: http.StatusNoContent, - }, - } - for _, tt := range tests { - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - Logger: tt.fields.Logger, - } - - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - - h.RemoveSourceRole(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. RemoveSourceRole() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - } -} - -func TestService_SourceRoles(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - ID string - RoleID string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Get roles for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://server.local/chronograf/v1/sources/1/roles", - nil), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - RolesF: func(ctx context.Context) (chronograf.RolesStore, error) { - return &mocks.RolesStore{ - AllF: func(ctx context.Context) ([]chronograf.Role, error) { - return []chronograf.Role{ - chronograf.Role{ - Name: "biffsgang", - Permissions: chronograf.Permissions{ - { - Name: "grays_sports_almanac", - Scope: "DBScope", - Allowed: chronograf.Allowances{ - "ReadData", - }, - }, - }, - Users: []chronograf.User{ - { - Name: "match", - }, - { - Name: "skinhead", - }, - { - Name: "3-d", - }, - }, - }, - }, nil - }, - }, nil - }, - }, - }, - ID: "1", - RoleID: "biffsgang", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"roles":[{"users":[{"links":{"self":"/chronograf/v1/sources/1/users/match"},"name":"match"},{"links":{"self":"/chronograf/v1/sources/1/users/skinhead"},"name":"skinhead"},{"links":{"self":"/chronograf/v1/sources/1/users/3-d"},"name":"3-d"}],"name":"biffsgang","permissions":[{"scope":"DBScope","name":"grays_sports_almanac","allowed":["ReadData"]}],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}}]} -`, - }, - } - for _, tt := range tests { - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - Logger: tt.fields.Logger, - } - - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - - h.SourceRoles(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. SourceRoles() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. SourceRoles() = %v, want %v", tt.name, content, tt.wantContentType) - } - if tt.wantBody != "" && string(body) != tt.wantBody { - t.Errorf("%q. SourceRoles() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} diff --git a/http/chronograf_handler.go b/http/chronograf_handler.go index 7cf9a5150ba..6a991a3cb81 100644 --- a/http/chronograf_handler.go +++ b/http/chronograf_handler.go @@ -1,9 +1,6 @@ package http import ( - "net/http" - - "github.com/NYTimes/gziphandler" "github.com/influxdata/httprouter" "github.com/influxdata/influxdb" "github.com/influxdata/influxdb/chronograf/server" @@ -37,92 +34,6 @@ func NewChronografHandler(s *server.Service, he influxdb.HTTPErrorHandler) *Chro h.HandlerFunc("PUT", "/chronograf/v1/mappings/:id", h.Service.UpdateMapping) h.HandlerFunc("DELETE", "/chronograf/v1/mappings/:id", h.Service.RemoveMapping) - // Sources - h.HandlerFunc("GET", "/chronograf/v1/sources", h.Service.Sources) - h.HandlerFunc("POST", "/chronograf/v1/sources", h.Service.NewSource) - - h.HandlerFunc("GET", "/chronograf/v1/sources/:id", h.Service.SourcesID) - h.HandlerFunc("PATCH", "/chronograf/v1/sources/:id", h.Service.UpdateSource) - h.HandlerFunc("DELETE", "/chronograf/v1/sources/:id", h.Service.RemoveSource) - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/health", h.Service.SourceHealth) - - // Source Proxy to Influx; Has gzip compression around the handler - influx := gziphandler.GzipHandler(http.HandlerFunc(h.Service.Influx)) - h.Handler("POST", "/chronograf/v1/sources/:id/proxy", influx) - - // Write proxies line protocol write requests to InfluxDB - h.HandlerFunc("POST", "/chronograf/v1/sources/:id/write", h.Service.Write) - - // Queries is used to analyze a specific queries and does not create any - // resources. It's a POST because Queries are POSTed to InfluxDB, but this - // only modifies InfluxDB resources with certain metaqueries, e.g. DROP DATABASE. - // - // Admins should ensure that the InfluxDB source as the proper permissions - // intended for Chronograf Users with the Viewer Role type. - h.HandlerFunc("POST", "/chronograf/v1/sources/:id/queries", h.Service.Queries) - - // Annotations are user-defined events associated with this source - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/annotations", h.Service.Annotations) - h.HandlerFunc("POST", "/chronograf/v1/sources/:id/annotations", h.Service.NewAnnotation) - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/annotations/:aid", h.Service.Annotation) - h.HandlerFunc("DELETE", "/chronograf/v1/sources/:id/annotations/:aid", h.Service.RemoveAnnotation) - h.HandlerFunc("PATCH", "/chronograf/v1/sources/:id/annotations/:aid", h.Service.UpdateAnnotation) - - // All possible permissions for users in this source - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/permissions", h.Service.Permissions) - - // Users associated with the data source - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/users", h.Service.SourceUsers) - h.HandlerFunc("POST", "/chronograf/v1/sources/:id/users", h.Service.NewSourceUser) - - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/users/:uid", h.Service.SourceUserID) - h.HandlerFunc("DELETE", "/chronograf/v1/sources/:id/users/:uid", h.Service.RemoveSourceUser) - h.HandlerFunc("PATCH", "/chronograf/v1/sources/:id/users/:uid", h.Service.UpdateSourceUser) - - // Roles associated with the data source - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/roles", h.Service.SourceRoles) - h.HandlerFunc("POST", "/chronograf/v1/sources/:id/roles", h.Service.NewSourceRole) - - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/roles/:rid", h.Service.SourceRoleID) - h.HandlerFunc("DELETE", "/chronograf/v1/sources/:id/roles/:rid", h.Service.RemoveSourceRole) - h.HandlerFunc("PATCH", "/chronograf/v1/sources/:id/roles/:rid", h.Service.UpdateSourceRole) - - // h.Services are resources that chronograf proxies to - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/services", h.Service.Services) - h.HandlerFunc("POST", "/chronograf/v1/sources/:id/service", h.Service.NewService) - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/services/:kid", h.Service.ServiceID) - h.HandlerFunc("PATCH", "/chronograf/v1/sources/:id/services/:kid", h.Service.UpdateService) - h.HandlerFunc("DELETE", "/chronograf/v1/sources/:id/services/:kid", h.Service.RemoveService) - - // h.Service Proxy - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/h.Services/:kid/proxy", h.Service.ProxyGet) - h.HandlerFunc("POST", "/chronograf/v1/sources/:id/h.Services/:kid/proxy", h.Service.ProxyPost) - h.HandlerFunc("PATCH", "/chronograf/v1/sources/:id/h.Services/:kid/proxy", h.Service.ProxyPatch) - h.HandlerFunc("DELETE", "/chronograf/v1/sources/:id/h.Services/:kid/proxy", h.Service.ProxyDelete) - - // Kapacitor - //h.HandlerFunc("GET","/chronograf/v1/sources/:id/kapacitors", h.Service.Kapacitors)) - //h.HandlerFunc("POST","/chronograf/v1/sources/:id/kapacitors", h.Service.NewKapacitor)) - - //h.HandlerFunc("GET","/chronograf/v1/sources/:id/kapacitors/:kid", h.Service.KapacitorsID)) - //h.HandlerFunc("PATCH","/chronograf/v1/sources/:id/kapacitors/:kid", h.Service.UpdateKapacitor)) - //h.HandlerFunc("DELETE","/chronograf/v1/sources/:id/kapacitors/:kid", h.Service.RemoveKapacitor)) - - //// Kapacitor rules - //h.HandlerFunc("GET","/chronograf/v1/sources/:id/kapacitors/:kid/rules", h.Service.KapacitorRulesGet)) - //h.HandlerFunc("POST","/chronograf/v1/sources/:id/kapacitors/:kid/rules", h.Service.KapacitorRulesPost)) - - //h.HandlerFunc("GET","/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", h.Service.KapacitorRulesID)) - //h.HandlerFunc("PUT","/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", h.Service.KapacitorRulesPut)) - //h.HandlerFunc("PATCH","/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", h.Service.KapacitorRulesStatus)) - //h.HandlerFunc("DELETE","/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", h.Service.KapacitorRulesDelete)) - - //// Kapacitor Proxy - //h.HandlerFunc("GET","/chronograf/v1/sources/:id/kapacitors/:kid/proxy", h.Service.ProxyGet)) - //h.HandlerFunc("POST","/chronograf/v1/sources/:id/kapacitors/:kid/proxy", h.Service.ProxyPost)) - //h.HandlerFunc("PATCH","/chronograf/v1/sources/:id/kapacitors/:kid/proxy", h.Service.ProxyPatch)) - //h.HandlerFunc("DELETE","/chronograf/v1/sources/:id/kapacitors/:kid/proxy", h.Service.ProxyDelete)) - // Layouts h.HandlerFunc("GET", "/chronograf/v1/layouts", h.Service.Layouts) h.HandlerFunc("GET", "/chronograf/v1/layouts/:id", h.Service.LayoutsID) @@ -172,22 +83,6 @@ func NewChronografHandler(s *server.Service, he influxdb.HTTPErrorHandler) *Chro h.HandlerFunc("DELETE", "/chronograf/v1/dashboards/:id/templates/:tid", h.Service.RemoveTemplate) h.HandlerFunc("PUT", "/chronograf/v1/dashboards/:id/templates/:tid", h.Service.ReplaceTemplate) - // Databases - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/dbs", h.Service.GetDatabases) - h.HandlerFunc("POST", "/chronograf/v1/sources/:id/dbs", h.Service.NewDatabase) - - h.HandlerFunc("DELETE", "/chronograf/v1/sources/:id/dbs/:db", h.Service.DropDatabase) - - // Retention Policies - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/dbs/:db/rps", h.Service.RetentionPolicies) - h.HandlerFunc("POST", "/chronograf/v1/sources/:id/dbs/:db/rps", h.Service.NewRetentionPolicy) - - h.HandlerFunc("PUT", "/chronograf/v1/sources/:id/dbs/:db/rps/:rp", h.Service.UpdateRetentionPolicy) - h.HandlerFunc("DELETE", "/chronograf/v1/sources/:id/dbs/:db/rps/:rp", h.Service.DropRetentionPolicy) - - // Measurements - h.HandlerFunc("GET", "/chronograf/v1/sources/:id/dbs/:db/measurements", h.Service.Measurements) - // Global application config for Chronograf h.HandlerFunc("GET", "/chronograf/v1/config", h.Service.Config) h.HandlerFunc("GET", "/chronograf/v1/config/auth", h.Service.AuthConfig) diff --git a/ui/mocks/dummyData.ts b/ui/mocks/dummyData.ts index cd419cf0b36..f1216d978c5 100644 --- a/ui/mocks/dummyData.ts +++ b/ui/mocks/dummyData.ts @@ -1,12 +1,5 @@ import {ViewProperties} from 'src/client' -import { - SourceLinks, - Cell, - Dashboard, - Task, - Links, - ConfigurationState, -} from 'src/types' +import {Cell, Dashboard, Task, Links, ConfigurationState} from 'src/types' import {OnboardingStepProps} from 'src/onboarding/containers/OnboardingWizard' import {WithRouterProps} from 'react-router' import {NumericColumnData} from '@influxdata/giraffe' @@ -110,21 +103,6 @@ export const queryConfig = { ], } -export const sourceLinks: SourceLinks = { - query: '/chronograf/v1/sources/16/query', - services: '/chronograf/v1/sources/16/services', - self: '/chronograf/v1/sources/16', - kapacitors: '/chronograf/v1/sources/16/kapacitors', - proxy: '/chronograf/v1/sources/16/proxy', - queries: '/chronograf/v1/sources/16/queries', - write: '/chronograf/v1/sources/16/write', - permissions: '/chronograf/v1/sources/16/permissions', - users: '/chronograf/v1/sources/16/users', - databases: '/chronograf/v1/sources/16/dbs', - annotations: '/chronograf/v1/sources/16/annotations', - health: '/chronograf/v1/sources/16/health', -} - export const source: Source = { id: '16', name: 'ssl', @@ -133,7 +111,6 @@ export const source: Source = { url: 'https://localhost:9086', insecureSkipVerify: true, telegraf: 'telegraf', - links: sourceLinks, } export const timeRange = { diff --git a/ui/src/dashboards/resources.ts b/ui/src/dashboards/resources.ts index 862470b6926..4c39c05f5a8 100644 --- a/ui/src/dashboards/resources.ts +++ b/ui/src/dashboards/resources.ts @@ -5,10 +5,6 @@ import { Dashboard, FieldOption, DecimalPlaces, - Service, - Source, - SourceAuthenticationMethod, - SourceLinks, TimeRange, TableOptions, } from 'src/types' @@ -38,50 +34,6 @@ export const dashboard: Dashboard = { }, } -export const sourceLinks: SourceLinks = { - query: '/chronograf/v1/query/4', - services: '/chronograf/v1/sources/4', - self: '/chronograf/v1/sources/4', - kapacitors: '/chronograf/v1/sources/4/kapacitors', - proxy: '/chronograf/v1/sources/4/proxy', - queries: '/chronograf/v1/sources/4/queries', - write: '/chronograf/v1/sources/4/write', - permissions: '/chronograf/v1/sources/4/permissions', - users: '/chronograf/v1/sources/4/users', - databases: '/chronograf/v1/sources/4/dbs', - annotations: '/chronograf/v1/sources/4/annotations', - health: '/chronograf/v1/sources/4/health', -} - -export const source: Source = { - id: '4', - name: 'Influx 1', - type: 'influx', - url: 'http://localhost:8086', - default: false, - telegraf: 'telegraf', - links: sourceLinks, - insecureSkipVerify: false, - authentication: SourceAuthenticationMethod.Basic, -} - -export const service: Service = { - id: '1', - sourceID: '1', - name: 'Flux', - url: 'http://localhost:8093', - insecureSkipVerify: false, - type: 'flux', - metadata: { - active: true, - }, - links: { - proxy: '/chronograf/v1/sources/1/services/1/proxy', - self: '/chronograf/v1/sources/1/services/1', - source: '/chronograf/v1/sources/1', - }, -} - export const axes: Axes = { x: { bounds: ['', ''],