Merge branch 'master' into alerting_definitions

This commit is contained in:
bergquist
2016-05-19 08:05:35 +02:00
42 changed files with 1833 additions and 95 deletions
+2 -1
View File
@@ -1,3 +1,4 @@
{ {
"url": "https://floobits.com/raintank/grafana" "url": "https://floobits.com/raintank/grafana"
} }
+5 -1
View File
@@ -5,7 +5,11 @@
* **Graph**: Adds sort order options for graph tooltip, closes [#1189](https://github.com/grafana/grafana/issues/1189) * **Graph**: Adds sort order options for graph tooltip, closes [#1189](https://github.com/grafana/grafana/issues/1189)
* **Theme**: Add default theme to config file [#5011](https://github.com/grafana/grafana/pull/5011) * **Theme**: Add default theme to config file [#5011](https://github.com/grafana/grafana/pull/5011)
# 3.0.2 Stable (2016-05-16) # 3.0.3 Patch release (unreleased)
* **Time picker**: Fixed issue timepicker and UTC when reading time from URL, fixes [#5078](https://github.com/grafana/grafana/issues/5078)
* **CloudWatch**: Support for Multiple Account by AssumeRole, closes [#3522](https://github.com/grafana/grafana/issues/3522)
# 3.0.2 Patch release (2016-05-16)
* **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988) * **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988)
* **Templating**: Fixed issue detecting dependencies in nested variables, fixes [#4987](https://github.com/grafana/grafana/issues/4987), fixes [#4986](https://github.com/grafana/grafana/issues/4986) * **Templating**: Fixed issue detecting dependencies in nested variables, fixes [#4987](https://github.com/grafana/grafana/issues/4987), fixes [#4986](https://github.com/grafana/grafana/issues/4986)
+5
View File
@@ -124,6 +124,11 @@
"Comment": "v1.0.0", "Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
}, },
{
"ImportPath": "github.com/aws/aws-sdk-go/service/sts",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{ {
"ImportPath": "github.com/bmizerany/assert", "ImportPath": "github.com/bmizerany/assert",
"Comment": "release.r60-6-ge17e998", "Comment": "release.r60-6-ge17e998",
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
package sts
import "github.com/aws/aws-sdk-go/aws/request"
func init() {
initRequest = func(r *request.Request) {
switch r.Operation.Name {
case opAssumeRoleWithSAML, opAssumeRoleWithWebIdentity:
r.Handlers.Sign.Clear() // these operations are unsigned
}
}
}
@@ -0,0 +1,39 @@
package sts_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/awstesting/unit"
"github.com/aws/aws-sdk-go/service/sts"
)
var svc = sts.New(unit.Session, &aws.Config{
Region: aws.String("mock-region"),
})
func TestUnsignedRequest_AssumeRoleWithSAML(t *testing.T) {
req, _ := svc.AssumeRoleWithSAMLRequest(&sts.AssumeRoleWithSAMLInput{
PrincipalArn: aws.String("ARN01234567890123456789"),
RoleArn: aws.String("ARN01234567890123456789"),
SAMLAssertion: aws.String("ASSERT"),
})
err := req.Sign()
assert.NoError(t, err)
assert.Equal(t, "", req.HTTPRequest.Header.Get("Authorization"))
}
func TestUnsignedRequest_AssumeRoleWithWebIdentity(t *testing.T) {
req, _ := svc.AssumeRoleWithWebIdentityRequest(&sts.AssumeRoleWithWebIdentityInput{
RoleArn: aws.String("ARN01234567890123456789"),
RoleSessionName: aws.String("SESSION"),
WebIdentityToken: aws.String("TOKEN"),
})
err := req.Sign()
assert.NoError(t, err)
assert.Equal(t, "", req.HTTPRequest.Header.Get("Authorization"))
}
@@ -0,0 +1,149 @@
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
package sts_test
import (
"bytes"
"fmt"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
)
var _ time.Duration
var _ bytes.Buffer
func ExampleSTS_AssumeRole() {
svc := sts.New(session.New())
params := &sts.AssumeRoleInput{
RoleArn: aws.String("arnType"), // Required
RoleSessionName: aws.String("roleSessionNameType"), // Required
DurationSeconds: aws.Int64(1),
ExternalId: aws.String("externalIdType"),
Policy: aws.String("sessionPolicyDocumentType"),
SerialNumber: aws.String("serialNumberType"),
TokenCode: aws.String("tokenCodeType"),
}
resp, err := svc.AssumeRole(params)
if err != nil {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
return
}
// Pretty-print the response data.
fmt.Println(resp)
}
func ExampleSTS_AssumeRoleWithSAML() {
svc := sts.New(session.New())
params := &sts.AssumeRoleWithSAMLInput{
PrincipalArn: aws.String("arnType"), // Required
RoleArn: aws.String("arnType"), // Required
SAMLAssertion: aws.String("SAMLAssertionType"), // Required
DurationSeconds: aws.Int64(1),
Policy: aws.String("sessionPolicyDocumentType"),
}
resp, err := svc.AssumeRoleWithSAML(params)
if err != nil {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
return
}
// Pretty-print the response data.
fmt.Println(resp)
}
func ExampleSTS_AssumeRoleWithWebIdentity() {
svc := sts.New(session.New())
params := &sts.AssumeRoleWithWebIdentityInput{
RoleArn: aws.String("arnType"), // Required
RoleSessionName: aws.String("roleSessionNameType"), // Required
WebIdentityToken: aws.String("clientTokenType"), // Required
DurationSeconds: aws.Int64(1),
Policy: aws.String("sessionPolicyDocumentType"),
ProviderId: aws.String("urlType"),
}
resp, err := svc.AssumeRoleWithWebIdentity(params)
if err != nil {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
return
}
// Pretty-print the response data.
fmt.Println(resp)
}
func ExampleSTS_DecodeAuthorizationMessage() {
svc := sts.New(session.New())
params := &sts.DecodeAuthorizationMessageInput{
EncodedMessage: aws.String("encodedMessageType"), // Required
}
resp, err := svc.DecodeAuthorizationMessage(params)
if err != nil {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
return
}
// Pretty-print the response data.
fmt.Println(resp)
}
func ExampleSTS_GetFederationToken() {
svc := sts.New(session.New())
params := &sts.GetFederationTokenInput{
Name: aws.String("userNameType"), // Required
DurationSeconds: aws.Int64(1),
Policy: aws.String("sessionPolicyDocumentType"),
}
resp, err := svc.GetFederationToken(params)
if err != nil {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
return
}
// Pretty-print the response data.
fmt.Println(resp)
}
func ExampleSTS_GetSessionToken() {
svc := sts.New(session.New())
params := &sts.GetSessionTokenInput{
DurationSeconds: aws.Int64(1),
SerialNumber: aws.String("serialNumberType"),
TokenCode: aws.String("tokenCodeType"),
}
resp, err := svc.GetSessionToken(params)
if err != nil {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
return
}
// Pretty-print the response data.
fmt.Println(resp)
}
+130
View File
@@ -0,0 +1,130 @@
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
package sts
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol/query"
"github.com/aws/aws-sdk-go/private/signer/v4"
)
// The AWS Security Token Service (STS) is a web service that enables you to
// request temporary, limited-privilege credentials for AWS Identity and Access
// Management (IAM) users or for users that you authenticate (federated users).
// This guide provides descriptions of the STS API. For more detailed information
// about using this service, go to Temporary Security Credentials (http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html).
//
// As an alternative to using the API, you can use one of the AWS SDKs, which
// consist of libraries and sample code for various programming languages and
// platforms (Java, Ruby, .NET, iOS, Android, etc.). The SDKs provide a convenient
// way to create programmatic access to STS. For example, the SDKs take care
// of cryptographically signing requests, managing errors, and retrying requests
// automatically. For information about the AWS SDKs, including how to download
// and install them, see the Tools for Amazon Web Services page (http://aws.amazon.com/tools/).
// For information about setting up signatures and authorization through the
// API, go to Signing AWS API Requests (http://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html"
// target="_blank) in the AWS General Reference. For general information about
// the Query API, go to Making Query Requests (http://docs.aws.amazon.com/IAM/latest/UserGuide/IAM_UsingQueryAPI.html"
// target="_blank) in Using IAM. For information about using security tokens
// with other AWS products, go to AWS Services That Work with IAM (http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html)
// in the Using IAM.
//
// If you're new to AWS and need additional technical information about a specific
// AWS product, you can find the product's technical documentation at http://aws.amazon.com/documentation/
// (http://aws.amazon.com/documentation/" target="_blank).
//
// Endpoints
//
// The AWS Security Token Service (STS) has a default endpoint of https://sts.amazonaws.com
// that maps to the US East (N. Virginia) region. Additional regions are available,
// but must first be activated in the AWS Management Console before you can
// use a different region's endpoint. For more information about activating
// a region for STS see Activating STS in a New Region (http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html)
// in the Using IAM.
//
// For information about STS endpoints, see Regions and Endpoints (http://docs.aws.amazon.com/general/latest/gr/rande.html#sts_region)
// in the AWS General Reference.
//
// Recording API requests
//
// STS supports AWS CloudTrail, which is a service that records AWS calls for
// your AWS account and delivers log files to an Amazon S3 bucket. By using
// information collected by CloudTrail, you can determine what requests were
// successfully made to STS, who made the request, when it was made, and so
// on. To learn more about CloudTrail, including how to turn it on and find
// your log files, see the AWS CloudTrail User Guide (http://docs.aws.amazon.com/awscloudtrail/latest/userguide/what_is_cloud_trail_top_level.html).
//The service client's operations are safe to be used concurrently.
// It is not safe to mutate any of the client's properties though.
type STS struct {
*client.Client
}
// Used for custom client initialization logic
var initClient func(*client.Client)
// Used for custom request initialization logic
var initRequest func(*request.Request)
// A ServiceName is the name of the service the client will make API calls to.
const ServiceName = "sts"
// New creates a new instance of the STS client with a session.
// If additional configuration is needed for the client instance use the optional
// aws.Config parameter to add your extra config.
//
// Example:
// // Create a STS client from just a session.
// svc := sts.New(mySession)
//
// // Create a STS client with additional configuration
// svc := sts.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *STS {
c := p.ClientConfig(ServiceName, cfgs...)
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion)
}
// newClient creates, initializes and returns a new service client instance.
func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string) *STS {
svc := &STS{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: ServiceName,
SigningRegion: signingRegion,
Endpoint: endpoint,
APIVersion: "2011-06-15",
},
handlers,
),
}
// Handlers
svc.Handlers.Sign.PushBack(v4.Sign)
svc.Handlers.Build.PushBack(query.Build)
svc.Handlers.Unmarshal.PushBack(query.Unmarshal)
svc.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta)
svc.Handlers.UnmarshalError.PushBack(query.UnmarshalError)
// Run custom client initialization if present
if initClient != nil {
initClient(svc.Client)
}
return svc
}
// newRequest creates a new request for a STS operation and runs any
// custom request initialization.
func (c *STS) newRequest(op *request.Operation, params, data interface{}) *request.Request {
req := c.NewRequest(op, params, data)
// Run custom request initialization if present
if initRequest != nil {
initRequest(req)
}
return req
}
@@ -0,0 +1,38 @@
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
// Package stsiface provides an interface for the AWS Security Token Service.
package stsiface
import (
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/sts"
)
// STSAPI is the interface type for sts.STS.
type STSAPI interface {
AssumeRoleRequest(*sts.AssumeRoleInput) (*request.Request, *sts.AssumeRoleOutput)
AssumeRole(*sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error)
AssumeRoleWithSAMLRequest(*sts.AssumeRoleWithSAMLInput) (*request.Request, *sts.AssumeRoleWithSAMLOutput)
AssumeRoleWithSAML(*sts.AssumeRoleWithSAMLInput) (*sts.AssumeRoleWithSAMLOutput, error)
AssumeRoleWithWebIdentityRequest(*sts.AssumeRoleWithWebIdentityInput) (*request.Request, *sts.AssumeRoleWithWebIdentityOutput)
AssumeRoleWithWebIdentity(*sts.AssumeRoleWithWebIdentityInput) (*sts.AssumeRoleWithWebIdentityOutput, error)
DecodeAuthorizationMessageRequest(*sts.DecodeAuthorizationMessageInput) (*request.Request, *sts.DecodeAuthorizationMessageOutput)
DecodeAuthorizationMessage(*sts.DecodeAuthorizationMessageInput) (*sts.DecodeAuthorizationMessageOutput, error)
GetFederationTokenRequest(*sts.GetFederationTokenInput) (*request.Request, *sts.GetFederationTokenOutput)
GetFederationToken(*sts.GetFederationTokenInput) (*sts.GetFederationTokenOutput, error)
GetSessionTokenRequest(*sts.GetSessionTokenInput) (*request.Request, *sts.GetSessionTokenOutput)
GetSessionToken(*sts.GetSessionTokenInput) (*sts.GetSessionTokenOutput, error)
}
var _ STSAPI = (*sts.STS)(nil)
+1 -1
View File
@@ -8,7 +8,7 @@ graphite:
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro
fake-data-gen: fake-graphite-data:
image: grafana/fake-data-gen image: grafana/fake-data-gen
net: bridge net: bridge
environment: environment:
+8
View File
@@ -4,3 +4,11 @@ influxdb:
- "2004:2004" - "2004:2004"
- "8083:8083" - "8083:8083"
- "8086:8086" - "8086:8086"
fake-influxdb-data:
image: grafana/fake-data-gen
net: bridge
environment:
FD_DATASOURCE: influxdb
FD_PORT: 8086
+1 -1
View File
@@ -3,7 +3,7 @@ opentsdb:
ports: ports:
- "4242:4242" - "4242:4242"
fake-data-gen: fake-opentsdb-data:
image: grafana/fake-data-gen image: grafana/fake-data-gen
net: bridge net: bridge
environment: environment:
+10
View File
@@ -1,6 +1,16 @@
prometheus: prometheus:
build: blocks/prometheus build: blocks/prometheus
net: bridge
ports: ports:
- "9090:9090" - "9090:9090"
volumes: volumes:
- /var/docker/prometheus:/prometheus-data - /var/docker/prometheus:/prometheus-data
fake-prometheus-data:
image: grafana/fake-data-gen
net: bridge
ports:
- "9091:9091"
environment:
FD_DATASOURCE: prom
+2
View File
@@ -26,6 +26,8 @@ Name | The data source name, important that this is the same as in Grafana v1.x
Default | Default data source means that it will be pre-selected for new panels. Default | Default data source means that it will be pre-selected for new panels.
Credentials profile name | Specify the name of the profile to use (if you use `~/aws/credentials` file), leave blank for default. This option was introduced in Grafana 2.5.1 Credentials profile name | Specify the name of the profile to use (if you use `~/aws/credentials` file), leave blank for default. This option was introduced in Grafana 2.5.1
Default Region | Used in query editor to set region (can be changed on per query basis) Default Region | Used in query editor to set region (can be changed on per query basis)
Custom Metrics namespace | Specify the CloudWatch namespace of Custom metrics
Assume Role Arn | Specify the ARN of the role to assume
## Authentication ## Authentication
+1 -1
View File
@@ -7,7 +7,7 @@ page_keywords: grafana, opentsdb, documentation
# OpenTSDB Guide # OpenTSDB Guide
The newest release of Grafana adds additional functionality when using an OpenTSDB Data source. The newest release of Grafana adds additional functionality when using an OpenTSDB Data source.
![](/img/v2/add_OpenTSDB.jpg) ![](/img/v2/add_OpenTSDB.png)
1. Open the side menu by clicking the the Grafana icon in the top header. 1. Open the side menu by clicking the the Grafana icon in the top header.
2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`. 2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`.
+1
View File
@@ -18,4 +18,5 @@ dashboards, creating users and updating data sources.
* [User API](/http_api/user/) * [User API](/http_api/user/)
* [Admin API](/http_api/admin/) * [Admin API](/http_api/admin/)
* [Snapshot API](/http_api/snapshot/) * [Snapshot API](/http_api/snapshot/)
* [Preferences API](/http_api/preferences/)
* [Other API](/http_api/other/) * [Other API](/http_api/other/)
+100
View File
@@ -0,0 +1,100 @@
----
page_title: Preferences API
page_description: Grafana Preferences API Reference
page_keywords: grafana, preferences, http, api, documentation
---
# User and Org Preferences API
Keys:
- **theme** - One of: ``light``, ``dark``, or an empty string for the default theme
- **homeDashboardId** - The numerical ``:id`` of a favorited dashboard, default: ``0``
- **timezone** - One of: ``utc``, ``browser``, or an empty string for the default
Omitting a key will cause the current value to be replaced with the
system default value.
## Get Current User Prefs
`GET /api/user/preferences`
**Example Request**:
GET /api/user/preferences HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
**Example Response**:
HTTP/1.1 200
Content-Type: application/json
{"theme":"","homeDashboardId":0,"timezone":""}
## Update Current User Prefs
`PUT /api/user/preferences`
**Example Request**:
PUT /api/user/preferences HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
{
"theme": "",
"homeDashboardId":0,
"timezone":"utc"
}
**Example Response**:
HTTP/1.1 200
Content-Type: text/plain; charset=utf-8
{"message":"Preferences updated"}
## Get Current Org Prefs
`GET /api/org/preferences`
**Example Request**:
GET /api/org/preferences HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
**Example Response**:
HTTP/1.1 200
Content-Type: application/json
{"theme":"","homeDashboardId":0,"timezone":""}
## Update Current Org Prefs
`PUT /api/org/preferences`
**Example Request**:
PUT /api/org/preferences HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
{
"theme": "",
"homeDashboardId":0,
"timezone":"utc"
}
**Example Response**:
HTTP/1.1 200
Content-Type: text/plain; charset=utf-8
{"message":"Preferences updated"}
+2 -2
View File
@@ -1,4 +1,4 @@
{ {
"stable": "3.0.1", "stable": "3.0.2",
"testing": "3.0.1" "testing": "3.0.2"
} }
+1 -1
View File
@@ -13,7 +13,7 @@
"zone.js": "^0.6.6", "zone.js": "^0.6.6",
"autoprefixer": "^6.3.3", "autoprefixer": "^6.3.3",
"es6-promise": "^3.0.2", "es6-promise": "^3.0.2",
"es6-shim": "^0.35.0", "es6-shim": "^0.35.1",
"expect.js": "~0.2.0", "expect.js": "~0.2.0",
"glob": "~3.2.7", "glob": "~3.2.7",
"grunt": "~0.4.0", "grunt": "~0.4.0",
+1 -1
View File
@@ -14,6 +14,6 @@ CONF_DIR=/etc/grafana
CONF_FILE=/etc/grafana/grafana.ini CONF_FILE=/etc/grafana/grafana.ini
RESTART_ON_UPGRADE=true RESTART_ON_UPGRADE=false
PLUGINS_DIR=/var/lib/grafana/plugins PLUGINS_DIR=/var/lib/grafana/plugins
+1 -1
View File
@@ -14,6 +14,6 @@ CONF_DIR=/etc/grafana
CONF_FILE=/etc/grafana/grafana.ini CONF_FILE=/etc/grafana/grafana.ini
RESTART_ON_UPGRADE=true RESTART_ON_UPGRADE=false
PLUGINS_DIR=/var/lib/grafana/plugins PLUGINS_DIR=/var/lib/grafana/plugins
+81 -32
View File
@@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"io/ioutil" "io/ioutil"
"strings"
"sync"
"time" "time"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
@@ -14,6 +16,8 @@ import (
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
) )
@@ -44,31 +48,96 @@ func init() {
} }
} }
var awsCredentials map[string]*credentials.Credentials = make(map[string]*credentials.Credentials) type cache struct {
credential *credentials.Credentials
expiration *time.Time
}
func getCredentials(profile string) *credentials.Credentials { var awsCredentialCache map[string]cache = make(map[string]cache)
if _, ok := awsCredentials[profile]; ok { var credentialCacheLock sync.RWMutex
return awsCredentials[profile]
func getCredentials(profile string, region string, assumeRoleArn string) *credentials.Credentials {
credentialCacheLock.RLock()
if _, ok := awsCredentialCache[profile]; ok {
if awsCredentialCache[profile].expiration != nil &&
(*awsCredentialCache[profile].expiration).After(time.Now().UTC()) {
result := awsCredentialCache[profile].credential
credentialCacheLock.RUnlock()
return result
}
}
credentialCacheLock.RUnlock()
accessKeyId := ""
secretAccessKey := ""
sessionToken := ""
var expiration *time.Time
expiration = nil
if strings.Index(assumeRoleArn, "arn:aws:iam:") == 0 {
params := &sts.AssumeRoleInput{
RoleArn: aws.String(assumeRoleArn),
RoleSessionName: aws.String("GrafanaSession"),
DurationSeconds: aws.Int64(900),
}
stsSess := session.New()
stsCreds := credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: profile},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(stsSess), ExpiryWindow: 5 * time.Minute},
})
stsConfig := &aws.Config{
Region: aws.String(region),
Credentials: stsCreds,
}
svc := sts.New(session.New(stsConfig), stsConfig)
resp, err := svc.AssumeRole(params)
if err != nil {
// ignore
log.Error(3, "CloudWatch: Failed to assume role", err)
}
if resp.Credentials != nil {
accessKeyId = *resp.Credentials.AccessKeyId
secretAccessKey = *resp.Credentials.SecretAccessKey
sessionToken = *resp.Credentials.SessionToken
expiration = resp.Credentials.Expiration
}
} }
sess := session.New() sess := session.New()
creds := credentials.NewChainCredentials( creds := credentials.NewChainCredentials(
[]credentials.Provider{ []credentials.Provider{
&credentials.StaticProvider{Value: credentials.Value{
AccessKeyID: accessKeyId,
SecretAccessKey: secretAccessKey,
SessionToken: sessionToken,
}},
&credentials.EnvProvider{}, &credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: profile}, &credentials.SharedCredentialsProvider{Filename: "", Profile: profile},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
}) })
awsCredentials[profile] = creds credentialCacheLock.Lock()
awsCredentialCache[profile] = cache{
credential: creds,
expiration: expiration,
}
credentialCacheLock.Unlock()
return creds return creds
} }
func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { func getAwsConfig(req *cwRequest) *aws.Config {
assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString()
cfg := &aws.Config{ cfg := &aws.Config{
Region: aws.String(req.Region), Region: aws.String(req.Region),
Credentials: getCredentials(req.DataSource.Database), Credentials: getCredentials(req.DataSource.Database, req.Region, assumeRoleArn),
} }
return cfg
}
func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
cfg := getAwsConfig(req)
svc := cloudwatch.New(session.New(cfg), cfg) svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct { reqParam := &struct {
@@ -104,11 +173,7 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
} }
func handleListMetrics(req *cwRequest, c *middleware.Context) { func handleListMetrics(req *cwRequest, c *middleware.Context) {
cfg := &aws.Config{ cfg := getAwsConfig(req)
Region: aws.String(req.Region),
Credentials: getCredentials(req.DataSource.Database),
}
svc := cloudwatch.New(session.New(cfg), cfg) svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct { reqParam := &struct {
@@ -144,11 +209,7 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) {
} }
func handleDescribeAlarms(req *cwRequest, c *middleware.Context) { func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
cfg := &aws.Config{ cfg := getAwsConfig(req)
Region: aws.String(req.Region),
Credentials: getCredentials(req.DataSource.Database),
}
svc := cloudwatch.New(session.New(cfg), cfg) svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct { reqParam := &struct {
@@ -187,11 +248,7 @@ func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
} }
func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
cfg := &aws.Config{ cfg := getAwsConfig(req)
Region: aws.String(req.Region),
Credentials: getCredentials(req.DataSource.Database),
}
svc := cloudwatch.New(session.New(cfg), cfg) svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct { reqParam := &struct {
@@ -227,11 +284,7 @@ func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
} }
func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) { func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
cfg := &aws.Config{ cfg := getAwsConfig(req)
Region: aws.String(req.Region),
Credentials: getCredentials(req.DataSource.Database),
}
svc := cloudwatch.New(session.New(cfg), cfg) svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct { reqParam := &struct {
@@ -263,11 +316,7 @@ func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
} }
func handleDescribeInstances(req *cwRequest, c *middleware.Context) { func handleDescribeInstances(req *cwRequest, c *middleware.Context) {
cfg := &aws.Config{ cfg := getAwsConfig(req)
Region: aws.String(req.Region),
Credentials: getCredentials(req.DataSource.Database),
}
svc := ec2.New(session.New(cfg), cfg) svc := ec2.New(session.New(cfg), cfg)
reqParam := &struct { reqParam := &struct {
+10 -8
View File
@@ -166,7 +166,8 @@ func handleGetMetrics(req *cwRequest, c *middleware.Context) {
} }
} else { } else {
var err error var err error
if namespaceMetrics, err = getMetricsForCustomMetrics(req.Region, reqParam.Parameters.Namespace, req.DataSource.Database, getAllMetrics); err != nil { assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString()
if namespaceMetrics, err = getMetricsForCustomMetrics(req.Region, reqParam.Parameters.Namespace, req.DataSource.Database, assumeRoleArn, getAllMetrics); err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err) c.JsonApiErr(500, "Unable to call AWS API", err)
return return
} }
@@ -199,7 +200,8 @@ func handleGetDimensions(req *cwRequest, c *middleware.Context) {
} }
} else { } else {
var err error var err error
if dimensionValues, err = getDimensionsForCustomMetrics(req.Region, reqParam.Parameters.Namespace, req.DataSource.Database, getAllMetrics); err != nil { assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString()
if dimensionValues, err = getDimensionsForCustomMetrics(req.Region, reqParam.Parameters.Namespace, req.DataSource.Database, assumeRoleArn, getAllMetrics); err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err) c.JsonApiErr(500, "Unable to call AWS API", err)
return return
} }
@@ -214,10 +216,10 @@ func handleGetDimensions(req *cwRequest, c *middleware.Context) {
c.JSON(200, result) c.JSON(200, result)
} }
func getAllMetrics(region string, namespace string, database string) (cloudwatch.ListMetricsOutput, error) { func getAllMetrics(region string, namespace string, database string, assumeRoleArn string) (cloudwatch.ListMetricsOutput, error) {
cfg := &aws.Config{ cfg := &aws.Config{
Region: aws.String(region), Region: aws.String(region),
Credentials: getCredentials(database), Credentials: getCredentials(database, region, assumeRoleArn),
} }
svc := cloudwatch.New(session.New(cfg), cfg) svc := cloudwatch.New(session.New(cfg), cfg)
@@ -244,8 +246,8 @@ func getAllMetrics(region string, namespace string, database string) (cloudwatch
var metricsCacheLock sync.Mutex var metricsCacheLock sync.Mutex
func getMetricsForCustomMetrics(region string, namespace string, database string, getAllMetrics func(string, string, string) (cloudwatch.ListMetricsOutput, error)) ([]string, error) { func getMetricsForCustomMetrics(region string, namespace string, database string, assumeRoleArn string, getAllMetrics func(string, string, string, string) (cloudwatch.ListMetricsOutput, error)) ([]string, error) {
result, err := getAllMetrics(region, namespace, database) result, err := getAllMetrics(region, namespace, database, assumeRoleArn)
if err != nil { if err != nil {
return []string{}, err return []string{}, err
} }
@@ -282,8 +284,8 @@ func getMetricsForCustomMetrics(region string, namespace string, database string
var dimensionsCacheLock sync.Mutex var dimensionsCacheLock sync.Mutex
func getDimensionsForCustomMetrics(region string, namespace string, database string, getAllMetrics func(string, string, string) (cloudwatch.ListMetricsOutput, error)) ([]string, error) { func getDimensionsForCustomMetrics(region string, namespace string, database string, assumeRoleArn string, getAllMetrics func(string, string, string, string) (cloudwatch.ListMetricsOutput, error)) ([]string, error) {
result, err := getAllMetrics(region, namespace, database) result, err := getAllMetrics(region, namespace, database, assumeRoleArn)
if err != nil { if err != nil {
return []string{}, err return []string{}, err
} }
+6 -4
View File
@@ -14,7 +14,8 @@ func TestCloudWatchMetrics(t *testing.T) {
region := "us-east-1" region := "us-east-1"
namespace := "Foo" namespace := "Foo"
database := "default" database := "default"
f := func(region string, namespace string, database string) (cloudwatch.ListMetricsOutput, error) { assumeRoleArn := ""
f := func(region string, namespace string, database string, assumeRoleArn string) (cloudwatch.ListMetricsOutput, error) {
return cloudwatch.ListMetricsOutput{ return cloudwatch.ListMetricsOutput{
Metrics: []*cloudwatch.Metric{ Metrics: []*cloudwatch.Metric{
{ {
@@ -28,7 +29,7 @@ func TestCloudWatchMetrics(t *testing.T) {
}, },
}, nil }, nil
} }
metrics, _ := getMetricsForCustomMetrics(region, namespace, database, f) metrics, _ := getMetricsForCustomMetrics(region, namespace, database, assumeRoleArn, f)
Convey("Should contain Test_MetricName", func() { Convey("Should contain Test_MetricName", func() {
So(metrics, ShouldContain, "Test_MetricName") So(metrics, ShouldContain, "Test_MetricName")
@@ -39,7 +40,8 @@ func TestCloudWatchMetrics(t *testing.T) {
region := "us-east-1" region := "us-east-1"
namespace := "Foo" namespace := "Foo"
database := "default" database := "default"
f := func(region string, namespace string, database string) (cloudwatch.ListMetricsOutput, error) { assumeRoleArn := ""
f := func(region string, namespace string, database string, assumeRoleArn string) (cloudwatch.ListMetricsOutput, error) {
return cloudwatch.ListMetricsOutput{ return cloudwatch.ListMetricsOutput{
Metrics: []*cloudwatch.Metric{ Metrics: []*cloudwatch.Metric{
{ {
@@ -53,7 +55,7 @@ func TestCloudWatchMetrics(t *testing.T) {
}, },
}, nil }, nil
} }
dimensionKeys, _ := getDimensionsForCustomMetrics(region, namespace, database, f) dimensionKeys, _ := getDimensionsForCustomMetrics(region, namespace, database, assumeRoleArn, f)
Convey("Should contain Test_DimensionName", func() { Convey("Should contain Test_DimensionName", func() {
So(dimensionKeys, ShouldContain, "Test_DimensionName") So(dimensionKeys, ShouldContain, "Test_DimensionName")
+1
View File
@@ -142,6 +142,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
"buildstamp": setting.BuildStamp, "buildstamp": setting.BuildStamp,
"latestVersion": plugins.GrafanaLatestVersion, "latestVersion": plugins.GrafanaLatestVersion,
"hasUpdate": plugins.GrafanaHasUpdate, "hasUpdate": plugins.GrafanaHasUpdate,
"env": setting.Env,
}, },
"alertingEnabled": setting.AlertingEnabled, "alertingEnabled": setting.AlertingEnabled,
} }
+10 -1
View File
@@ -24,7 +24,16 @@ func GetPluginSettings(orgId int64) (map[string]*m.PluginSettingInfoDTO, error)
} }
// default to enabled true // default to enabled true
opt := &m.PluginSettingInfoDTO{Enabled: true} opt := &m.PluginSettingInfoDTO{
PluginId: pluginDef.Id,
OrgId: orgId,
Enabled: true,
}
// apps are disabled by default
if pluginDef.Type == PluginTypeApp {
opt.Enabled = false
}
// if it's included in app check app settings // if it's included in app check app settings
if pluginDef.IncludedInAppId != "" { if pluginDef.IncludedInAppId != "" {
+16 -6
View File
@@ -534,6 +534,17 @@ var logLevels = map[string]int{
"Critical": 5, "Critical": 5,
} }
func getLogLevel(key string, defaultName string) (string, int) {
levelName := Cfg.Section(key).Key("level").In(defaultName, []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
level, ok := logLevels[levelName]
if !ok {
log.Fatal(4, "Unknown log level: %s", levelName)
}
return levelName, level
}
func initLogging(args *CommandLineArgs) { func initLogging(args *CommandLineArgs) {
//close any existing log handlers. //close any existing log handlers.
log.Close() log.Close()
@@ -541,8 +552,12 @@ func initLogging(args *CommandLineArgs) {
LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), ",") LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), ",")
LogsPath = makeAbsolute(Cfg.Section("paths").Key("logs").String(), HomePath) LogsPath = makeAbsolute(Cfg.Section("paths").Key("logs").String(), HomePath)
defaultLevelName, _ := getLogLevel("log", "Info")
LogConfigs = make([]util.DynMap, len(LogModes)) LogConfigs = make([]util.DynMap, len(LogModes))
for i, mode := range LogModes { for i, mode := range LogModes {
mode = strings.TrimSpace(mode) mode = strings.TrimSpace(mode)
sec, err := Cfg.GetSection("log." + mode) sec, err := Cfg.GetSection("log." + mode)
if err != nil { if err != nil {
@@ -550,12 +565,7 @@ func initLogging(args *CommandLineArgs) {
} }
// Log level. // Log level.
levelName := Cfg.Section("log."+mode).Key("level").In("Trace", _, level := getLogLevel("log."+mode, defaultLevelName)
[]string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
level, ok := logLevels[levelName]
if !ok {
log.Fatal(4, "Unknown log level: %s", levelName)
}
// Generate log configuration. // Generate log configuration.
switch mode { switch mode {
+3 -1
View File
@@ -42,7 +42,9 @@ export class GrafanaApp {
app.constant('grafanaVersion', "@grafanaVersion@"); app.constant('grafanaVersion', "@grafanaVersion@");
app.config(($locationProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) => { app.config(($locationProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) => {
//$compileProvider.debugInfoEnabled(false); if (config.buildInfo.env !== 'development') {
$compileProvider.debugInfoEnabled(false);
}
this.registerFunctions.controller = $controllerProvider.register; this.registerFunctions.controller = $controllerProvider.register;
this.registerFunctions.directive = $compileProvider.directive; this.registerFunctions.directive = $compileProvider.directive;
+1 -1
View File
@@ -28,7 +28,7 @@ export function parse(text, roundUp?) {
mathString = text.substring(index + 2); mathString = text.substring(index + 2);
} }
// We're going to just require ISO8601 timestamps, k? // We're going to just require ISO8601 timestamps, k?
time = moment(parseString); time = moment(parseString, moment.ISO_8601);
} }
if (!mathString.length) { if (!mathString.length) {
+2
View File
@@ -396,6 +396,7 @@ function($, _) {
kbn.valueFormats.ev = kbn.formatBuilders.decimalSIPrefix('eV'); kbn.valueFormats.ev = kbn.formatBuilders.decimalSIPrefix('eV');
kbn.valueFormats.amp = kbn.formatBuilders.decimalSIPrefix('A'); kbn.valueFormats.amp = kbn.formatBuilders.decimalSIPrefix('A');
kbn.valueFormats.volt = kbn.formatBuilders.decimalSIPrefix('V'); kbn.valueFormats.volt = kbn.formatBuilders.decimalSIPrefix('V');
kbn.valueFormats.dBm = kbn.formatBuilders.decimalSIPrefix('dBm');
// Temperature // Temperature
kbn.valueFormats.celsius = kbn.formatBuilders.fixedUnit('°C'); kbn.valueFormats.celsius = kbn.formatBuilders.fixedUnit('°C');
@@ -677,6 +678,7 @@ function($, _) {
{text: 'electron volt (eV)', value: 'ev' }, {text: 'electron volt (eV)', value: 'ev' },
{text: 'Ampere (A)', value: 'amp' }, {text: 'Ampere (A)', value: 'amp' },
{text: 'Volt (V)', value: 'volt' }, {text: 'Volt (V)', value: 'volt' },
{text: 'Decibel-milliwatt (dBm)', value: 'dBm' },
] ]
}, },
{ {
@@ -55,10 +55,11 @@ define([
}, this); }, this);
}); });
promiseCached = $q.all(promises) promiseCached = $q.all(promises).then(function() {
.then(function() { return list;
return list; }).catch(function(err) {
}); $rootScope.appEvent('alert-error', ['Annotations failed', (err.message || err)]);
});
return promiseCached; return promiseCached;
}; };
@@ -65,7 +65,7 @@ function (angular, $, _, moment) {
// cleans meta data and other non peristent state // cleans meta data and other non peristent state
p.getSaveModelClone = function() { p.getSaveModelClone = function() {
var copy = angular.copy(this); var copy = $.extend(true, {}, this);
delete copy.meta; delete copy.meta;
return copy; return copy;
}; };
+1 -1
View File
@@ -50,7 +50,7 @@ define([
if (!isNaN(value)) { if (!isNaN(value)) {
var epoch = parseInt(value); var epoch = parseInt(value);
return moment(epoch); return moment.utc(epoch);
} }
return null; return null;
+9
View File
@@ -90,6 +90,15 @@ export class PanelCtrl {
this.addEditorTab('General', 'public/app/partials/panelgeneral.html'); this.addEditorTab('General', 'public/app/partials/panelgeneral.html');
this.editModeInitiated = true; this.editModeInitiated = true;
this.events.emit('init-edit-mode', null); this.events.emit('init-edit-mode', null);
var route = this.$injector.get('$route');
if (route.current.params.editorTab) {
this.editorTabs.forEach((tab, i) => {
if (tab.title === route.current.params.editorTab) {
this.editorTabIndex = i;
}
});
}
} }
addEditorTab(title, directiveFn, index?) { addEditorTab(title, directiveFn, index?) {
@@ -24,4 +24,11 @@
Namespaces of Custom Metrics Namespaces of Custom Metrics
</info-popover> </info-popover>
</div> </div>
<div class="gf-form">
<label class="gf-form-label width-13">Assume Role ARN</label>
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.jsonData.assumeRoleArn' placeholder="arn:aws:iam:*"></input>
<info-popover mode="right-absolute">
ARN of Assume Role
</info-popover>
</div>
</div> </div>
@@ -10,7 +10,7 @@ import PrometheusMetricFindQuery from './metric_find_query';
var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/; var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/;
/** @ngInject */ /** @ngInject */
export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv) { export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv, timeSrv) {
this.type = 'prometheus'; this.type = 'prometheus';
this.editorSrc = 'app/features/prometheus/partials/query.editor.html'; this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
this.name = instanceSettings.name; this.name = instanceSettings.name;
@@ -43,7 +43,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&'); return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&');
} }
function interpolateQueryExpr(value, variable, defaultFormatFn) { this.interpolateQueryExpr = function(value, variable, defaultFormatFn) {
// if no multi or include all do not regexEscape // if no multi or include all do not regexEscape
if (!variable.multi && !variable.includeAll) { if (!variable.multi && !variable.includeAll) {
return value; return value;
@@ -59,6 +59,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
// Called once per panel (graph) // Called once per panel (graph)
this.query = function(options) { this.query = function(options) {
var self = this;
var start = getPrometheusTime(options.range.from, false); var start = getPrometheusTime(options.range.from, false);
var end = getPrometheusTime(options.range.to, true); var end = getPrometheusTime(options.range.to, true);
@@ -73,7 +74,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
activeTargets.push(target); activeTargets.push(target);
var query: any = {}; var query: any = {};
query.expr = templateSrv.replace(target.expr, options.scopedVars, interpolateQueryExpr); query.expr = templateSrv.replace(target.expr, options.scopedVars, self.interpolateQueryExpr);
var interval = target.interval || options.interval; var interval = target.interval || options.interval;
var intervalFactor = target.intervalFactor || 1; var intervalFactor = target.intervalFactor || 1;
@@ -99,7 +100,6 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
return this.performTimeSeriesQuery(query, start, end); return this.performTimeSeriesQuery(query, start, end);
}, this)); }, this));
var self = this;
return $q.all(allQueryPromise) return $q.all(allQueryPromise)
.then(function(allResponse) { .then(function(allResponse) {
var result = []; var result = [];
@@ -140,12 +140,12 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
var interpolated; var interpolated;
try { try {
interpolated = templateSrv.replace(query); interpolated = templateSrv.replace(query, {}, this.interpolateQueryExpr);
} catch (err) { } catch (err) {
return $q.reject(err); return $q.reject(err);
} }
var metricFindQuery = new PrometheusMetricFindQuery(this, interpolated); var metricFindQuery = new PrometheusMetricFindQuery(this, interpolated, timeSrv);
return metricFindQuery.process(); return metricFindQuery.process();
}; };
@@ -160,7 +160,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
var interpolated; var interpolated;
try { try {
interpolated = templateSrv.replace(expr, {}, interpolateQueryExpr); interpolated = templateSrv.replace(expr, {}, this.interpolateQueryExpr);
} catch (err) { } catch (err) {
return $q.reject(err); return $q.reject(err);
} }
@@ -1,13 +1,13 @@
define([ define([
'lodash', 'lodash'
'moment',
], ],
function (_, moment) { function (_) {
'use strict'; 'use strict';
function PrometheusMetricFindQuery(datasource, query) { function PrometheusMetricFindQuery(datasource, query, timeSrv) {
this.datasource = datasource; this.datasource = datasource;
this.query = query; this.query = query;
this.range = timeSrv.timeRange();
} }
PrometheusMetricFindQuery.prototype.process = function() { PrometheusMetricFindQuery.prototype.process = function() {
@@ -51,7 +51,9 @@ function (_, moment) {
}); });
}); });
} else { } else {
url = '/api/v1/series?match[]=' + encodeURIComponent(metric); url = '/api/v1/series?match[]=' + encodeURIComponent(metric)
+ '&start=' + (this.range.from.valueOf() / 1000)
+ '&end=' + (this.range.to.valueOf() / 1000);
return this.datasource._request('GET', url) return this.datasource._request('GET', url)
.then(function(result) { .then(function(result) {
@@ -86,7 +88,7 @@ function (_, moment) {
}; };
PrometheusMetricFindQuery.prototype.queryResultQuery = function(query) { PrometheusMetricFindQuery.prototype.queryResultQuery = function(query) {
var url = '/api/v1/query?query=' + encodeURIComponent(query) + '&time=' + (moment().valueOf() / 1000); var url = '/api/v1/query?query=' + encodeURIComponent(query) + '&time=' + (this.range.to.valueOf() / 1000);
return this.datasource._request('GET', url) return this.datasource._request('GET', url)
.then(function(result) { .then(function(result) {
@@ -107,7 +109,9 @@ function (_, moment) {
}; };
PrometheusMetricFindQuery.prototype.metricNameAndLabelsQuery = function(query) { PrometheusMetricFindQuery.prototype.metricNameAndLabelsQuery = function(query) {
var url = '/api/v1/series?match[]=' + encodeURIComponent(query); var url = '/api/v1/series?match[]=' + encodeURIComponent(query)
+ '&start=' + (this.range.from.valueOf() / 1000)
+ '&end=' + (this.range.to.valueOf() / 1000);
var self = this; var self = this;
return this.datasource._request('GET', url) return this.datasource._request('GET', url)
@@ -61,7 +61,7 @@ class PrometheusQueryCtrl extends QueryCtrl {
var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000); var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000);
var endTime = range.to.utc().format('YYYY-MM-DD HH:mm'); var endTime = range.to.utc().format('YYYY-MM-DD HH:mm');
var expr = { var expr = {
expr: this.templateSrv.replace(this.target.expr, this.panelCtrl.panel.scopedVars), expr: this.templateSrv.replace(this.target.expr, this.panelCtrl.panel.scopedVars, this.datasource.interpolateQueryExpr),
range_input: rangeDiff + 's', range_input: rangeDiff + 's',
end_input: endTime, end_input: endTime,
step_input: '', step_input: '',
@@ -28,7 +28,7 @@ describe('PrometheusMetricFindQuery', function() {
data: ["value1", "value2", "value3"] data: ["value1", "value2", "value3"]
}; };
ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/resource/values').respond(response); ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/resource/values').respond(response);
var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(resource)'); var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(resource)', ctx.timeSrv);
pm.process().then(function(data) { results = data; }); pm.process().then(function(data) { results = data; });
ctx.$httpBackend.flush(); ctx.$httpBackend.flush();
ctx.$rootScope.$apply(); ctx.$rootScope.$apply();
@@ -43,13 +43,22 @@ describe('PrometheusMetricFindQuery', function() {
{__name__: "metric", resource: "value3"} {__name__: "metric", resource: "value3"}
] ]
}; };
ctx.$httpBackend.expect('GET', 'proxied/api/v1/series?match[]=metric').respond(response); ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response);
var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)'); var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv);
pm.process().then(function(data) { results = data; }); pm.process().then(function(data) { results = data; });
ctx.$httpBackend.flush(); ctx.$httpBackend.flush();
ctx.$rootScope.$apply(); ctx.$rootScope.$apply();
expect(results.length).to.be(3); expect(results.length).to.be(3);
}); });
it('label_values(metric, resource) should pass correct time', function() {
ctx.timeSrv.setTime({ from: moment.utc('2011-01-01'), to: moment.utc('2015-01-01') });
ctx.$httpBackend.expect('GET',
/proxied\/api\/v1\/series\?match\[\]=metric&start=1293840000&end=1420070400/).respond(response);
var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv);
pm.process().then(function(data) { results = data; });
ctx.$httpBackend.flush();
ctx.$rootScope.$apply();
});
it('label_values(metric{label1="foo", label2="bar", label3="baz"}, resource) should generate series query', function() { it('label_values(metric{label1="foo", label2="bar", label3="baz"}, resource) should generate series query', function() {
response = { response = {
status: "success", status: "success",
@@ -59,8 +68,8 @@ describe('PrometheusMetricFindQuery', function() {
{__name__: "metric", resource: "value3"} {__name__: "metric", resource: "value3"}
] ]
}; };
ctx.$httpBackend.expect('GET', 'proxied/api/v1/series?match[]=metric').respond(response); ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response);
var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)'); var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv);
pm.process().then(function(data) { results = data; }); pm.process().then(function(data) { results = data; });
ctx.$httpBackend.flush(); ctx.$httpBackend.flush();
ctx.$rootScope.$apply(); ctx.$rootScope.$apply();
@@ -72,7 +81,7 @@ describe('PrometheusMetricFindQuery', function() {
data: ["metric1","metric2","metric3","nomatch"] data: ["metric1","metric2","metric3","nomatch"]
}; };
ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/__name__/values').respond(response); ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/__name__/values').respond(response);
var pm = new PrometheusMetricFindQuery(ctx.ds, 'metrics(metric.*)'); var pm = new PrometheusMetricFindQuery(ctx.ds, 'metrics(metric.*)', ctx.timeSrv);
pm.process().then(function(data) { results = data; }); pm.process().then(function(data) { results = data; });
ctx.$httpBackend.flush(); ctx.$httpBackend.flush();
ctx.$rootScope.$apply(); ctx.$rootScope.$apply();
@@ -90,7 +99,7 @@ describe('PrometheusMetricFindQuery', function() {
} }
}; };
ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/query\?query=metric&time=.*/).respond(response); ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/query\?query=metric&time=.*/).respond(response);
var pm = new PrometheusMetricFindQuery(ctx.ds, 'query_result(metric)'); var pm = new PrometheusMetricFindQuery(ctx.ds, 'query_result(metric)', ctx.timeSrv);
pm.process().then(function(data) { results = data; }); pm.process().then(function(data) { results = data; });
ctx.$httpBackend.flush(); ctx.$httpBackend.flush();
ctx.$rootScope.$apply(); ctx.$rootScope.$apply();
@@ -12,7 +12,7 @@
</div> </div>
</div> </div>
<div class="editor-row" ng-if="ctrl.panel.mappingType==1"> <div class="editor-row" ng-if="ctrl.panel.mappingType==1">
<h5 class="page-heading">Set valuea mappings</h5> <h5 class="page-heading">Set value mappings</h5>
<div class="gf-form-group"> <div class="gf-form-group">
<div class="gf-form" ng-repeat="map in ctrl.panel.valueMaps"> <div class="gf-form" ng-repeat="map in ctrl.panel.valueMaps">
<span class="gf-form-label"> <span class="gf-form-label">
+4
View File
@@ -138,6 +138,10 @@ define([
this.replace = function(target) { this.replace = function(target) {
return target; return target;
}; };
this.setTime = function(time) {
this.time = time;
};
} }
function ContextSrvStub() { function ContextSrvStub() {
@@ -29,6 +29,7 @@ define([
beforeEach(function() { beforeEach(function() {
dash = _dashboardSrv.create({ dash = _dashboardSrv.create({
refresh: false,
rows: [ rows: [
{ {
panels: [{ test: "asd", legend: { } }] panels: [{ test: "asd", legend: { } }]