diff --git a/.drone/Tests.Dockerfile b/.drone/Tests.Dockerfile new file mode 100644 index 000000000..8cf04af1f --- /dev/null +++ b/.drone/Tests.Dockerfile @@ -0,0 +1,2 @@ +FROM golang:alpine as builder +RUN apk --no-cache add make gcc git cairo-dev musl-dev \ No newline at end of file diff --git a/.drone/cmd/coverage/main.go b/.drone/cmd/coverage/main.go new file mode 100644 index 000000000..f8d425251 --- /dev/null +++ b/.drone/cmd/coverage/main.go @@ -0,0 +1,98 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "os" + "sort" + "strconv" + "strings" +) + +type aggregation struct { + NumStmts int + NumCoveredStmts int +} + +func (a *aggregation) CoveragePct() string { + pct := (float64(a.NumCoveredStmts) / float64(a.NumStmts)) * 100 + return fmt.Sprintf("%.1f%%", pct) +} + +func main() { + covFilename := flag.String("f", "coverage.out", "Output of `go test -coverprofile=coverage.out ./...`") + flag.Parse() + + f, err := os.Open(*covFilename) + if err != nil { + panic(err) + } + + agg := make(map[string]*aggregation) + + s := bufio.NewScanner(f) + s.Split(bufio.ScanLines) + + s.Scan() // First line specifies the mode; it doesn't affect what we do so we can just skip it. + + for s.Scan() { + line := s.Text() + + cols := strings.Fields(line) + key := strings.Split(cols[0], ":")[0] + + numStmts, err := strconv.Atoi(cols[1]) + if err != nil { + panic(err) + } + numTimesCovered, err := strconv.Atoi(cols[2]) + if err != nil { + panic(err) + } + + if val, ok := agg[key]; ok { + val.NumStmts += numStmts + if numTimesCovered > 0 { + val.NumCoveredStmts += numStmts + } + } else { + numCoveredStmts := 0 + if numTimesCovered > 0 { + numCoveredStmts = numStmts + } + + agg[key] = &aggregation{ + NumStmts: numStmts, + NumCoveredStmts: numCoveredStmts, + } + } + } + + keys := make([]string, 0, len(agg)) + for k := range agg { + keys = append(keys, k) + } + sort.Strings(keys) + + fmt.Println("Go coverage report:") + fmt.Println("
") + fmt.Println("Click to expand.") + fmt.Println("") + fmt.Println("| File | % |") + fmt.Println("| ---- | - |") + + totalStmts := 0 + totalCoveredStmts := 0 + + for _, k := range keys { + a := agg[k] + fmt.Printf("| %s | %s |\n", k, a.CoveragePct()) + + totalStmts += a.NumStmts + totalCoveredStmts += a.NumCoveredStmts + } + + fmt.Printf("| total | %.1f%% |\n", (float64(totalCoveredStmts)/float64(totalStmts))*100) + fmt.Println("
") +} diff --git a/.drone/cmd/ghcomment/main.go b/.drone/cmd/ghcomment/main.go new file mode 100644 index 000000000..61e96e86b --- /dev/null +++ b/.drone/cmd/ghcomment/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "context" + "flag" + "io/ioutil" + "os" + "strconv" + "strings" + + "github.com/shurcooL/githubv4" + + "github.com/go-graphite/carbonapi/drone/pkg/github" +) + +const CommenterLogin = "grafanabot" + +func main() { + grafanabotPat := getRequiredEnv("GRAFANABOT_PAT") + + repoOwner := getRequiredEnv("DRONE_REPO_OWNER") + repoName := getRequiredEnv("DRONE_REPO_NAME") + + pullRequest, err := strconv.Atoi(getRequiredEnv("DRONE_PULL_REQUEST")) + if err != nil { + panic(err) + } + + commentTypeIdentifier := flag.String("id", "", "String that identifies the comment type being submitted") + commentBodyFilename := flag.String("bodyfile", "", "A file containing the comment body") + flag.Parse() + + if *commentTypeIdentifier == "" { + panic("Required argument: -i") + } + if *commentBodyFilename == "" { + panic("Required argument: -b") + } + + api := github.NewAPI(context.Background(), grafanabotPat) + + err = minimizeOutdatedComments(api, repoOwner, repoName, pullRequest, *commentTypeIdentifier) + if err != nil { + panic(err) + } + + commentBody, err := ioutil.ReadFile(*commentBodyFilename) + if err != nil { + panic(err) + } + + err = addComment(api, repoOwner, repoName, pullRequest, string(commentBody)) + if err != nil { + panic(err) + } +} + +func getRequiredEnv(k string) string { + v, p := os.LookupEnv(k) + if !p { + panic("Missing required env var: " + k) + } + + return v +} + +func minimizeOutdatedComments(api *github.API, repoOwner string, repoName string, pullRequestNo int, commentTypeIdentifier string) error { + prComments, err := api.ListPullRequestComments(repoOwner, repoName, pullRequestNo) + if err != nil { + return err + } + + for _, comment := range prComments { + if comment.Author.Login == CommenterLogin && strings.Contains(comment.Body, commentTypeIdentifier) && !comment.IsMinimized { + err := api.MinimizeComment(comment.ID, githubv4.ReportedContentClassifiersOutdated) + if err != nil { + return err + } + } + } + + return nil +} + +func addComment(api *github.API, repoOwner string, repoName string, pullRequestNo int, commentBody string) error { + pullRequestNodeID, err := api.GetPullRequestNodeID(repoOwner, repoName, pullRequestNo) + if err != nil { + return err + } + + _, err = api.AddComment(pullRequestNodeID, commentBody) + if err != nil { + return err + } + + return nil +} diff --git a/.drone/drone.jsonnet b/.drone/drone.jsonnet new file mode 100644 index 000000000..2acd6f39b --- /dev/null +++ b/.drone/drone.jsonnet @@ -0,0 +1,93 @@ +local drone = import 'lib/drone/drone.libsonnet'; +local images = import 'lib/drone/images.libsonnet'; +local triggers = import 'lib/drone/triggers.libsonnet'; +local vault = import 'lib/vault/vault.libsonnet'; + +local pipeline = drone.pipeline; +local step = drone.step; +local withInlineStep = drone.withInlineStep; +local withStep = drone.withStep; +local withSteps = drone.withSteps; + +local imagePullSecrets = { image_pull_secrets: ['dockerconfigjson'] }; + +local commentCoverageLintReport = { + step: step('coverage + lint', $.commands, image=$.image, environment=$.environment), + commands: [ + // Build drone utilities. + 'scripts/build-drone-utilities.sh', + // Generate the raw coverage report. + 'go test -coverprofile=coverage.out ./...', + // Process the raw coverage report. + '.drone/coverage > coverage_report.out', + // Generate the lint report. + 'scripts/generate-lint-report.sh', + // Combine the reports. + 'cat coverage_report.out > report.out', + 'echo "" >> report.out', + 'cat lint.out >> report.out', + // Submit the comment to GitHub. + '.drone/ghcomment -id "Go coverage report:" -bodyfile report.out', + ], + environment: { + GRAFANABOT_PAT: { from_secret: 'gh_token' }, + }, + image: images._images.goLint, +}; + +local buildAndPushImages = { + // step builds the pipeline step to build and push a docker image + step(app): step( + '%s: build and push' % app, + [], + image=buildAndPushImages.pluginName, + settings=buildAndPushImages.settings(app), + ), + + pluginName: 'plugins/gcr', + + // settings generates the CI Pipeline step settings + settings(app): { + repo: $._repo(app), + registry: $._registry, + dockerfile: './Dockerfile', + json_key: { from_secret: 'gcr_admin' }, + mirror: 'https://mirror.gcr.io', + build_args: ['cmd=' + app], + }, + + // image generates the image for the given app + image(app): $._registry + '/' + $._repo(app), + + _repo(app):: 'kubernetes-dev/' + app, + _registry:: 'us.gcr.io', +}; + +local runTests = { + step: step('run tests', $.commands, image=$.image), + commands: [ + 'make test' + ], + image: images._images.testRunner, + settings: { + + } +}; + +[ + pipeline('test') + + withStep(runTests.step) + + imagePullSecrets + + triggers.pr + + triggers.main, + + pipeline('coverageLintReport') + + withStep(commentCoverageLintReport.step) + + triggers.pr, +] ++ [ + vault.secret('dockerconfigjson', 'secret/data/common/gcr', '.dockerconfigjson'), + vault.secret('gh_token', 'infra/data/ci/github/grafanabot', 'pat'), + vault.secret('gcr_admin', 'infra/data/ci/gcr-admin', 'service-account'), + vault.secret('argo_token', 'infra/data/ci/argo-workflows/trigger-service-account', 'token'), +] \ No newline at end of file diff --git a/.drone/drone.yml b/.drone/drone.yml new file mode 100644 index 000000000..1aa27e8ae --- /dev/null +++ b/.drone/drone.yml @@ -0,0 +1,79 @@ +--- +depends_on: null +image_pull_secrets: +- dockerconfigjson +kind: pipeline +name: test +steps: +- commands: + - make test + depends_on: [] + entrypoint: null + environment: {} + image: us.gcr.io/kubernetes-dev/carbonapi/test-runner:latest + name: run tests + settings: {} +trigger: + branch: + - main + event: + include: + - pull_request + - push +type: docker +--- +depends_on: null +kind: pipeline +name: coverageLintReport +steps: +- commands: + - scripts/build-drone-utilities.sh + - go test -coverprofile=coverage.out ./... + - .drone/coverage > coverage_report.out + - scripts/generate-lint-report.sh + - cat coverage_report.out > report.out + - echo "" >> report.out + - cat lint.out >> report.out + - .drone/ghcomment -id "Go coverage report:" -bodyfile report.out + depends_on: [] + entrypoint: null + environment: + GRAFANABOT_PAT: + from_secret: gh_token + image: golangci/golangci-lint:v1.45 + name: coverage + lint + settings: {} +trigger: + event: + include: + - pull_request +type: docker +--- +get: + name: .dockerconfigjson + path: secret/data/common/gcr +kind: secret +name: dockerconfigjson +--- +get: + name: pat + path: infra/data/ci/github/grafanabot +kind: secret +name: gh_token +--- +get: + name: service-account + path: infra/data/ci/gcr-admin +kind: secret +name: gcr_admin +--- +get: + name: token + path: infra/data/ci/argo-workflows/trigger-service-account +kind: secret +name: argo_token +--- +kind: signature +hmac: 786972e7020e02e75b297bf8f7a67669cf11bd5bf02f61ac62ec63d0fca2a6c1 + +... diff --git a/.drone/go.mod b/.drone/go.mod new file mode 100644 index 000000000..8d8bf331e --- /dev/null +++ b/.drone/go.mod @@ -0,0 +1,16 @@ +module github.com/go-graphite/carbonapi/drone + +go 1.18 + +require ( + github.com/shurcooL/githubv4 v0.0.0-20220520033151-0b4e3294ff00 + golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0 +) + +require ( + github.com/golang/protobuf v1.5.2 // indirect + github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect + golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.0 // indirect +) diff --git a/.drone/go.sum b/.drone/go.sum new file mode 100644 index 000000000..1b386f507 --- /dev/null +++ b/.drone/go.sum @@ -0,0 +1,27 @@ +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/shurcooL/githubv4 v0.0.0-20220520033151-0b4e3294ff00 h1:fiFvD4lT0aWjuuAb64LlZ/67v87m+Kc9Qsu5cMFNK0w= +github.com/shurcooL/githubv4 v0.0.0-20220520033151-0b4e3294ff00/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= +github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 h1:B1PEwpArrNp4dkQrfxh/abbBAOZBVp0ds+fBEOUOqOc= +github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 h1:8NSylCMxLW4JvserAndSgFL7aPli6A68yf0bYFTcWCM= +golang.org/x/net v0.0.0-20220706163947-c90051bbdb60/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0 h1:VnGaRqoLmqZH/3TMLJwYCEWkR4j1nuIU1U9TvbqsDUw= +golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/.drone/lib/drone/drone.libsonnet b/.drone/lib/drone/drone.libsonnet new file mode 100644 index 000000000..9f3a68787 --- /dev/null +++ b/.drone/lib/drone/drone.libsonnet @@ -0,0 +1,46 @@ +local images = import 'images.libsonnet'; +{ + step(name, + commands, + image=images._images.go, + settings={}, + environment={}, + entrypoint=null, + depends_on=[], + dir=null):: { + name: name, + entrypoint: entrypoint, + commands: if (dir == null || dir == '') then commands else ['cd %s' % dir] + commands, + image: image, + settings: settings, + environment: environment, + depends_on: depends_on, + }, + + withStep(step):: { + steps+: [step], + }, + + withSteps(steps):: { + steps+: steps, + }, + + + withInlineStep(name, + commands, + image=images._images.go, + settings={}, + environment={}, + entrypoint=null, + depends_on=[], + dir=null):: + $.withStep($.step(name, commands, image, settings, environment, entrypoint, depends_on, dir)), + + pipeline(name, steps=[], depends_on=null):: { + kind: 'pipeline', + type: 'docker', + name: name, + steps: steps, + depends_on: depends_on, + }, +} \ No newline at end of file diff --git a/.drone/lib/drone/images.libsonnet b/.drone/lib/drone/images.libsonnet new file mode 100644 index 000000000..df032b117 --- /dev/null +++ b/.drone/lib/drone/images.libsonnet @@ -0,0 +1,9 @@ +{ + _images+:: { + argoCli: 'us.gcr.io/kubernetes-dev/drone/plugins/argo-cli', + go: 'golang:1.17', + goLint: 'golangci/golangci-lint:v1.45', + dind: 'docker:dind', + testRunner: 'us.gcr.io/kubernetes-dev/carbonapi/test-runner:latest', + }, +} \ No newline at end of file diff --git a/.drone/lib/drone/triggers.libsonnet b/.drone/lib/drone/triggers.libsonnet new file mode 100644 index 000000000..aa0146905 --- /dev/null +++ b/.drone/lib/drone/triggers.libsonnet @@ -0,0 +1,25 @@ +{ + main:: { + trigger+: { + branch+: ['main'], + event+: { + include+: ['push'], + }, + }, + }, + pr:: { + trigger+: { + event+: { + include+: ['pull_request'], + }, + }, + }, + // excluding paths disables runs that contain changes to ONLY these files + excludeModifiedPaths(paths):: { + trigger+: { + paths+: { + exclude+: paths, + }, + }, + }, +} \ No newline at end of file diff --git a/.drone/lib/vault/vault.libsonnet b/.drone/lib/vault/vault.libsonnet new file mode 100644 index 000000000..dfaf4f762 --- /dev/null +++ b/.drone/lib/vault/vault.libsonnet @@ -0,0 +1,10 @@ +{ + secret(name, vault_path, key):: { + kind: 'secret', + name: name, + get: { + path: vault_path, + name: key, + }, + }, +} \ No newline at end of file diff --git a/.drone/pkg/github/github.go b/.drone/pkg/github/github.go new file mode 100644 index 000000000..0b0ae99fa --- /dev/null +++ b/.drone/pkg/github/github.go @@ -0,0 +1,139 @@ +package github + +import ( + "context" + "github.com/shurcooL/githubv4" + "golang.org/x/oauth2" +) + +type API struct { + ctx *context.Context + githubClient *githubv4.Client +} + +func NewAPI(ctx context.Context, pat string) *API { + src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: pat}) + httpClient := oauth2.NewClient(ctx, src) + githubClient := githubv4.NewClient(httpClient) + + return &API{ + ctx: &ctx, + githubClient: githubClient, + } +} + +type PullRequestComment struct { + ID githubv4.ID + Body string + IsMinimized bool + Author struct { + Login string + } +} + +func (a *API) ListPullRequestComments(repoOwner string, repoName string, pullRequestNo int) ([]PullRequestComment, error) { + var q struct { + Repository struct { + PullRequest struct { + Comments struct { + Nodes []PullRequestComment + PageInfo struct { + EndCursor githubv4.String + HasNextPage bool + } + } `graphql:"comments(first: 100, after: $commentsCursor)"` + } `graphql:"pullRequest(number: $pr)"` + } `graphql:"repository(owner: $owner, name: $name)"` + } + + variables := map[string]interface{}{ + "owner": githubv4.String(repoOwner), + "name": githubv4.String(repoName), + "pr": githubv4.Int(pullRequestNo), + "commentsCursor": (*githubv4.String)(nil), + } + + var allComments []PullRequestComment + for { + err := a.githubClient.Query(*a.ctx, &q, variables) + if err != nil { + return nil, err + } + + allComments = append(allComments, q.Repository.PullRequest.Comments.Nodes...) + + if !q.Repository.PullRequest.Comments.PageInfo.HasNextPage { + break + } + variables["commentsCursor"] = githubv4.NewString(q.Repository.PullRequest.Comments.PageInfo.EndCursor) + } + + return allComments, nil +} + +func (a *API) MinimizeComment(commentNodeID githubv4.ID, classifier githubv4.ReportedContentClassifiers) error { + var m struct { + MinimizeComment struct { + MinimizedComment struct { + IsMinimized bool + } + } `graphql:"minimizeComment(input: $input)"` + } + input := githubv4.MinimizeCommentInput{ + SubjectID: commentNodeID, + Classifier: classifier, + } + + err := a.githubClient.Mutate(*a.ctx, &m, input, nil) + if err != nil { + return err + } + + return nil +} + +func (a *API) GetPullRequestNodeID(repoOwner string, repoName string, pullRequestNo int) (githubv4.ID, error) { + var q struct { + Repository struct { + PullRequest struct { + ID githubv4.ID + } `graphql:"pullRequest(number: $pr)"` + } `graphql:"repository(owner: $owner, name: $name)"` + } + + variables := map[string]interface{}{ + "owner": githubv4.String(repoOwner), + "name": githubv4.String(repoName), + "pr": githubv4.Int(pullRequestNo), + } + + err := a.githubClient.Query(*a.ctx, &q, variables) + if err != nil { + return "", err + } + + return q.Repository.PullRequest.ID, nil +} + +func (a *API) AddComment(pullRequestNodeID githubv4.ID, commentBody string) (githubv4.ID, error) { + var m struct { + AddComment struct { + CommentEdge struct { + Node struct { + ID githubv4.ID + } + } + } `graphql:"addComment(input: $input)"` + } + input := githubv4.AddCommentInput{ + SubjectID: pullRequestNodeID, + Body: githubv4.String(commentBody), + } + + err := a.githubClient.Mutate(*a.ctx, &m, input, nil) + if err != nil { + return "", err + } + + return m.AddComment.CommentEdge.Node.ID, nil +} diff --git a/.github/workflows/sync-upstream.yaml b/.github/workflows/sync-upstream.yaml new file mode 100644 index 000000000..24014814f --- /dev/null +++ b/.github/workflows/sync-upstream.yaml @@ -0,0 +1,44 @@ +name: Sync fork with upstream + +on: + schedule: + - cron: "12 7 * * 1" + # scheduled at 07:12 every Monday + workflow_dispatch: + +jobs: + sync_with_upstream: + runs-on: ubuntu-latest + name: Sync HEAD with upstream latest + + steps: + # Step 1: run a standard checkout action, provided by github + - name: Checkout HEAD + uses: actions/checkout@v2 + with: + ref: main + + # Step 2: run this sync action - specify the upstream repo, upstream branch to sync with, and target sync branch + - name: Pull upstream changes + id: sync + uses: aormsby/Fork-Sync-With-Upstream-action@v3.2 + with: + target_sync_branch: main + target_repo_token: ${{ secrets.GITHUB_TOKEN }} + upstream_sync_repo: go-graphite/carbonapi + upstream_sync_branch: main + + # Set test_mode true to run tests instead of the true action!! + test_mode: true + + # Step 3: Display a sample message based on the sync output var 'has_new_commits' + - name: New commits found + if: steps.sync.outputs.has_new_commits == 'true' + run: echo "New commits were found to sync." + + - name: No new commits + if: steps.sync.outputs.has_new_commits == 'false' + run: echo "There were no new commits." + + - name: Show value of 'has_new_commits' + run: echo ${{ steps.sync.outputs.has_new_commits }} diff --git a/expr/functions/timeFunction/function.go b/expr/functions/timeFunction/function.go index 5a5c1c596..a6c75ae1b 100644 --- a/expr/functions/timeFunction/function.go +++ b/expr/functions/timeFunction/function.go @@ -29,9 +29,15 @@ func New(configFile string) []interfaces.FunctionMetadata { } func (f *timeFunction) Do(ctx context.Context, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { - name, err := e.GetStringArg(0) - if err != nil { - return nil, err + var name string + var err error + if e.Arg(0).IsName() { // Fixes error if a series is passed in as the first argument. See https://github.com/grafana/carbonapi/issues/84 + name = "" + } else { + name, err = e.GetStringArg(0) + if err != nil { + return nil, err + } } stepInt, err := e.GetIntArgDefault(1, 60) diff --git a/scripts/build-drone-utilities.sh b/scripts/build-drone-utilities.sh new file mode 100755 index 000000000..6556e7866 --- /dev/null +++ b/scripts/build-drone-utilities.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -eufo pipefail + +pushd .drone + +go build -o coverage ./cmd/coverage +go build -o ghcomment ./cmd/ghcomment + +popd \ No newline at end of file diff --git a/scripts/generate-drone-yml.sh b/scripts/generate-drone-yml.sh new file mode 100755 index 000000000..d7ce1857f --- /dev/null +++ b/scripts/generate-drone-yml.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -eufo pipefail + +drone jsonnet --source .drone/drone.jsonnet --target .drone/drone.yml --stream --format +drone lint --trusted .drone/drone.yml +drone sign --save grafana/carbonapi .drone/drone.yml \ No newline at end of file