Merged with master, resolved conflicts

This commit is contained in:
utkarshcmu
2015-12-18 00:23:08 -08:00
229 changed files with 4350 additions and 3690 deletions

View File

@@ -13,7 +13,7 @@ func Register(r *macaron.Macaron) {
reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
regOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
reqOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
quota := middleware.Quota
bind := binding.Bind
@@ -41,6 +41,9 @@ func Register(r *macaron.Macaron) {
r.Get("/admin/orgs", reqGrafanaAdmin, Index)
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
r.Get("/plugins", reqSignedIn, Index)
r.Get("/plugins/edit/*", reqSignedIn, Index)
r.Get("/dashboard/*", reqSignedIn, Index)
r.Get("/dashboard-solo/*", reqSignedIn, Index)
@@ -65,6 +68,7 @@ func Register(r *macaron.Macaron) {
r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
r.Get("/dashboard/snapshot/*", Index)
r.Get("/api/snapshot/shared-options/", GetSharingOptions)
r.Get("/api/snapshots/:key", GetDashboardSnapshot)
r.Get("/api/snapshots-delete/:key", DeleteDashboardSnapshot)
@@ -113,7 +117,7 @@ func Register(r *macaron.Macaron) {
r.Get("/invites", wrap(GetPendingOrgInvites))
r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
}, regOrgAdmin)
}, reqOrgAdmin)
// create new org
r.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg))
@@ -140,7 +144,7 @@ func Register(r *macaron.Macaron) {
r.Get("/", wrap(GetApiKeys))
r.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddApiKey))
r.Delete("/:id", wrap(DeleteApiKey))
}, regOrgAdmin)
}, reqOrgAdmin)
// Data sources
r.Group("/datasources", func() {
@@ -150,7 +154,7 @@ func Register(r *macaron.Macaron) {
r.Delete("/:id", DeleteDataSource)
r.Get("/:id", wrap(GetDataSourceById))
r.Get("/plugins", GetDataSourcePlugins)
}, regOrgAdmin)
}, reqOrgAdmin)
r.Get("/frontend/settings/", GetFrontendSettings)
r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
@@ -30,13 +31,15 @@ type cwRequest struct {
func init() {
actionHandlers = map[string]actionHandler{
"GetMetricStatistics": handleGetMetricStatistics,
"ListMetrics": handleListMetrics,
"DescribeInstances": handleDescribeInstances,
"__GetRegions": handleGetRegions,
"__GetNamespaces": handleGetNamespaces,
"__GetMetrics": handleGetMetrics,
"__GetDimensions": handleGetDimensions,
"GetMetricStatistics": handleGetMetricStatistics,
"ListMetrics": handleListMetrics,
"DescribeAlarmsForMetric": handleDescribeAlarmsForMetric,
"DescribeAlarmHistory": handleDescribeAlarmHistory,
"DescribeInstances": handleDescribeInstances,
"__GetRegions": handleGetRegions,
"__GetNamespaces": handleGetNamespaces,
"__GetMetrics": handleGetMetrics,
"__GetDimensions": handleGetDimensions,
}
}
@@ -119,7 +122,107 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) {
Dimensions: reqParam.Parameters.Dimensions,
}
resp, err := svc.ListMetrics(params)
var resp cloudwatch.ListMetricsOutput
err := svc.ListMetricsPages(params,
func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool {
metrics, _ := awsutil.ValuesAtPath(page, "Metrics")
for _, metric := range metrics {
resp.Metrics = append(resp.Metrics, metric.(*cloudwatch.Metric))
}
return !lastPage
})
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
c.JSON(200, resp)
}
func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
sess := session.New()
creds := credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
})
cfg := &aws.Config{
Region: aws.String(req.Region),
Credentials: creds,
}
svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct {
Parameters struct {
Namespace string `json:"namespace"`
MetricName string `json:"metricName"`
Dimensions []*cloudwatch.Dimension `json:"dimensions"`
Statistic string `json:"statistic"`
Period int64 `json:"period"`
} `json:"parameters"`
}{}
json.Unmarshal(req.Body, reqParam)
params := &cloudwatch.DescribeAlarmsForMetricInput{
Namespace: aws.String(reqParam.Parameters.Namespace),
MetricName: aws.String(reqParam.Parameters.MetricName),
Period: aws.Int64(reqParam.Parameters.Period),
}
if len(reqParam.Parameters.Dimensions) != 0 {
params.Dimensions = reqParam.Parameters.Dimensions
}
if reqParam.Parameters.Statistic != "" {
params.Statistic = aws.String(reqParam.Parameters.Statistic)
}
resp, err := svc.DescribeAlarmsForMetric(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
c.JSON(200, resp)
}
func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
sess := session.New()
creds := credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
})
cfg := &aws.Config{
Region: aws.String(req.Region),
Credentials: creds,
}
svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct {
Parameters struct {
AlarmName string `json:"alarmName"`
HistoryItemType string `json:"historyItemType"`
StartDate int64 `json:"startDate"`
EndDate int64 `json:"endDate"`
} `json:"parameters"`
}{}
json.Unmarshal(req.Body, reqParam)
params := &cloudwatch.DescribeAlarmHistoryInput{
AlarmName: aws.String(reqParam.Parameters.AlarmName),
StartDate: aws.Time(time.Unix(reqParam.Parameters.StartDate, 0)),
EndDate: aws.Time(time.Unix(reqParam.Parameters.EndDate, 0)),
}
if reqParam.Parameters.HistoryItemType != "" {
params.HistoryItemType = aws.String(reqParam.Parameters.HistoryItemType)
}
resp, err := svc.DescribeAlarmHistory(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
@@ -160,7 +263,15 @@ func handleDescribeInstances(req *cwRequest, c *middleware.Context) {
params.InstanceIds = reqParam.Parameters.InstanceIds
}
resp, err := svc.DescribeInstances(params)
var resp ec2.DescribeInstancesOutput
err := svc.DescribeInstancesPages(params,
func(page *ec2.DescribeInstancesOutput, lastPage bool) bool {
reservations, _ := awsutil.ValuesAtPath(page, "Reservations")
for _, reservation := range reservations {
resp.Reservations = append(resp.Reservations, reservation.(*ec2.Reservation))
}
return !lastPage
})
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return

View File

@@ -15,31 +15,47 @@ func init() {
metricsMap = map[string][]string{
"AWS/AutoScaling": {"GroupMinSize", "GroupMaxSize", "GroupDesiredCapacity", "GroupInServiceInstances", "GroupPendingInstances", "GroupStandbyInstances", "GroupTerminatingInstances", "GroupTotalInstances"},
"AWS/Billing": {"EstimatedCharges"},
"AWS/EC2": {"CPUCreditUsage", "CPUCreditBalance", "CPUUtilization", "DiskReadOps", "DiskWriteOps", "DiskReadBytes", "DiskWriteBytes", "NetworkIn", "NetworkOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"},
"AWS/ECS": {"CPUUtilization", "MemoryUtilization"},
"AWS/CloudFront": {"Requests", "BytesDownloaded", "BytesUploaded", "TotalErrorRate", "4xxErrorRate", "5xxErrorRate"},
"AWS/CloudSearch": {"SuccessfulRequests", "SearchableDocuments", "IndexUtilization", "Partitions"},
"AWS/DynamoDB": {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReturnedItemCount", "SuccessfulRequestLatency", "SystemErrors", "ThrottledRequests", "UserErrors", "WriteThrottleEvents"},
"AWS/ECS": {"CPUUtilization", "MemoryUtilization"},
"AWS/ElastiCache": {
"CPUUtilization", "SwapUsage", "FreeableMemory", "NetworkBytesIn", "NetworkBytesOut",
"CPUUtilization", "FreeableMemory", "NetworkBytesIn", "NetworkBytesOut", "SwapUsage",
"BytesUsedForCacheItems", "BytesReadIntoMemcached", "BytesWrittenOutFromMemcached", "CasBadval", "CasHits", "CasMisses", "CmdFlush", "CmdGet", "CmdSet", "CurrConnections", "CurrItems", "DecrHits", "DecrMisses", "DeleteHits", "DeleteMisses", "Evictions", "GetHits", "GetMisses", "IncrHits", "IncrMisses", "Reclaimed",
"CurrConnections", "Evictions", "Reclaimed", "NewConnections", "BytesUsedForCache", "CacheHits", "CacheMisses", "ReplicationLag", "GetTypeCmds", "SetTypeCmds", "KeyBasedCmds", "StringBasedCmds", "HashBasedCmds", "ListBasedCmds", "SetBasedCmds", "SortedSetBasedCmds", "CurrItems",
"BytesUsedForHash", "CmdConfigGet", "CmdConfigSet", "CmdTouch", "CurrConfig", "EvictedUnfetched", "ExpiredUnfetched", "SlabsMoved", "TouchHits", "TouchMisses",
"NewConnections", "NewItems", "UnusedMemory",
"BytesUsedForCache", "CacheHits", "CacheMisses", "CurrConnections", "Evictions", "HyperLogLogBasedCmds", "NewConnections", "Reclaimed", "ReplicationBytes", "ReplicationLag", "SaveInProgress",
"CurrItems", "GetTypeCmds", "HashBasedCmds", "KeyBasedCmds", "ListBasedCmds", "SetBasedCmds", "SetTypeCmds", "SortedSetBasedCmds", "StringBasedCmds",
},
"AWS/EBS": {"VolumeReadBytes", "VolumeWriteBytes", "VolumeReadOps", "VolumeWriteOps", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeIdleTime", "VolumeQueueLength", "VolumeThroughputPercentage", "VolumeConsumedReadWriteOps"},
"AWS/ELB": {"HealthyHostCount", "UnHealthyHostCount", "RequestCount", "Latency", "HTTPCode_ELB_4XX", "HTTPCode_ELB_5XX", "HTTPCode_Backend_2XX", "HTTPCode_Backend_3XX", "HTTPCode_Backend_4XX", "HTTPCode_Backend_5XX", "BackendConnectionErrors", "SurgeQueueLength", "SpilloverCount"},
"AWS/ElasticMapReduce": {"CoreNodesPending", "CoreNodesRunning", "HBaseBackupFailed", "HBaseMostRecentBackupDuration", "HBaseTimeSinceLastSuccessfulBackup", "HDFSBytesRead", "HDFSBytesWritten", "HDFSUtilization", "IsIdle", "JobsFailed", "JobsRunning", "LiveDataNodes", "LiveTaskTrackers", "MapSlotsOpen", "MissingBlocks", "ReduceSlotsOpen", "RemainingMapTasks", "RemainingMapTasksPerSlot", "RemainingReduceTasks", "RunningMapTasks", "RunningReduceTasks", "S3BytesRead", "S3BytesWritten", "TaskNodesPending", "TaskNodesRunning", "TotalLoad"},
"AWS/Kinesis": {"PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Success", "PutRecords.Bytes", "PutRecords.Latency", "PutRecords.Records", "PutRecords.Success", "IncomingBytes", "IncomingRecords", "GetRecords.Bytes", "GetRecords.IteratorAgeMilliseconds", "GetRecords.Latency", "GetRecords.Success"},
"AWS/ML": {"PredictCount", "PredictFailureCount"},
"AWS/OpsWorks": {"cpu_idle", "cpu_nice", "cpu_system", "cpu_user", "cpu_waitio", "load_1", "load_5", "load_15", "memory_buffers", "memory_cached", "memory_free", "memory_swap", "memory_total", "memory_used", "procs"},
"AWS/Redshift": {"CPUUtilization", "DatabaseConnections", "HealthStatus", "MaintenanceMode", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "PercentageDiskSpaceUsed", "ReadIOPS", "ReadLatency", "ReadThroughput", "WriteIOPS", "WriteLatency", "WriteThroughput"},
"AWS/RDS": {"BinLogDiskUsage", "CPUUtilization", "DatabaseConnections", "DiskQueueDepth", "FreeableMemory", "FreeStorageSpace", "ReplicaLag", "SwapUsage", "ReadIOPS", "WriteIOPS", "ReadLatency", "WriteLatency", "ReadThroughput", "WriteThroughput", "NetworkReceiveThroughput", "NetworkTransmitThroughput"},
"AWS/Route53": {"HealthCheckStatus", "HealthCheckPercentageHealthy"},
"AWS/SNS": {"NumberOfMessagesPublished", "PublishSize", "NumberOfNotificationsDelivered", "NumberOfNotificationsFailed"},
"AWS/SQS": {"NumberOfMessagesSent", "SentMessageSize", "NumberOfMessagesReceived", "NumberOfEmptyReceives", "NumberOfMessagesDeleted", "ApproximateNumberOfMessagesDelayed", "ApproximateNumberOfMessagesVisible", "ApproximateNumberOfMessagesNotVisible"},
"AWS/S3": {"BucketSizeBytes", "NumberOfObjects"},
"AWS/SWF": {"DecisionTaskScheduleToStartTime", "DecisionTaskStartToCloseTime", "DecisionTasksCompleted", "StartedDecisionTasksTimedOutOnClose", "WorkflowStartToCloseTime", "WorkflowsCanceled", "WorkflowsCompleted", "WorkflowsContinuedAsNew", "WorkflowsFailed", "WorkflowsTerminated", "WorkflowsTimedOut"},
"AWS/StorageGateway": {"CacheHitPercent", "CachePercentUsed", "CachePercentDirty", "CloudBytesDownloaded", "CloudDownloadLatency", "CloudBytesUploaded", "UploadBufferFree", "UploadBufferPercentUsed", "UploadBufferUsed", "QueuedWrites", "ReadBytes", "ReadTime", "TotalCacheSize", "WriteBytes", "WriteTime", "WorkingStorageFree", "WorkingStoragePercentUsed", "WorkingStorageUsed", "CacheHitPercent", "CachePercentUsed", "CachePercentDirty", "ReadBytes", "ReadTime", "WriteBytes", "WriteTime", "QueuedWrites"},
"AWS/WorkSpaces": {"Available", "Unhealthy", "ConnectionAttempt", "ConnectionSuccess", "ConnectionFailure", "SessionLaunchTime", "InSessionLatency", "SessionDisconnect"},
"AWS/EBS": {"VolumeReadBytes", "VolumeWriteBytes", "VolumeReadOps", "VolumeWriteOps", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeIdleTime", "VolumeQueueLength", "VolumeThroughputPercentage", "VolumeConsumedReadWriteOps"},
"AWS/EC2": {"CPUCreditUsage", "CPUCreditBalance", "CPUUtilization", "DiskReadOps", "DiskWriteOps", "DiskReadBytes", "DiskWriteBytes", "NetworkIn", "NetworkOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"},
"AWS/ELB": {"HealthyHostCount", "UnHealthyHostCount", "RequestCount", "Latency", "HTTPCode_ELB_4XX", "HTTPCode_ELB_5XX", "HTTPCode_Backend_2XX", "HTTPCode_Backend_3XX", "HTTPCode_Backend_4XX", "HTTPCode_Backend_5XX", "BackendConnectionErrors", "SurgeQueueLength", "SpilloverCount"},
"AWS/ElasticMapReduce": {"IsIdle", "JobsRunning", "JobsFailed",
"MapTasksRunning", "MapTasksRemaining", "MapSlotsOpen", "RemainingMapTasksPerSlot", "ReduceTasksRunning", "ReduceTasksRemaining", "ReduceSlotsOpen",
"CoreNodesRunning", "CoreNodesPending", "LiveDataNodes", "TaskNodesRunning", "TaskNodesPending", "LiveTaskTrackers",
"S3BytesWritten", "S3BytesRead", "HDFSUtilization", "HDFSBytesRead", "HDFSBytesWritten", "MissingBlocks", "TotalLoad",
"BackupFailed", "MostRecentBackupDuration", "TimeSinceLastSuccessfulBackup",
"IsIdle", "ContainerAllocated", "ContainerReserved", "ContainerPending", "AppsCompleted", "AppsFailed", "AppsKilled", "AppsPending", "AppsRunning", "AppsSubmitted",
"CoreNodesRunning", "CoreNodesPending", "LiveDataNodes", "MRTotalNodes", "MRActiveNodes", "MRLostNodes", "MRUnhealthyNodes", "MRDecommissionedNodes", "MRRebootedNodes",
"S3BytesWritten", "S3BytesRead", "HDFSUtilization", "HDFSBytesRead", "HDFSBytesWritten", "MissingBlocks", "CorruptBlocks", "TotalLoad", "MemoryTotalMB", "MemoryReservedMB", "MemoryAvailableMB", "MemoryAllocatedMB", "PendingDeletionBlocks", "UnderReplicatedBlocks", "DfsPendingReplicationBlocks", "CapacityRemainingGB",
"HbaseBackupFailed", "MostRecentBackupDuration", "TimeSinceLastSuccessfulBackup"},
"AWS/ES": {"ClusterStatus.green", "ClusterStatus.yellow", "ClusterStatus.red", "Nodes", "SearchableDocuments", "DeletedDocuments", "CPUUtilization", "FreeStorageSpace", "JVMMemoryPressure", "AutomatedSnapshotFailure", "MasterCPUUtilization", "MasterFreeStorageSpace", "MasterJVMMemoryPressure", "ReadLatency", "WriteLatency", "ReadThroughput", "WriteThroughput", "DiskQueueLength", "ReadIOPS", "WriteIOPS"},
"AWS/Kinesis": {"PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Success", "PutRecords.Bytes", "PutRecords.Latency", "PutRecords.Records", "PutRecords.Success", "IncomingBytes", "IncomingRecords", "GetRecords.Bytes", "GetRecords.IteratorAgeMilliseconds", "GetRecords.Latency", "GetRecords.Success"},
"AWS/Lambda": {"Invocations", "Errors", "Duration", "Throttles"},
"AWS/ML": {"PredictCount", "PredictFailureCount"},
"AWS/OpsWorks": {"cpu_idle", "cpu_nice", "cpu_system", "cpu_user", "cpu_waitio", "load_1", "load_5", "load_15", "memory_buffers", "memory_cached", "memory_free", "memory_swap", "memory_total", "memory_used", "procs"},
"AWS/Redshift": {"CPUUtilization", "DatabaseConnections", "HealthStatus", "MaintenanceMode", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "PercentageDiskSpaceUsed", "ReadIOPS", "ReadLatency", "ReadThroughput", "WriteIOPS", "WriteLatency", "WriteThroughput"},
"AWS/RDS": {"BinLogDiskUsage", "CPUUtilization", "CPUCreditUsage", "CPUCreditBalance", "DatabaseConnections", "DiskQueueDepth", "FreeableMemory", "FreeStorageSpace", "ReplicaLag", "SwapUsage", "ReadIOPS", "WriteIOPS", "ReadLatency", "WriteLatency", "ReadThroughput", "WriteThroughput", "NetworkReceiveThroughput", "NetworkTransmitThroughput"},
"AWS/Route53": {"HealthCheckStatus", "HealthCheckPercentageHealthy"},
"AWS/SNS": {"NumberOfMessagesPublished", "PublishSize", "NumberOfNotificationsDelivered", "NumberOfNotificationsFailed"},
"AWS/SQS": {"NumberOfMessagesSent", "SentMessageSize", "NumberOfMessagesReceived", "NumberOfEmptyReceives", "NumberOfMessagesDeleted", "ApproximateNumberOfMessagesDelayed", "ApproximateNumberOfMessagesVisible", "ApproximateNumberOfMessagesNotVisible"},
"AWS/S3": {"BucketSizeBytes", "NumberOfObjects"},
"AWS/SWF": {"DecisionTaskScheduleToStartTime", "DecisionTaskStartToCloseTime", "DecisionTasksCompleted", "StartedDecisionTasksTimedOutOnClose", "WorkflowStartToCloseTime", "WorkflowsCanceled", "WorkflowsCompleted", "WorkflowsContinuedAsNew", "WorkflowsFailed", "WorkflowsTerminated", "WorkflowsTimedOut",
"ActivityTaskScheduleToCloseTime", "ActivityTaskScheduleToStartTime", "ActivityTaskStartToCloseTime", "ActivityTasksCanceled", "ActivityTasksCompleted", "ActivityTasksFailed", "ScheduledActivityTasksTimedOutOnClose", "ScheduledActivityTasksTimedOutOnStart", "StartedActivityTasksTimedOutOnClose", "StartedActivityTasksTimedOutOnHeartbeat"},
"AWS/StorageGateway": {"CacheHitPercent", "CachePercentUsed", "CachePercentDirty", "CloudBytesDownloaded", "CloudDownloadLatency", "CloudBytesUploaded", "UploadBufferFree", "UploadBufferPercentUsed", "UploadBufferUsed", "QueuedWrites", "ReadBytes", "ReadTime", "TotalCacheSize", "WriteBytes", "WriteTime", "TimeSinceLastRecoveryPoint", "WorkingStorageFree", "WorkingStoragePercentUsed", "WorkingStorageUsed",
"CacheHitPercent", "CachePercentUsed", "CachePercentDirty", "ReadBytes", "ReadTime", "WriteBytes", "WriteTime", "QueuedWrites"},
"AWS/WAF": {"AllowedRequests", "BlockedRequests", "CountedRequests"},
"AWS/WorkSpaces": {"Available", "Unhealthy", "ConnectionAttempt", "ConnectionSuccess", "ConnectionFailure", "SessionLaunchTime", "InSessionLatency", "SessionDisconnect"},
}
dimensionsMap = map[string][]string{
"AWS/AutoScaling": {"AutoScalingGroupName"},
@@ -47,13 +63,15 @@ func init() {
"AWS/CloudFront": {"DistributionId", "Region"},
"AWS/CloudSearch": {},
"AWS/DynamoDB": {"TableName", "GlobalSecondaryIndexName", "Operation"},
"AWS/ECS": {"ClusterName", "ServiceName"},
"AWS/ElastiCache": {"CacheClusterId", "CacheNodeId"},
"AWS/EBS": {"VolumeId"},
"AWS/EC2": {"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"},
"AWS/ECS": {"ClusterName", "ServiceName"},
"AWS/ELB": {"LoadBalancerName", "AvailabilityZone"},
"AWS/ElasticMapReduce": {"ClusterId", "JobId"},
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
"AWS/ES": {},
"AWS/Kinesis": {"StreamName"},
"AWS/Lambda": {"FunctionName"},
"AWS/ML": {"MLModelId", "RequestMode"},
"AWS/OpsWorks": {"StackId", "LayerId", "InstanceId"},
"AWS/Redshift": {"NodeID", "ClusterIdentifier"},
@@ -62,8 +80,9 @@ func init() {
"AWS/SNS": {"Application", "Platform", "TopicName"},
"AWS/SQS": {"QueueName"},
"AWS/S3": {"BucketName", "StorageType"},
"AWS/SWF": {"Domain", "ActivityTypeName", "ActivityTypeVersion"},
"AWS/SWF": {"Domain", "WorkflowTypeName", "WorkflowTypeVersion", "ActivityTypeName", "ActivityTypeVersion"},
"AWS/StorageGateway": {"GatewayId", "GatewayName", "VolumeId"},
"AWS/WAF": {"Rule", "WebACL"},
"AWS/WorkSpaces": {"DirectoryId", "WorkspaceId"},
}
}
@@ -113,6 +132,7 @@ func handleGetMetrics(req *cwRequest, c *middleware.Context) {
c.JsonApiErr(404, "Unable to find namespace "+reqParam.Parameters.Namespace, nil)
return
}
sort.Sort(sort.StringSlice(namespaceMetrics))
result := []interface{}{}
for _, name := range namespaceMetrics {
@@ -136,6 +156,7 @@ func handleGetDimensions(req *cwRequest, c *middleware.Context) {
c.JsonApiErr(404, "Unable to find dimension "+reqParam.Parameters.Namespace, nil)
return
}
sort.Sort(sort.StringSlice(dimensionValues))
result := []interface{}{}
for _, name := range dimensionValues {

View File

@@ -12,6 +12,14 @@ import (
"github.com/grafana/grafana/pkg/util"
)
func GetSharingOptions(c *middleware.Context) {
c.JSON(200, util.DynMap{
"externalSnapshotURL": setting.ExternalSnapshotUrl,
"externalSnapshotName": setting.ExternalSnapshotName,
"externalEnabled": setting.ExternalEnabled,
})
}
func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapshotCommand) {
if cmd.External {
// external snapshot ref requires key and delete key

View File

@@ -65,6 +65,7 @@ func GetDataSourceById(c *middleware.Context) Response {
BasicAuth: ds.BasicAuth,
BasicAuthUser: ds.BasicAuthUser,
BasicAuthPassword: ds.BasicAuthPassword,
WithCredentials: ds.WithCredentials,
IsDefault: ds.IsDefault,
JsonData: ds.JsonData,
})
@@ -117,7 +118,7 @@ func GetDataSourcePlugins(c *middleware.Context) {
dsList := make(map[string]interface{})
for key, value := range plugins.DataSources {
if value.(map[string]interface{})["builtIn"] == nil {
if !value.BuiltIn {
dsList[key] = value
}
}

25
pkg/api/dtos/index.go Normal file
View File

@@ -0,0 +1,25 @@
package dtos
type IndexViewData struct {
User *CurrentUser
Settings map[string]interface{}
AppUrl string
AppSubUrl string
GoogleAnalyticsId string
GoogleTagManagerId string
PluginCss []*PluginCss
PluginJs []string
MainNavLinks []*NavLink
}
type PluginCss struct {
Light string `json:"light"`
Dark string `json:"dark"`
}
type NavLink struct {
Text string `json:"text"`
Icon string `json:"icon"`
Href string `json:"href"`
}

View File

@@ -0,0 +1,8 @@
package dtos
type PluginBundle struct {
Type string `json:"type"`
Enabled bool `json:"enabled"`
Module string `json:"module"`
JsonData map[string]interface{} `json:"jsonData"`
}

View File

@@ -62,6 +62,9 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
if ds.BasicAuth {
dsMap["basicAuth"] = util.GetBasicAuthHeader(ds.BasicAuthUser, ds.BasicAuthPassword)
}
if ds.WithCredentials {
dsMap["withCredentials"] = ds.WithCredentials
}
if ds.Type == m.DS_INFLUXDB_08 {
dsMap["username"] = ds.User
@@ -106,11 +109,21 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
defaultDatasource = "-- Grafana --"
}
panels := map[string]interface{}{}
for _, panel := range plugins.Panels {
panels[panel.Type] = map[string]interface{}{
"module": panel.Module,
"name": panel.Name,
}
}
jsonObj := map[string]interface{}{
"defaultDatasource": defaultDatasource,
"datasources": datasources,
"panels": panels,
"appSubUrl": setting.AppSubUrl,
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"authProxyEnabled": setting.AuthProxyEnabled,
"buildInfo": map[string]interface{}{
"version": setting.BuildVersion,
"commit": setting.BuildCommit,

View File

@@ -3,65 +3,74 @@ package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
func setIndexViewData(c *middleware.Context) error {
func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
settings, err := getFrontendSettingsMap(c)
if err != nil {
return err
return nil, err
}
currentUser := &dtos.CurrentUser{
Id: c.UserId,
IsSignedIn: c.IsSignedIn,
Login: c.Login,
Email: c.Email,
Name: c.Name,
LightTheme: c.Theme == "light",
OrgId: c.OrgId,
OrgName: c.OrgName,
OrgRole: c.OrgRole,
GravatarUrl: dtos.GetGravatarUrl(c.Email),
IsGrafanaAdmin: c.IsGrafanaAdmin,
var data = dtos.IndexViewData{
User: &dtos.CurrentUser{
Id: c.UserId,
IsSignedIn: c.IsSignedIn,
Login: c.Login,
Email: c.Email,
Name: c.Name,
LightTheme: c.Theme == "light",
OrgId: c.OrgId,
OrgName: c.OrgName,
OrgRole: c.OrgRole,
GravatarUrl: dtos.GetGravatarUrl(c.Email),
IsGrafanaAdmin: c.IsGrafanaAdmin,
},
Settings: settings,
AppUrl: setting.AppUrl,
AppSubUrl: setting.AppSubUrl,
GoogleAnalyticsId: setting.GoogleAnalyticsId,
GoogleTagManagerId: setting.GoogleTagManagerId,
}
if setting.DisableGravatar {
currentUser.GravatarUrl = setting.AppSubUrl + "/img/user_profile.png"
data.User.GravatarUrl = setting.AppSubUrl + "/img/user_profile.png"
}
if len(currentUser.Name) == 0 {
currentUser.Name = currentUser.Login
if len(data.User.Name) == 0 {
data.User.Name = data.User.Login
}
themeUrlParam := c.Query("theme")
if themeUrlParam == "light" {
currentUser.LightTheme = true
data.User.LightTheme = true
}
c.Data["User"] = currentUser
c.Data["Settings"] = settings
c.Data["AppUrl"] = setting.AppUrl
c.Data["AppSubUrl"] = setting.AppSubUrl
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
Text: "Dashboards",
Icon: "fa fa-fw fa-th-large",
Href: "/",
})
if setting.GoogleAnalyticsId != "" {
c.Data["GoogleAnalyticsId"] = setting.GoogleAnalyticsId
if c.OrgRole == m.ROLE_ADMIN {
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
Text: "Data Sources",
Icon: "fa fa-fw fa-database",
Href: "/datasources",
})
}
if setting.GoogleTagManagerId != "" {
c.Data["GoogleTagManagerId"] = setting.GoogleTagManagerId
}
return nil
return &data, nil
}
func Index(c *middleware.Context) {
if err := setIndexViewData(c); err != nil {
if data, err := setIndexViewData(c); err != nil {
c.Handle(500, "Failed to get settings", err)
return
} else {
c.HTML(200, "index", data)
}
c.HTML(200, "index")
}
func NotFoundHandler(c *middleware.Context) {
@@ -70,10 +79,10 @@ func NotFoundHandler(c *middleware.Context) {
return
}
if err := setIndexViewData(c); err != nil {
if data, err := setIndexViewData(c); err != nil {
c.Handle(500, "Failed to get settings", err)
return
} else {
c.HTML(404, "index", data)
}
c.HTML(404, "index")
}

View File

@@ -19,18 +19,19 @@ const (
)
func LoginView(c *middleware.Context) {
if err := setIndexViewData(c); err != nil {
viewData, err := setIndexViewData(c)
if err != nil {
c.Handle(500, "Failed to get settings", err)
return
}
settings := c.Data["Settings"].(map[string]interface{})
settings["googleAuthEnabled"] = setting.OAuthService.Google
settings["githubAuthEnabled"] = setting.OAuthService.GitHub
settings["disableUserSignUp"] = !setting.AllowUserSignUp
viewData.Settings["googleAuthEnabled"] = setting.OAuthService.Google
viewData.Settings["githubAuthEnabled"] = setting.OAuthService.GitHub
viewData.Settings["disableUserSignUp"] = !setting.AllowUserSignUp
viewData.Settings["loginHint"] = setting.LoginHint
if !tryLoginUsingRememberCookie(c) {
c.HTML(200, VIEW_INDEX)
c.HTML(200, VIEW_INDEX, viewData)
return
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/api/static"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
)
@@ -28,12 +29,18 @@ func newMacaron() *macaron.Macaron {
m.Use(middleware.Gziper())
}
mapStatic(m, "", "public")
mapStatic(m, "app", "app")
mapStatic(m, "css", "css")
mapStatic(m, "img", "img")
mapStatic(m, "fonts", "fonts")
mapStatic(m, "robots.txt", "robots.txt")
for _, route := range plugins.StaticRoutes {
pluginRoute := path.Join("/public/plugins/", route.Url)
log.Info("Plugin: Adding static route %s -> %s", pluginRoute, route.Path)
mapStatic(m, route.Path, "", pluginRoute)
}
mapStatic(m, setting.StaticRootPath, "", "public")
mapStatic(m, setting.StaticRootPath, "app", "app")
mapStatic(m, setting.StaticRootPath, "css", "css")
mapStatic(m, setting.StaticRootPath, "img", "img")
mapStatic(m, setting.StaticRootPath, "fonts", "fonts")
mapStatic(m, setting.StaticRootPath, "robots.txt", "robots.txt")
m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: path.Join(setting.StaticRootPath, "views"),
@@ -51,7 +58,7 @@ func newMacaron() *macaron.Macaron {
return m
}
func mapStatic(m *macaron.Macaron, dir string, prefix string) {
func mapStatic(m *macaron.Macaron, rootDir string, dir string, prefix string) {
headers := func(c *macaron.Context) {
c.Resp.Header().Set("Cache-Control", "public, max-age=3600")
}
@@ -63,7 +70,7 @@ func mapStatic(m *macaron.Macaron, dir string, prefix string) {
}
m.Use(httpstatic.Static(
path.Join(setting.StaticRootPath, dir),
path.Join(rootDir, dir),
httpstatic.StaticOptions{
SkipLogging: true,
Prefix: prefix,

95
pkg/log/syslog.go Normal file
View File

@@ -0,0 +1,95 @@
//+build !windows,!nacl,!plan9
package log
import (
"encoding/json"
"errors"
"log/syslog"
)
type SyslogWriter struct {
syslog *syslog.Writer
Network string `json:"network"`
Address string `json:"address"`
Facility string `json:"facility"`
Tag string `json:"tag"`
}
func NewSyslog() LoggerInterface {
return new(SyslogWriter)
}
func (sw *SyslogWriter) Init(config string) error {
if err := json.Unmarshal([]byte(config), sw); err != nil {
return err
}
prio, err := parseFacility(sw.Facility)
if err != nil {
return err
}
w, err := syslog.Dial(sw.Network, sw.Address, prio, sw.Tag)
if err != nil {
return err
}
sw.syslog = w
return nil
}
func (sw *SyslogWriter) WriteMsg(msg string, skip, level int) error {
var err error
switch level {
case TRACE, DEBUG:
err = sw.syslog.Debug(msg)
case INFO:
err = sw.syslog.Info(msg)
case WARN:
err = sw.syslog.Warning(msg)
case ERROR:
err = sw.syslog.Err(msg)
case CRITICAL:
err = sw.syslog.Crit(msg)
case FATAL:
err = sw.syslog.Alert(msg)
default:
err = errors.New("invalid syslog level")
}
return err
}
func (sw *SyslogWriter) Destroy() {
sw.syslog.Close()
}
func (sw *SyslogWriter) Flush() {}
var facilities = map[string]syslog.Priority{
"user": syslog.LOG_USER,
"daemon": syslog.LOG_DAEMON,
"local0": syslog.LOG_LOCAL0,
"local1": syslog.LOG_LOCAL1,
"local2": syslog.LOG_LOCAL2,
"local3": syslog.LOG_LOCAL3,
"local4": syslog.LOG_LOCAL4,
"local5": syslog.LOG_LOCAL5,
"local6": syslog.LOG_LOCAL6,
"local7": syslog.LOG_LOCAL7,
}
func parseFacility(facility string) (syslog.Priority, error) {
prio, ok := facilities[facility]
if !ok {
return syslog.LOG_LOCAL0, errors.New("invalid syslog facility")
}
return prio, nil
}
func init() {
Register("syslog", NewSyslog)
}

View File

@@ -131,8 +131,8 @@ func (a *ldapAuther) getGrafanaUserFor(ldapUser *ldapUserInfo) (*m.User, error)
}
return userQuery.Result, nil
}
}
func (a *ldapAuther) createGrafanaUser(ldapUser *ldapUserInfo) (*m.User, error) {
cmd := m.CreateUserCommand{
Login: ldapUser.Username,

View File

@@ -32,7 +32,12 @@ func Logger() macaron.Handler {
rw := res.(macaron.ResponseWriter)
c.Next()
content := fmt.Sprintf("Completed %s %v %s in %v", req.URL.Path, rw.Status(), http.StatusText(rw.Status()), time.Since(start))
uname := c.GetCookie(setting.CookieUserName)
if len(uname) == 0 {
uname = "-"
}
content := fmt.Sprintf("Completed %s %s \"%s %s %s\" %v %s %d bytes in %dus", c.RemoteAddr(), uname, req.Method, req.URL.Path, req.Proto, rw.Status(), http.StatusText(rw.Status()), rw.Size(), time.Since(start)/time.Microsecond)
switch rw.Status() {
case 200, 304:

View File

@@ -40,6 +40,7 @@ type DataSource struct {
BasicAuth bool
BasicAuthUser string
BasicAuthPassword string
WithCredentials bool
IsDefault bool
JsonData map[string]interface{}
@@ -83,6 +84,7 @@ type AddDataSourceCommand struct {
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData map[string]interface{} `json:"jsonData"`
@@ -103,6 +105,7 @@ type UpdateDataSourceCommand struct {
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData map[string]interface{} `json:"jsonData"`

View File

@@ -0,0 +1,34 @@
package models
import "time"
type PluginBundle struct {
Id int64
Type string
OrgId int64
Enabled bool
JsonData map[string]interface{}
Created time.Time
Updated time.Time
}
// ----------------------
// COMMANDS
// Also acts as api DTO
type UpdatePluginBundleCmd struct {
Type string `json:"type" binding:"Required"`
Enabled bool `json:"enabled"`
JsonData map[string]interface{} `json:"jsonData"`
Id int64 `json:"-"`
OrgId int64 `json:"-"`
}
// ---------------------
// QUERIES
type GetPluginBundlesQuery struct {
OrgId int64
Result []*PluginBundle
}

26
pkg/plugins/models.go Normal file
View File

@@ -0,0 +1,26 @@
package plugins
type DataSourcePlugin struct {
Type string `json:"type"`
Name string `json:"name"`
ServiceName string `json:"serviceName"`
Module string `json:"module"`
Partials map[string]interface{} `json:"partials"`
DefaultMatchFormat string `json:"defaultMatchFormat"`
Annotations bool `json:"annotations"`
Metrics bool `json:"metrics"`
BuiltIn bool `json:"builtIn"`
StaticRootConfig *StaticRootConfig `json:"staticRoot"`
}
type PanelPlugin struct {
Type string `json:"type"`
Name string `json:"name"`
Module string `json:"module"`
StaticRootConfig *StaticRootConfig `json:"staticRoot"`
}
type StaticRootConfig struct {
Url string `json:"url"`
Path string `json:"path"`
}

View File

@@ -6,18 +6,17 @@ import (
"os"
"path"
"path/filepath"
"strings"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
type PluginMeta struct {
Type string `json:"type"`
Name string `json:"name"`
}
var (
DataSources map[string]interface{}
DataSources map[string]DataSourcePlugin
Panels map[string]PanelPlugin
StaticRoutes []*StaticRootConfig
)
type PluginScanner struct {
@@ -25,18 +24,37 @@ type PluginScanner struct {
errors []error
}
func Init() {
func Init() error {
DataSources = make(map[string]DataSourcePlugin)
StaticRoutes = make([]*StaticRootConfig, 0)
Panels = make(map[string]PanelPlugin)
scan(path.Join(setting.StaticRootPath, "app/plugins"))
scan(path.Join(setting.PluginsPath))
checkExternalPluginPaths()
return nil
}
func checkExternalPluginPaths() error {
for _, section := range setting.Cfg.Sections() {
if strings.HasPrefix(section.Name(), "plugin.") {
path := section.Key("path").String()
if path != "" {
log.Info("Plugin: Scaning dir %s", path)
scan(path)
}
}
}
return nil
}
func scan(pluginDir string) error {
DataSources = make(map[string]interface{})
scanner := &PluginScanner{
pluginPath: pluginDir,
}
if err := filepath.Walk(pluginDir, scanner.walker); err != nil {
if err := util.Walk(pluginDir, true, true, scanner.walker); err != nil {
return err
}
@@ -47,7 +65,7 @@ func scan(pluginDir string) error {
return nil
}
func (scanner *PluginScanner) walker(path string, f os.FileInfo, err error) error {
func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err error) error {
if err != nil {
return err
}
@@ -57,17 +75,25 @@ func (scanner *PluginScanner) walker(path string, f os.FileInfo, err error) erro
}
if f.Name() == "plugin.json" {
err := scanner.loadPluginJson(path)
err := scanner.loadPluginJson(currentPath)
if err != nil {
log.Error(3, "Failed to load plugin json file: %v, err: %v", path, err)
log.Error(3, "Failed to load plugin json file: %v, err: %v", currentPath, err)
scanner.errors = append(scanner.errors, err)
}
}
return nil
}
func (scanner *PluginScanner) loadPluginJson(path string) error {
reader, err := os.Open(path)
func addStaticRoot(staticRootConfig *StaticRootConfig, currentDir string) {
if staticRootConfig != nil {
staticRootConfig.Path = path.Join(currentDir, staticRootConfig.Path)
StaticRoutes = append(StaticRoutes, staticRootConfig)
}
}
func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
currentDir := filepath.Dir(pluginJsonFilePath)
reader, err := os.Open(pluginJsonFilePath)
if err != nil {
return err
}
@@ -87,11 +113,33 @@ func (scanner *PluginScanner) loadPluginJson(path string) error {
}
if pluginType == "datasource" {
datasourceType, exists := pluginJson["type"]
if !exists {
p := DataSourcePlugin{}
reader.Seek(0, 0)
if err := jsonParser.Decode(&p); err != nil {
return err
}
if p.Type == "" {
return errors.New("Did not find type property in plugin.json")
}
DataSources[datasourceType.(string)] = pluginJson
DataSources[p.Type] = p
addStaticRoot(p.StaticRootConfig, currentDir)
}
if pluginType == "panel" {
p := PanelPlugin{}
reader.Seek(0, 0)
if err := jsonParser.Decode(&p); err != nil {
return err
}
if p.Type == "" {
return errors.New("Did not find type property in plugin.json")
}
Panels[p.Type] = p
addStaticRoot(p.StaticRootConfig, currentDir)
}
return nil

View File

@@ -4,14 +4,17 @@ import (
"path/filepath"
"testing"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/ini.v1"
)
func TestPluginScans(t *testing.T) {
Convey("When scaning for plugins", t, func() {
path, _ := filepath.Abs("../../public/app/plugins")
err := scan(path)
setting.StaticRootPath, _ = filepath.Abs("../../public/")
setting.Cfg = ini.Empty()
err := Init()
So(err, ShouldBeNil)
So(len(DataSources), ShouldBeGreaterThan, 1)

View File

@@ -114,12 +114,14 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
BasicAuth: cmd.BasicAuth,
BasicAuthUser: cmd.BasicAuthUser,
BasicAuthPassword: cmd.BasicAuthPassword,
WithCredentials: cmd.WithCredentials,
JsonData: cmd.JsonData,
Updated: time.Now(),
}
sess.UseBool("is_default")
sess.UseBool("basic_auth")
sess.UseBool("with_credentials")
_, err := sess.Where("id=? and org_id=?", ds.Id, ds.OrgId).Update(ds)
if err != nil {

View File

@@ -96,4 +96,9 @@ func addDataSourceMigration(mg *Migrator) {
}))
mg.AddMigration("Drop old table data_source_v1 #2", NewDropTableMigration("data_source_v1"))
// add column to activate withCredentials option
mg.AddMigration("Add column with_credentials", NewAddColumnMigration(tableV2, &Column{
Name: "with_credentials", Type: DB_Bool, Nullable: false, Default: "0",
}))
}

View File

@@ -18,6 +18,7 @@ func AddMigrations(mg *Migrator) {
addApiKeyMigrations(mg)
addDashboardSnapshotMigrations(mg)
addQuotaMigration(mg)
addPluginBundleMigration(mg)
}
func addMigrationLogMigrations(mg *Migrator) {

View File

@@ -0,0 +1,26 @@
package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addPluginBundleMigration(mg *Migrator) {
var pluginBundleV1 = Table{
Name: "plugin_bundle",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "org_id", Type: DB_BigInt, Nullable: true},
{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "enabled", Type: DB_Bool, Nullable: false},
{Name: "json_data", Type: DB_Text, Nullable: true},
{Name: "created", Type: DB_DateTime, Nullable: false},
{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
{Cols: []string{"org_id", "type"}, Type: UniqueIndex},
},
}
mg.AddMigration("create plugin_bundle table v1", NewAddTableMigration(pluginBundleV1))
//------- indexes ------------------
addTableIndicesMigrations(mg, "v1", pluginBundleV1)
}

View File

@@ -55,7 +55,7 @@ func (col *Column) StringNoPk(d Dialect) string {
}
if col.Default != "" {
sql += "DEFAULT " + col.Default + " "
sql += "DEFAULT " + d.Default(col) + " "
}
return sql

View File

@@ -17,10 +17,11 @@ type Dialect interface {
SqlType(col *Column) string
SupportEngine() bool
LikeStr() string
Default(col *Column) string
CreateIndexSql(tableName string, index *Index) string
CreateTableSql(table *Table) string
AddColumnSql(tableName string, Col *Column) string
AddColumnSql(tableName string, col *Column) string
CopyTableData(sourceTable string, targetTable string, sourceCols []string, targetCols []string) string
DropTable(tableName string) string
DropIndexSql(tableName string, index *Index) string
@@ -71,6 +72,10 @@ func (b *BaseDialect) EqStr() string {
return "="
}
func (b *BaseDialect) Default(col *Column) string {
return col.Default
}
func (b *BaseDialect) CreateTableSql(table *Table) string {
var sql string
sql = "CREATE TABLE IF NOT EXISTS "

View File

@@ -64,6 +64,10 @@ type AddColumnMigration struct {
column *Column
}
func NewAddColumnMigration(table Table, col *Column) *AddColumnMigration {
return &AddColumnMigration{tableName: table.Name, column: col}
}
func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration {
m.tableName = tableName
return m

View File

@@ -36,6 +36,17 @@ func (db *Postgres) AutoIncrStr() string {
return ""
}
func (b *Postgres) Default(col *Column) string {
if col.Type == DB_Bool {
if col.Default == "0" {
return "FALSE"
} else {
return "TRUE"
}
}
return col.Default
}
func (db *Postgres) SqlType(c *Column) string {
var res string
switch t := c.Type; t {

View File

@@ -0,0 +1,46 @@
package sqlstore
import (
"time"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("sql", GetPluginBundles)
bus.AddHandler("sql", UpdatePluginBundle)
}
func GetPluginBundles(query *m.GetPluginBundlesQuery) error {
sess := x.Where("org_id=?", query.OrgId)
query.Result = make([]*m.PluginBundle, 0)
return sess.Find(&query.Result)
}
func UpdatePluginBundle(cmd *m.UpdatePluginBundleCmd) error {
return inTransaction2(func(sess *session) error {
var bundle m.PluginBundle
exists, err := sess.Where("org_id=? and type=?", cmd.OrgId, cmd.Type).Get(&bundle)
sess.UseBool("enabled")
if !exists {
bundle = m.PluginBundle{
Type: cmd.Type,
OrgId: cmd.OrgId,
Enabled: cmd.Enabled,
JsonData: cmd.JsonData,
Created: time.Now(),
Updated: time.Now(),
}
_, err = sess.Insert(&bundle)
return err
} else {
bundle.Enabled = cmd.Enabled
bundle.JsonData = cmd.JsonData
_, err = sess.Id(bundle.Id).Update(&bundle)
return err
}
})
}

View File

@@ -48,9 +48,10 @@ var (
BuildStamp int64
// Paths
LogsPath string
HomePath string
DataPath string
LogsPath string
HomePath string
DataPath string
PluginsPath string
// Log settings.
LogModes []string
@@ -76,12 +77,18 @@ var (
EmailCodeValidMinutes int
DataProxyWhiteList map[string]bool
// Snapshots
ExternalSnapshotUrl string
ExternalSnapshotName string
ExternalEnabled bool
// User settings
AllowUserSignUp bool
AllowUserOrgCreate bool
AutoAssignOrg bool
AutoAssignOrgRole string
VerifyEmailEnabled bool
LoginHint string
// Http auth
AdminUser string
@@ -281,13 +288,11 @@ func loadSpecifedConfigFile(configFile string) {
defaultSec, err := Cfg.GetSection(section.Name())
if err != nil {
log.Error(3, "Unknown config section %s defined in %s", section.Name(), configFile)
continue
defaultSec, _ = Cfg.NewSection(section.Name())
}
defaultKey, err := defaultSec.GetKey(key.Name())
if err != nil {
log.Error(3, "Unknown config key %s defined in section %s, in file %s", key.Name(), section.Name(), configFile)
continue
defaultKey, _ = defaultSec.NewKey(key.Name(), key.Value())
}
defaultKey.SetValue(key.Value())
}
@@ -389,6 +394,7 @@ func NewConfigContext(args *CommandLineArgs) error {
loadConfiguration(args)
Env = Cfg.Section("").Key("app_mode").MustString("development")
PluginsPath = Cfg.Section("paths").Key("plugins").String()
server := Cfg.Section("server")
AppUrl, AppSubUrl = parseAppUrlAndSubUrl(server)
@@ -420,6 +426,12 @@ func NewConfigContext(args *CommandLineArgs) error {
CookieRememberName = security.Key("cookie_remember_name").String()
DisableGravatar = security.Key("disable_gravatar").MustBool(true)
// read snapshots settings
snapshots := Cfg.Section("snapshots")
ExternalSnapshotUrl = snapshots.Key("external_snapshot_url").String()
ExternalSnapshotName = snapshots.Key("external_snapshot_name").String()
ExternalEnabled = snapshots.Key("external_enabled").MustBool(true)
// read data source proxy white list
DataProxyWhiteList = make(map[string]bool)
for _, hostAndIp := range security.Key("data_source_proxy_whitelist").Strings(" ") {
@@ -436,6 +448,7 @@ func NewConfigContext(args *CommandLineArgs) error {
AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
LoginHint = users.Key("login_hint").String()
// anonymous access
AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
@@ -573,6 +586,14 @@ func initLogging(args *CommandLineArgs) {
"driver": sec.Key("driver").String(),
"conn": sec.Key("conn").String(),
}
case "syslog":
LogConfigs[i] = util.DynMap{
"level": level,
"network": sec.Key("network").MustString(""),
"address": sec.Key("address").MustString(""),
"facility": sec.Key("facility").MustString("local7"),
"tag": sec.Key("tag").MustString(""),
}
}
cfgJsonBytes, _ := json.Marshal(LogConfigs[i])
@@ -607,6 +628,7 @@ func LogConfigurationInfo() {
text.WriteString(fmt.Sprintf(" home: %s\n", HomePath))
text.WriteString(fmt.Sprintf(" data: %s\n", DataPath))
text.WriteString(fmt.Sprintf(" logs: %s\n", LogsPath))
text.WriteString(fmt.Sprintf(" plugins: %s\n", PluginsPath))
log.Info(text.String())
}

98
pkg/util/filepath.go Normal file
View File

@@ -0,0 +1,98 @@
package util
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
//WalkSkipDir is the Error returned when we want to skip descending into a directory
var WalkSkipDir = errors.New("skip this directory")
//WalkFunc is a callback function called for each path as a directory is walked
//If resolvedPath != "", then we are following symbolic links.
type WalkFunc func(resolvedPath string, info os.FileInfo, err error) error
//Walk walks a path, optionally following symbolic links, and for each path,
//it calls the walkFn passed.
//
//It is similar to filepath.Walk, except that it supports symbolic links and
//can detect infinite loops while following sym links.
//It solves the issue where your WalkFunc needs a path relative to the symbolic link
//(resolving links within walkfunc loses the path to the symbolic link for each traversal).
func Walk(path string, followSymlinks bool, detectSymlinkInfiniteLoop bool, walkFn WalkFunc) error {
info, err := os.Lstat(path)
if err != nil {
return err
}
var symlinkPathsFollowed map[string]bool
var resolvedPath string
if followSymlinks {
resolvedPath = path
if detectSymlinkInfiniteLoop {
symlinkPathsFollowed = make(map[string]bool, 8)
}
}
return walk(path, info, resolvedPath, symlinkPathsFollowed, walkFn)
}
//walk walks the path. It is a helper/sibling function to Walk.
//It takes a resolvedPath into consideration. This way, paths being walked are
//always relative to the path argument, even if symbolic links were resolved).
//
//If resolvedPath is "", then we are not following symbolic links.
//If symlinkPathsFollowed is not nil, then we need to detect infinite loop.
func walk(path string, info os.FileInfo, resolvedPath string,
symlinkPathsFollowed map[string]bool, walkFn WalkFunc) error {
if info == nil {
return errors.New("Walk: Nil FileInfo passed")
}
err := walkFn(resolvedPath, info, nil)
if err != nil {
if info.IsDir() && err == WalkSkipDir {
err = nil
}
return err
}
if resolvedPath != "" && info.Mode()&os.ModeSymlink == os.ModeSymlink {
path2, err := os.Readlink(resolvedPath)
if err != nil {
return err
}
//vout("SymLink Path: %v, links to: %v", resolvedPath, path2)
if symlinkPathsFollowed != nil {
if _, ok := symlinkPathsFollowed[path2]; ok {
errMsg := "Potential SymLink Infinite Loop. Path: %v, Link To: %v"
return fmt.Errorf(errMsg, resolvedPath, path2)
} else {
symlinkPathsFollowed[path2] = true
}
}
info2, err := os.Lstat(path2)
if err != nil {
return err
}
return walk(path, info2, path2, symlinkPathsFollowed, walkFn)
}
if info.IsDir() {
list, err := ioutil.ReadDir(path)
if err != nil {
return walkFn(resolvedPath, info, err)
}
for _, fileInfo := range list {
path2 := filepath.Join(path, fileInfo.Name())
var resolvedPath2 string
if resolvedPath != "" {
resolvedPath2 = filepath.Join(resolvedPath, fileInfo.Name())
}
err = walk(path2, fileInfo, resolvedPath2, symlinkPathsFollowed, walkFn)
if err != nil {
return err
}
}
return nil
}
return nil
}