Merge pull request #4445 from hashicorp/llb-monthly-active-users
This adds support for tracking and reporting monthly active users for the purpose of billing. It adds a new API endpoint, `/v1/billing:monthly-active-users` and new cli command, `boundary billing monthly-active-users` that can be used to view the monthly active user counts. This adds a new accumulating fact table to the data warehouse for auth tokens. This fact table is used to calculate the number of active users within a month, where an active user is a user that has at least one issued auth token within the time range of the month.
This commit is contained in:
84 files changed
+4903
-245
No files matched your search
@@ -244,6 +244,8 @@ protobuild:
|
||||
@protoc-go-inject-tag -input=./internal/gen/controller/servers/servers.pb.go
|
||||
@protoc-go-inject-tag -input=./sdk/pbs/controller/api/resources/policies/policy.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/gen/controller/api/services/policy_service.pb.go
|
||||
@protoc-go-inject-tag -input=./sdk/pbs/controller/api/resources/billing/billing.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/gen/controller/api/services/billing_service.pb.go
|
||||
|
||||
|
||||
# these protos, services and openapi artifacts are purely for testing purposes
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Code generated by "make api"; DO NOT EDIT.
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package billing
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
)
|
||||
|
||||
type ActiveUsers struct {
|
||||
Count uint32 `json:"count"`
|
||||
StartTime time.Time `json:"start_time,omitempty"`
|
||||
EndTime time.Time `json:"end_time,omitempty"`
|
||||
}
|
||||
|
||||
// Client is a client for this collection
|
||||
type Client struct {
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
// Creates a new client for this collection. The submitted API client is cloned;
|
||||
// modifications to it after generating this client will not have effect. If you
|
||||
// need to make changes to the underlying API client, use ApiClient() to access
|
||||
// it.
|
||||
func NewClient(c *api.Client) *Client {
|
||||
return &Client{client: c.Clone()}
|
||||
}
|
||||
|
||||
// ApiClient returns the underlying API client
|
||||
func (c *Client) ApiClient() *api.Client {
|
||||
return c.client
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package billing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
)
|
||||
|
||||
type MonthlyActiveUsersResult struct {
|
||||
Items []*ActiveUsers
|
||||
response *api.Response
|
||||
}
|
||||
|
||||
func (r MonthlyActiveUsersResult) GetItems() any {
|
||||
return r.Items
|
||||
}
|
||||
|
||||
func (r MonthlyActiveUsersResult) GetResponse() *api.Response {
|
||||
return r.response
|
||||
}
|
||||
|
||||
func (c *Client) MonthlyActiveUsers(ctx context.Context, opt ...Option) (*MonthlyActiveUsersResult, error) {
|
||||
opts, apiOpts := getOpts(opt...)
|
||||
|
||||
if c.client == nil {
|
||||
return nil, fmt.Errorf("nil client")
|
||||
}
|
||||
|
||||
req, err := c.client.NewRequest(ctx, "GET", "billing:monthly-active-users", nil, apiOpts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating MonthlyActiveUsers request: %w", err)
|
||||
}
|
||||
|
||||
if len(opts.queryMap) > 0 {
|
||||
q := url.Values{}
|
||||
for k, v := range opts.queryMap {
|
||||
q.Add(k, v)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error performing client request during MonthlyActiveUsers call: %w", err)
|
||||
}
|
||||
|
||||
mau := new(MonthlyActiveUsersResult)
|
||||
mau.Items = []*ActiveUsers{}
|
||||
apiErr, err := resp.Decode(mau)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding MonthlyActiveUsers response: %w", err)
|
||||
}
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
mau.response = resp
|
||||
return mau, nil
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Code generated by "make api"; DO NOT EDIT.
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package billing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
)
|
||||
|
||||
// Option is a func that sets optional attributes for a call. This does not need
|
||||
// to be used directly, but instead option arguments are built from the
|
||||
// functions in this package. WithX options set a value to that given in the
|
||||
// argument; DefaultX options indicate that the value should be set to its
|
||||
// default. When an API call is made options are processed in the order they
|
||||
// appear in the function call, so for a given argument X, a succession of WithX
|
||||
// or DefaultX calls will result in the last call taking effect.
|
||||
type Option func(*options)
|
||||
|
||||
type options struct {
|
||||
postMap map[string]interface{}
|
||||
queryMap map[string]string
|
||||
withAutomaticVersioning bool
|
||||
withSkipCurlOutput bool
|
||||
withFilter string
|
||||
withListToken string
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
return options{
|
||||
postMap: make(map[string]interface{}),
|
||||
queryMap: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func getOpts(opt ...Option) (options, []api.Option) {
|
||||
opts := getDefaultOptions()
|
||||
for _, o := range opt {
|
||||
if o != nil {
|
||||
o(&opts)
|
||||
}
|
||||
}
|
||||
var apiOpts []api.Option
|
||||
if opts.withSkipCurlOutput {
|
||||
apiOpts = append(apiOpts, api.WithSkipCurlOutput(true))
|
||||
}
|
||||
if opts.withFilter != "" {
|
||||
opts.queryMap["filter"] = opts.withFilter
|
||||
}
|
||||
if opts.withListToken != "" {
|
||||
opts.queryMap["list_token"] = opts.withListToken
|
||||
}
|
||||
return opts, apiOpts
|
||||
}
|
||||
|
||||
// If set, and if the version is zero during an update, the API will perform a
|
||||
// fetch to get the current version of the resource and populate it during the
|
||||
// update call. This is convenient but opens up the possibility for subtle
|
||||
// order-of-modification issues, so use carefully.
|
||||
func WithAutomaticVersioning(enable bool) Option {
|
||||
return func(o *options) {
|
||||
o.withAutomaticVersioning = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithSkipCurlOutput tells the API to not use the current call for cURL output.
|
||||
// Useful for when we need to look up versions.
|
||||
func WithSkipCurlOutput(skip bool) Option {
|
||||
return func(o *options) {
|
||||
o.withSkipCurlOutput = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithListToken tells the API to use the provided list token
|
||||
// for listing operations on this resource.
|
||||
func WithListToken(listToken string) Option {
|
||||
return func(o *options) {
|
||||
o.withListToken = listToken
|
||||
}
|
||||
}
|
||||
|
||||
// WithFilter tells the API to filter the items returned using the provided
|
||||
// filter term. The filter should be in a format supported by
|
||||
// hashicorp/go-bexpr.
|
||||
func WithFilter(filter string) Option {
|
||||
return func(o *options) {
|
||||
o.withFilter = strings.TrimSpace(filter)
|
||||
}
|
||||
}
|
||||
|
||||
func WithEndTime(inEndTime string) Option {
|
||||
return func(o *options) {
|
||||
o.queryMap["end_time"] = fmt.Sprintf("%v", inEndTime)
|
||||
}
|
||||
}
|
||||
|
||||
func WithStartTime(inStartTime string) Option {
|
||||
return func(o *options) {
|
||||
o.queryMap["start_time"] = fmt.Sprintf("%v", inStartTime)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/accounts"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authmethods"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/billing"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentiallibraries"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentials"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentialstores"
|
||||
@@ -65,6 +66,7 @@ type fieldInfo struct {
|
||||
Query bool
|
||||
SkipDefault bool
|
||||
JsonTags []string // Appended to a field's `json` tag (comma separated)
|
||||
AllowEmpty bool
|
||||
}
|
||||
|
||||
type structInfo struct {
|
||||
@@ -138,6 +140,8 @@ type structInfo struct {
|
||||
// fieldFilter is a set of field names that will not result in generated API
|
||||
// fields
|
||||
fieldFilter []string
|
||||
|
||||
allowEmpty bool
|
||||
}
|
||||
|
||||
var inputStructs = []*structInfo{
|
||||
@@ -224,6 +228,39 @@ var inputStructs = []*structInfo{
|
||||
createResponseTypes: []string{CreateResponseType, ReadResponseType, UpdateResponseType, DeleteResponseType, ListResponseType},
|
||||
recursiveListing: true,
|
||||
},
|
||||
{
|
||||
inProto: &billing.ActiveUsers{},
|
||||
outFile: "billing/active_users.gen.go",
|
||||
templates: []*template.Template{
|
||||
clientTemplate,
|
||||
},
|
||||
fieldOverrides: []fieldInfo{
|
||||
{
|
||||
Name: "Count",
|
||||
ProtoName: "count",
|
||||
FieldType: "uint32",
|
||||
AllowEmpty: true,
|
||||
},
|
||||
},
|
||||
extraFields: []fieldInfo{
|
||||
{
|
||||
Name: "StartTime",
|
||||
ProtoName: "start_time",
|
||||
FieldType: "string",
|
||||
SkipDefault: true,
|
||||
Query: true,
|
||||
},
|
||||
{
|
||||
Name: "EndTime",
|
||||
ProtoName: "end_time",
|
||||
FieldType: "string",
|
||||
SkipDefault: true,
|
||||
Query: true,
|
||||
},
|
||||
},
|
||||
pluralResourceName: "billing",
|
||||
versionEnabled: true,
|
||||
},
|
||||
// User related resources
|
||||
{
|
||||
inProto: &users.Account{},
|
||||
|
||||
@@ -94,6 +94,9 @@ func fillTemplates() {
|
||||
if len(override.JsonTags) != 0 {
|
||||
field.JsonTags = override.JsonTags
|
||||
}
|
||||
if override.AllowEmpty {
|
||||
field.AllowEmpty = true
|
||||
}
|
||||
in.generatedStructure.fields[i] = field
|
||||
}
|
||||
}
|
||||
@@ -715,7 +718,7 @@ import (
|
||||
)
|
||||
|
||||
type {{ .Name }} struct { {{ range .Fields }}
|
||||
{{ .Name }} {{ .FieldType }} `, "`json:\"{{ .ProtoName }}{{ if ( ne ( len ( .JsonTags ) ) 0 ) }},{{ stringsjoin .JsonTags \",\" }}{{ end }},omitempty\"`", `{{ end }}
|
||||
{{ .Name }} {{ .FieldType }} `, "`json:\"{{ .ProtoName }}{{ if ( ne ( len ( .JsonTags ) ) 0 ) }},{{ stringsjoin .JsonTags \",\" }}{{ end }}{{ if ( not .AllowEmpty ) }},omitempty{{ end }}\"`", `{{ end }}
|
||||
{{ if ( not ( eq ( len ( .CreateResponseTypes ) ) 0 ) )}}
|
||||
response *api.Response
|
||||
{{ else if ( eq .Name "Error" ) }}
|
||||
|
||||
@@ -130,13 +130,17 @@ func TestAuthToken_DbCreate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rootWrapper := db.TestWrapper(t)
|
||||
iamRepo := iam.TestRepo(t, conn, rootWrapper)
|
||||
kms := kms.TestKms(t, conn, rootWrapper)
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, rootWrapper))
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
wrapper, err := kms.GetWrapper(context.Background(), org.GetPublicId(), 1)
|
||||
require.NoError(t, err)
|
||||
user := iam.TestUser(t, iamRepo, org.GetPublicId())
|
||||
am := password.TestAuthMethods(t, conn, org.GetPublicId(), 1)[0]
|
||||
acct := password.TestAccount(t, conn, am.GetPublicId(), "name1")
|
||||
createdAuthToken := TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
_, err = iamRepo.AddUserAccounts(ctx, user.GetPublicId(), user.GetVersion(), []string{acct.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
|
||||
testAuthTokenId := func() string {
|
||||
id, err := NewAuthTokenId(ctx)
|
||||
|
||||
@@ -890,7 +890,9 @@ func Test_CloseExpiredPendingTokens(t *testing.T) {
|
||||
repo, err := NewRepository(ctx, rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, rootWrapper))
|
||||
iamRepo := iam.TestRepo(t, conn, rootWrapper)
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
user := iam.TestUser(t, iamRepo, org.GetPublicId())
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -900,6 +902,11 @@ func Test_CloseExpiredPendingTokens(t *testing.T) {
|
||||
|
||||
accts := password.TestMultipleAccounts(t, conn, authMethodId, cnt)
|
||||
for i := 0; i < cnt; i++ {
|
||||
user, _, err = iamRepo.LookupUser(ctx, user.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _ = iamRepo.AddUserAccounts(ctx, user.GetPublicId(), user.GetVersion(), []string{accts[i].GetPublicId()})
|
||||
|
||||
at := allocAuthToken()
|
||||
id, err := NewAuthTokenId(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package billing
|
||||
|
||||
import "time"
|
||||
|
||||
// The ActiveUsersCount field is the number of unique users
|
||||
// counted between the start and end dates.
|
||||
// The start date is inclusive and the end date is exclusive.
|
||||
type ActiveUsers struct {
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
ActiveUsersCount uint32
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
// Package billing provides usage numbers that can be used for
|
||||
// billing purposes. The currently supported metric is monthly
|
||||
// active users. A user is considered active within a month
|
||||
// if they have at least one issued auth token within the time
|
||||
// range of the start and end of a given month.
|
||||
package billing
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package billing
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// getOpts - iterate the inbound Options and return a struct
|
||||
func getOpts(opt ...Option) options {
|
||||
opts := getDefaultOptions()
|
||||
for _, o := range opt {
|
||||
o(&opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// Option - how Options are passed as arguments.
|
||||
type Option func(*options)
|
||||
|
||||
// options = how options are represented
|
||||
type options struct {
|
||||
withStartTime *time.Time
|
||||
withEndTime *time.Time
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
return options{
|
||||
withStartTime: nil,
|
||||
withEndTime: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// WithStartTime allows setting the start time for the query.
|
||||
func WithStartTime(startTime *time.Time) Option {
|
||||
return func(o *options) {
|
||||
o.withStartTime = startTime
|
||||
}
|
||||
}
|
||||
|
||||
// WithEndTime allows setting the end time for the query.
|
||||
func WithEndTime(endTime *time.Time) Option {
|
||||
return func(o *options) {
|
||||
o.withEndTime = endTime
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package billing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Test_GetOpts provides unit tests for GetOpts and all the options
|
||||
func Test_GetOpts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
oneMonthAgo := time.Now().AddDate(0, -1, 0)
|
||||
|
||||
t.Run("WithStartTime", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
opts := getOpts(WithStartTime(&oneMonthAgo))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withStartTime = &oneMonthAgo
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
|
||||
t.Run("WithEndTime", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
opts := getOpts(WithEndTime(&oneMonthAgo))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withEndTime = &oneMonthAgo
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package billing
|
||||
|
||||
const (
|
||||
activeUsersLastTwoMonthsQuery = `
|
||||
select *
|
||||
from hcp_billing_monthly_active_users_last_2_months();
|
||||
`
|
||||
activeUsersWithStartTimeQuery = `
|
||||
select *
|
||||
from hcp_billing_monthly_active_users_all(@start_time);
|
||||
`
|
||||
activeUsersWithStartTimeAndEndTimeQuery = `
|
||||
select *
|
||||
from hcp_billing_monthly_active_users_all(@start_time, @end_time);
|
||||
`
|
||||
)
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package billing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
)
|
||||
|
||||
// A Repository retrieves the persistent type in the billing
|
||||
// package. It is not safe to use a repository concurrently.
|
||||
// It provides a method for requesting pre-aggregated user counts
|
||||
// per month. Depending on whether a start time and/or end time are given,
|
||||
// an ActiveUsers object will be returned:
|
||||
// - for every month from the provided start date until the present date,
|
||||
// with the present date being a cumulative count up to the present date.
|
||||
// - for every month from the provided start date until the provided end date.
|
||||
// - for the previous month and current month, with the current month being a
|
||||
// cumulative count up to the present date.
|
||||
type Repository struct {
|
||||
reader db.Reader
|
||||
}
|
||||
|
||||
// NewRepository creates a new Repository. The returned repository is not safe for concurrent go
|
||||
// routines to access it.
|
||||
func NewRepository(ctx context.Context, r db.Reader) (*Repository, error) {
|
||||
const op = "billing.NewRepository"
|
||||
switch {
|
||||
case r == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil db reader")
|
||||
}
|
||||
|
||||
return &Repository{
|
||||
reader: r,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MonthlyActiveUsers returns the active users for a range of months, from most recent to least.
|
||||
// If no start or end time is provided, it will return the active users for the last two months.
|
||||
// If a start time is provided, it will return the active users for that month until the current month.
|
||||
// If both a start and end time are provided, it will return the active users for that time range,
|
||||
// starting time inclusive and ending time exclusive.
|
||||
// The times provided must be the start of the month at midnight UTC.
|
||||
func (r *Repository) MonthlyActiveUsers(ctx context.Context, opt ...Option) ([]ActiveUsers, error) {
|
||||
const op = "billing.Repository.MonthlyActiveUsers"
|
||||
|
||||
opts := getOpts(opt...)
|
||||
|
||||
switch {
|
||||
case opts.withEndTime != nil && opts.withStartTime == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "end time set without start time")
|
||||
case opts.withEndTime != nil && !opts.withEndTime.After(*opts.withStartTime):
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "start time is not before end time")
|
||||
}
|
||||
query := activeUsersLastTwoMonthsQuery
|
||||
var args []any
|
||||
if opts.withStartTime != nil {
|
||||
if *opts.withStartTime != time.Date(opts.withStartTime.Year(), opts.withStartTime.Month(), 1, 0, 0, 0, 0, time.UTC) {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "start time must be the first day of the month at midnight UTC")
|
||||
}
|
||||
query = activeUsersWithStartTimeQuery
|
||||
args = append(args,
|
||||
sql.Named("start_time", timestamp.New(*opts.withStartTime)))
|
||||
}
|
||||
if opts.withEndTime != nil {
|
||||
if *opts.withEndTime != time.Date(opts.withEndTime.Year(), opts.withEndTime.Month(), 1, 0, 0, 0, 0, time.UTC) {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "end time must be the first day of the month at midnight UTC")
|
||||
}
|
||||
query = activeUsersWithStartTimeAndEndTimeQuery
|
||||
args = append(args,
|
||||
sql.Named("end_time", timestamp.New(*opts.withEndTime)))
|
||||
}
|
||||
|
||||
var activeUsers []ActiveUsers
|
||||
rows, err := r.reader.Query(ctx, query, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var startTime time.Time
|
||||
var endTime time.Time
|
||||
var count uint32
|
||||
if err := rows.Scan(&startTime, &endTime, &count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set start and end times to be in UTC
|
||||
auUTC := ActiveUsers{
|
||||
ActiveUsersCount: count,
|
||||
StartTime: startTime.UTC(),
|
||||
EndTime: endTime.UTC(),
|
||||
}
|
||||
activeUsers = append(activeUsers, auUTC)
|
||||
}
|
||||
|
||||
return activeUsers, nil
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package billing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
)
|
||||
|
||||
func TestRepository_New(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
|
||||
type args struct {
|
||||
r db.Reader
|
||||
w db.Writer
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *Repository
|
||||
wantIsErr errors.Code
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
args: args{
|
||||
r: rw,
|
||||
},
|
||||
want: &Repository{
|
||||
reader: rw,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil-reader",
|
||||
args: args{
|
||||
r: nil,
|
||||
},
|
||||
want: nil,
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "billing.NewRepository: nil db reader: parameter violation: error #100",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
got, err := NewRepository(context.Background(), tt.args.r)
|
||||
if tt.wantIsErr != 0 {
|
||||
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
|
||||
assert.Equal(tt.wantErrMsg, err.Error())
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.NotNil(got)
|
||||
assert.Equal(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_MonthlyActiveUsers(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
|
||||
TestGenerateActiveUsers(t, conn)
|
||||
|
||||
today := time.Now().UTC()
|
||||
threeMonthsAgo := time.Date(today.AddDate(0, -3, 0).Year(), today.AddDate(0, -3, 0).Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
oneMonthAgo := time.Date(today.AddDate(0, -1, 0).Year(), today.AddDate(0, -1, 0).Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
midMonth := time.Date(today.Year(), today.Month(), 15, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
t.Run("valid-no-options", func(t *testing.T) {
|
||||
repo := TestRepo(t, conn)
|
||||
activeUsers, err := repo.MonthlyActiveUsers(ctx)
|
||||
assert.NoError(t, err)
|
||||
require.Len(t, activeUsers, 2)
|
||||
// check counts for the last two months
|
||||
require.Equal(t, uint32(0), activeUsers[0].ActiveUsersCount)
|
||||
require.Equal(t, uint32(6), activeUsers[1].ActiveUsersCount)
|
||||
// assert start and end times are correct
|
||||
// the current month (contains the hour)
|
||||
assert.Equal(t, time.Date(today.Year(), today.Month(), 1, 0, 0, 0, 0, time.UTC), activeUsers[0].StartTime)
|
||||
assert.Equal(t, time.Date(today.Year(), today.Month(), today.Day(), today.Hour(), 0, 0, 0, time.UTC), activeUsers[0].EndTime)
|
||||
// the previous month
|
||||
assert.Equal(t, time.Date(today.Year(), today.Month()-1, 1, 0, 0, 0, 0, time.UTC), activeUsers[1].StartTime)
|
||||
assert.Equal(t, time.Date(today.Year(), today.Month(), 1, 0, 0, 0, 0, time.UTC), activeUsers[1].EndTime)
|
||||
})
|
||||
|
||||
t.Run("valid-with-start-time", func(t *testing.T) {
|
||||
repo := TestRepo(t, conn)
|
||||
activeUsers, err := repo.MonthlyActiveUsers(ctx, WithStartTime(&threeMonthsAgo))
|
||||
assert.NoError(t, err)
|
||||
require.Len(t, activeUsers, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
// check counts for the last four months
|
||||
if i == 0 {
|
||||
assert.Equal(t, uint32(0), activeUsers[i].ActiveUsersCount)
|
||||
// the current month (contains the hour)
|
||||
assert.Equal(t, time.Date(today.Year(), today.Month(), 1, 0, 0, 0, 0, time.UTC), activeUsers[i].StartTime)
|
||||
assert.Equal(t, time.Date(today.Year(), today.Month(), today.Day(), today.Hour(), 0, 0, 0, time.UTC), activeUsers[i].EndTime)
|
||||
} else {
|
||||
// create a sliding window of dates to assert start and end times are correct
|
||||
expectedStartTime := time.Date(today.AddDate(0, -i, 0).Year(), today.AddDate(0, -i, 0).Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
expectedEndTime := time.Date(today.AddDate(0, -i+1, 0).Year(), today.AddDate(0, -i+1, 0).Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
assert.Equal(t, uint32(6), activeUsers[i].ActiveUsersCount)
|
||||
assert.Equal(t, expectedStartTime, activeUsers[i].StartTime)
|
||||
assert.Equal(t, expectedEndTime, activeUsers[i].EndTime)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("valid-with-start-and-end-time", func(t *testing.T) {
|
||||
repo := TestRepo(t, conn)
|
||||
activeUsers, err := repo.MonthlyActiveUsers(ctx, WithStartTime(&threeMonthsAgo), WithEndTime(&oneMonthAgo))
|
||||
assert.NoError(t, err)
|
||||
require.Len(t, activeUsers, 2)
|
||||
expectedStartTime := time.Date(today.AddDate(0, -2, 0).Year(), today.AddDate(0, -2, 0).Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
expectedEndTime := time.Date(today.AddDate(0, -1, 0).Year(), today.AddDate(0, -1, 0).Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
require.Equal(t, uint32(6), activeUsers[0].ActiveUsersCount)
|
||||
assert.Equal(t, expectedStartTime, activeUsers[0].StartTime)
|
||||
assert.Equal(t, expectedEndTime, activeUsers[0].EndTime)
|
||||
|
||||
expectedStartTime = time.Date(today.AddDate(0, -3, 0).Year(), today.AddDate(0, -3, 0).Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
expectedEndTime = time.Date(today.AddDate(0, -2, 0).Year(), today.AddDate(0, -2, 0).Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
require.Equal(t, uint32(6), activeUsers[1].ActiveUsersCount)
|
||||
assert.Equal(t, expectedStartTime, activeUsers[1].StartTime)
|
||||
assert.Equal(t, expectedEndTime, activeUsers[1].EndTime)
|
||||
})
|
||||
|
||||
t.Run("invalid-end-time-without-start-time", func(t *testing.T) {
|
||||
repo := TestRepo(t, conn)
|
||||
_, err := repo.MonthlyActiveUsers(ctx, WithEndTime(&oneMonthAgo))
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "billing.Repository.MonthlyActiveUsers: end time set without start time: parameter violation: error #100", err.Error())
|
||||
})
|
||||
|
||||
t.Run("invalid-end-time-before-start-time", func(t *testing.T) {
|
||||
repo := TestRepo(t, conn)
|
||||
_, err := repo.MonthlyActiveUsers(ctx, WithStartTime(&oneMonthAgo), WithEndTime(&threeMonthsAgo))
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "billing.Repository.MonthlyActiveUsers: start time is not before end time: parameter violation: error #100", err.Error())
|
||||
})
|
||||
|
||||
t.Run("invalid-start-time-equals-end-time", func(t *testing.T) {
|
||||
repo := TestRepo(t, conn)
|
||||
_, err := repo.MonthlyActiveUsers(ctx, WithStartTime(&oneMonthAgo), WithEndTime(&oneMonthAgo))
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "billing.Repository.MonthlyActiveUsers: start time is not before end time: parameter violation: error #100", err.Error())
|
||||
})
|
||||
|
||||
t.Run("invalid-start-time-not-first-day-of-month", func(t *testing.T) {
|
||||
repo := TestRepo(t, conn)
|
||||
_, err := repo.MonthlyActiveUsers(ctx, WithStartTime(&midMonth))
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "billing.Repository.MonthlyActiveUsers: start time must be the first day of the month at midnight UTC: parameter violation: error #100", err.Error())
|
||||
})
|
||||
|
||||
t.Run("invalid-end-time-not-first-day-of-month", func(t *testing.T) {
|
||||
repo := TestRepo(t, conn)
|
||||
_, err := repo.MonthlyActiveUsers(ctx, WithStartTime(&oneMonthAgo), WithEndTime(&midMonth))
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "billing.Repository.MonthlyActiveUsers: end time must be the first day of the month at midnight UTC: parameter violation: error #100", err.Error())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package billing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const insertQuery = `
|
||||
with
|
||||
month_range (date_key, time_key, month) as (
|
||||
select wh_date_key(s), wh_time_key(s), s
|
||||
from generate_series(date_trunc('month', now()) - interval '1 year',
|
||||
date_trunc('month', now()) - interval '1 month',
|
||||
interval '1 month') as s
|
||||
),
|
||||
users (user_id, u) as (
|
||||
select 'u_____user'||u, u
|
||||
from generate_series(1, 6, 1) as u
|
||||
),
|
||||
user_key (key, user_id) as (
|
||||
insert into wh_user_dimension (
|
||||
user_id, user_name, user_description,
|
||||
auth_account_id, auth_account_type, auth_account_name, auth_account_description,
|
||||
auth_method_id, auth_method_type, auth_method_name, auth_method_description,
|
||||
user_organization_id, user_organization_name, user_organization_description,
|
||||
current_row_indicator,
|
||||
row_effective_time, row_expiration_time,
|
||||
auth_method_external_id, auth_account_external_id, auth_account_full_name, auth_account_email)
|
||||
select users.user_id, 'None', 'None',
|
||||
'a______acc1', 'None', 'None', 'None',
|
||||
'am______am1', 'None', 'None', 'None',
|
||||
'o______org1', 'None', 'None',
|
||||
'current',
|
||||
now(), 'infinity'::timestamptz,
|
||||
'None', 'None', 'None', 'None'
|
||||
from users
|
||||
returning key, user_id
|
||||
),
|
||||
tokens (date_key, time_key, user_id, token_id) as (
|
||||
select wh_date_key(s), wh_time_key(s), users.user_id, 't_____u'||users.u||'tok'||s as token_id
|
||||
from users,
|
||||
generate_series(date_trunc('month', now()) - interval '1 year',
|
||||
date_trunc('month', now()) - interval '1 month',
|
||||
interval '1 month') as s
|
||||
),
|
||||
tokens_user_keys (date_key, time_key, user_id, token_id, user_key) as (
|
||||
select tokens.date_key, tokens.time_key, tokens.user_id, tokens.token_id, user_key.key
|
||||
from tokens
|
||||
join user_key
|
||||
on user_key.user_id = tokens.user_id
|
||||
),
|
||||
auth_tokens (user_key, user_id, token_id, valid_range) as (
|
||||
select tokens_user_keys.user_key, tokens_user_keys.user_id, tokens_user_keys.token_id, tstzrange(month_range.month, month_range.month + interval '5 minutes', '[)')
|
||||
from tokens_user_keys
|
||||
join month_range
|
||||
on month_range.date_key = tokens_user_keys.date_key
|
||||
and month_range.time_key = tokens_user_keys.time_key
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select auth_tokens.token_id, auth_tokens.user_key,
|
||||
wh_date_key(lower(auth_tokens.valid_range)), wh_time_key(lower(auth_tokens.valid_range)), lower(auth_tokens.valid_range),
|
||||
coalesce(wh_date_key(upper(auth_tokens.valid_range)), -1), coalesce(wh_time_key(upper(auth_tokens.valid_range)), -1), upper(auth_tokens.valid_range),
|
||||
wh_date_key(upper(auth_tokens.valid_range)), wh_time_key(upper(auth_tokens.valid_range)), upper(auth_tokens.valid_range),
|
||||
auth_tokens.valid_range,
|
||||
auth_tokens.valid_range,
|
||||
1
|
||||
from auth_tokens;
|
||||
`
|
||||
|
||||
// TestRepo creates a repo that can be used for various testing purposes.
|
||||
func TestRepo(t testing.TB, conn *db.DB) *Repository {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
require := require.New(t)
|
||||
rw := db.New(conn)
|
||||
|
||||
repo, err := NewRepository(ctx, rw)
|
||||
require.NoError(err)
|
||||
return repo
|
||||
}
|
||||
|
||||
// TestGenerateActiveUsers is a test helper that populates the data warehouse
|
||||
// with active users for the last twelve months.
|
||||
func TestGenerateActiveUsers(t testing.TB, conn *db.DB) {
|
||||
t.Helper()
|
||||
db, err := conn.SqlDB(context.Background())
|
||||
require.NoError(t, err)
|
||||
_, err = db.Exec(insertQuery)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -18,11 +18,13 @@ var (
|
||||
)
|
||||
|
||||
type censusJob struct {
|
||||
r db.Reader
|
||||
w db.Writer
|
||||
lurEnabled bool
|
||||
agent any
|
||||
eventCtx context.Context
|
||||
r db.Reader
|
||||
w db.Writer
|
||||
lurEnabled bool
|
||||
emitEvents bool
|
||||
sessionsAgent any
|
||||
activeUsersAgent any
|
||||
eventCtx context.Context
|
||||
}
|
||||
|
||||
func newCensusJob(ctx context.Context, lurEnabled bool, r db.Reader, w db.Writer) (*censusJob, error) {
|
||||
@@ -35,11 +37,13 @@ func newCensusJob(ctx context.Context, lurEnabled bool, r db.Reader, w db.Writer
|
||||
}
|
||||
|
||||
return &censusJob{
|
||||
r: r,
|
||||
w: w,
|
||||
lurEnabled: lurEnabled,
|
||||
agent: nil,
|
||||
eventCtx: ctx,
|
||||
r: r,
|
||||
w: w,
|
||||
lurEnabled: lurEnabled,
|
||||
emitEvents: false,
|
||||
sessionsAgent: nil,
|
||||
activeUsersAgent: nil,
|
||||
eventCtx: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/cmd/commands/authenticate"
|
||||
"github.com/hashicorp/boundary/internal/cmd/commands/authmethodscmd"
|
||||
"github.com/hashicorp/boundary/internal/cmd/commands/authtokenscmd"
|
||||
"github.com/hashicorp/boundary/internal/cmd/commands/billingcmd"
|
||||
"github.com/hashicorp/boundary/internal/cmd/commands/config"
|
||||
"github.com/hashicorp/boundary/internal/cmd/commands/connect"
|
||||
"github.com/hashicorp/boundary/internal/cmd/commands/credentiallibrariescmd"
|
||||
@@ -243,6 +244,17 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
Func: "list",
|
||||
}),
|
||||
|
||||
"billing": func() (cli.Command, error) {
|
||||
return &billingcmd.Command{
|
||||
Command: base.NewCommand(ui, opts...),
|
||||
}, nil
|
||||
},
|
||||
"billing monthly-active-users": clientCacheWrapper(
|
||||
&billingcmd.Command{
|
||||
Command: base.NewCommand(ui),
|
||||
Func: "monthly-active-users",
|
||||
}),
|
||||
|
||||
"config": func() (cli.Command, error) {
|
||||
return &config.Command{
|
||||
Command: base.NewCommand(ui, opts...),
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
// Code generated by "make cli"; DO NOT EDIT.
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package billingcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/billing"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/hashicorp/boundary/internal/cmd/common"
|
||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
func initFlags() {
|
||||
flagsOnce.Do(func() {
|
||||
extraFlags := extraActionsFlagsMapFunc()
|
||||
for k, v := range extraFlags {
|
||||
flagsMap[k] = append(flagsMap[k], v...)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
_ cli.Command = (*Command)(nil)
|
||||
_ cli.CommandAutocomplete = (*Command)(nil)
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
*base.Command
|
||||
|
||||
Func string
|
||||
|
||||
plural string
|
||||
|
||||
extraCmdVars
|
||||
}
|
||||
|
||||
func (c *Command) AutocompleteArgs() complete.Predictor {
|
||||
initFlags()
|
||||
return complete.PredictAnything
|
||||
}
|
||||
|
||||
func (c *Command) AutocompleteFlags() complete.Flags {
|
||||
initFlags()
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *Command) Synopsis() string {
|
||||
if extra := extraSynopsisFunc(c); extra != "" {
|
||||
return extra
|
||||
}
|
||||
|
||||
synopsisStr := "billing"
|
||||
|
||||
return common.SynopsisFunc(c.Func, synopsisStr)
|
||||
}
|
||||
|
||||
func (c *Command) Help() string {
|
||||
initFlags()
|
||||
|
||||
var helpStr string
|
||||
helpMap := common.HelpMap("billing")
|
||||
|
||||
switch c.Func {
|
||||
|
||||
default:
|
||||
|
||||
helpStr = c.extraHelpFunc(helpMap)
|
||||
|
||||
}
|
||||
|
||||
// Keep linter from complaining if we don't actually generate code using it
|
||||
_ = helpMap
|
||||
return helpStr
|
||||
}
|
||||
|
||||
var flagsMap = map[string][]string{}
|
||||
|
||||
func (c *Command) Flags() *base.FlagSets {
|
||||
if len(flagsMap[c.Func]) == 0 {
|
||||
return c.FlagSet(base.FlagSetNone)
|
||||
}
|
||||
|
||||
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
|
||||
f := set.NewFlagSet("Command Options")
|
||||
common.PopulateCommonFlags(c.Command, f, "billing", flagsMap, c.Func)
|
||||
|
||||
extraFlagsFunc(c, set, f)
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *Command) Run(args []string) int {
|
||||
initFlags()
|
||||
|
||||
switch c.Func {
|
||||
case "":
|
||||
return cli.RunResultHelp
|
||||
|
||||
case "create":
|
||||
return cli.RunResultHelp
|
||||
|
||||
case "update":
|
||||
return cli.RunResultHelp
|
||||
|
||||
}
|
||||
|
||||
c.plural = "billing"
|
||||
switch c.Func {
|
||||
case "list":
|
||||
c.plural = "billing"
|
||||
}
|
||||
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.PrintCliError(err)
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
var opts []billing.Option
|
||||
|
||||
if strutil.StrListContains(flagsMap[c.Func], "-id") {
|
||||
switch c.Func {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if c.WrapperCleanupFunc != nil {
|
||||
defer func() {
|
||||
if err := c.WrapperCleanupFunc(); err != nil {
|
||||
c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
c.PrintCliError(fmt.Errorf("Error creating API client: %w", err))
|
||||
return base.CommandCliError
|
||||
}
|
||||
billingClient := billing.NewClient(client)
|
||||
|
||||
if c.FlagFilter != "" {
|
||||
opts = append(opts, billing.WithFilter(c.FlagFilter))
|
||||
}
|
||||
|
||||
var version uint32
|
||||
|
||||
if ok := extraFlagsHandlingFunc(c, f, &opts); !ok {
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
var resp *api.Response
|
||||
|
||||
resp, err = executeExtraActions(c, resp, err, billingClient, version, opts)
|
||||
if exitCode := c.checkFuncError(err); exitCode > 0 {
|
||||
return exitCode
|
||||
}
|
||||
|
||||
output, err := printCustomActionOutput(c)
|
||||
if err != nil {
|
||||
c.PrintCliError(err)
|
||||
return base.CommandUserError
|
||||
}
|
||||
if output {
|
||||
return base.CommandSuccess
|
||||
}
|
||||
|
||||
return base.CommandSuccess
|
||||
}
|
||||
|
||||
func (c *Command) checkFuncError(err error) int {
|
||||
if err == nil {
|
||||
return 0
|
||||
}
|
||||
if apiErr := api.AsServerError(err); apiErr != nil {
|
||||
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
|
||||
return base.CommandApiError
|
||||
}
|
||||
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
var (
|
||||
flagsOnce = new(sync.Once)
|
||||
|
||||
extraActionsFlagsMapFunc = func() map[string][]string { return nil }
|
||||
extraSynopsisFunc = func(*Command) string { return "" }
|
||||
extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {}
|
||||
extraFlagsHandlingFunc = func(*Command, *base.FlagSets, *[]billing.Option) bool { return true }
|
||||
executeExtraActions = func(_ *Command, inResp *api.Response, inErr error, _ *billing.Client, _ uint32, _ []billing.Option) (*api.Response, error) {
|
||||
return inResp, inErr
|
||||
}
|
||||
printCustomActionOutput = func(*Command) (bool, error) { return false, nil }
|
||||
)
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package billingcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/billing"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/mitchellh/go-wordwrap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
extraActionsFlagsMapFunc = extraActionsFlagsMapFuncImpl
|
||||
extraSynopsisFunc = extraSynopsisFuncImpl
|
||||
extraFlagsFunc = extraFlagsFuncImpl
|
||||
extraFlagsHandlingFunc = extraFlagsHandlingFuncImpl
|
||||
executeExtraActions = executeExtraActionsImpl
|
||||
printCustomActionOutput = printCustomActionOutputImpl
|
||||
}
|
||||
|
||||
type extraCmdVars struct {
|
||||
flagStartTime string
|
||||
flagEndTime string
|
||||
monthlyActiveUsers *billing.MonthlyActiveUsersResult
|
||||
}
|
||||
|
||||
func extraActionsFlagsMapFuncImpl() map[string][]string {
|
||||
return map[string][]string{
|
||||
"monthly-active-users": {"start-time", "end-time"},
|
||||
}
|
||||
}
|
||||
|
||||
func extraSynopsisFuncImpl(c *Command) string {
|
||||
switch c.Func {
|
||||
case "monthly-active-users":
|
||||
var in string
|
||||
switch {
|
||||
case strings.HasPrefix(c.Func, "start-time"):
|
||||
in = "Get monthly active users, starting from this time (YYYY-MM format)."
|
||||
case strings.HasPrefix(c.Func, "end-time"):
|
||||
in = "Get monthly active users, ending at this time (YYYY-MM format)."
|
||||
}
|
||||
return wordwrap.WrapString(in, base.TermWidth)
|
||||
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) {
|
||||
flagsMap[c.Func] = append(flagsMap[c.Func], "start-time", "end-time")
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: "start-time",
|
||||
Target: &c.flagStartTime,
|
||||
Usage: "Get monthly active users, starting from this time (YYYY-MM format).",
|
||||
})
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: "end-time",
|
||||
Target: &c.flagEndTime,
|
||||
Usage: "Get monthly active users, ending at this time (YYYY-MM format).",
|
||||
})
|
||||
}
|
||||
|
||||
func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]billing.Option) bool {
|
||||
switch c.Func {
|
||||
case "monthly-active-users":
|
||||
if len(c.flagStartTime) != 0 {
|
||||
*opts = append(*opts, billing.WithStartTime(c.flagStartTime))
|
||||
}
|
||||
if len(c.flagEndTime) != 0 {
|
||||
*opts = append(*opts, billing.WithEndTime(c.flagEndTime))
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func executeExtraActionsImpl(c *Command, origResp *api.Response, origError error, billingClient *billing.Client, _ uint32, opts []billing.Option) (*api.Response, error) {
|
||||
switch c.Func {
|
||||
case "monthly-active-users":
|
||||
var err error
|
||||
c.monthlyActiveUsers, err = billingClient.MonthlyActiveUsers(c.Context, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return origResp, origError
|
||||
}
|
||||
|
||||
func printCustomActionOutputImpl(c *Command) (bool, error) {
|
||||
switch c.Func {
|
||||
case "monthly-active-users":
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
items := c.monthlyActiveUsers.GetItems().([]*billing.ActiveUsers)
|
||||
var ret []string
|
||||
|
||||
ret = append(ret, "Billing information:")
|
||||
ret = append(ret, "")
|
||||
for i := range items {
|
||||
ret = append(ret,
|
||||
fmt.Sprintf(" Count: %d", items[i].Count),
|
||||
fmt.Sprintf(" Start Time: %s", items[i].StartTime),
|
||||
fmt.Sprintf(" End Time: %s", items[i].EndTime),
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
c.UI.Output(base.WrapForHelpText(ret))
|
||||
return true, nil
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(c.monthlyActiveUsers.GetResponse()); !ok {
|
||||
return false, fmt.Errorf("error formatting as JSON")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *Command) extraHelpFunc(helpMap map[string]func() string) string {
|
||||
var helpStr string
|
||||
switch c.Func {
|
||||
case "":
|
||||
helpStr = base.WrapForHelpText([]string{
|
||||
"Usage: boundary billing [sub command] [options] [args]",
|
||||
"",
|
||||
" This command allows for collecting Boundary billing reports. Example:",
|
||||
"",
|
||||
" Monthly active users:",
|
||||
"",
|
||||
` $ boundary billing monthly-active-users`,
|
||||
"",
|
||||
" Please see the billing subcommand help for detailed usage information.",
|
||||
})
|
||||
case "monthly-active-users":
|
||||
helpStr = base.WrapForHelpText([]string{
|
||||
"Usage: boundary billing monthly-active-users [options]",
|
||||
"",
|
||||
" This command allows for collecting active Boundary user reports, by month. Example:",
|
||||
"",
|
||||
" Monthly active users between September 2023 and February 2024:",
|
||||
"",
|
||||
` $ boundary billing monthly-active-users -start-time="2023-09" -end-time="2024-02"`,
|
||||
"",
|
||||
" Please see the billing subcommand help for detailed usage information.",
|
||||
})
|
||||
}
|
||||
return helpStr + c.Flags().Help()
|
||||
}
|
||||
@@ -34,6 +34,7 @@ func HelpMap(resType string) map[string]func() string {
|
||||
resource.AuthToken.String(): "at",
|
||||
resource.AuthMethod.String(): "am",
|
||||
resource.Account.String(): "a",
|
||||
resource.Billing.String(): "b",
|
||||
resource.Role.String(): "r",
|
||||
resource.Group.String(): "g",
|
||||
resource.User.String(): "u",
|
||||
|
||||
@@ -17,6 +17,9 @@ type cmdInfo struct {
|
||||
// Standard actions (with standard parameters) used by this resource
|
||||
StdActions []string
|
||||
|
||||
// HasCustomList indicates if there is a custom list action
|
||||
HasCustomList bool
|
||||
|
||||
// HasExtraCommandVars controls whether to generate an embedded struct with
|
||||
// extra command variables
|
||||
HasExtraCommandVars bool
|
||||
@@ -211,6 +214,15 @@ var inputStructs = map[string][]*cmdInfo{
|
||||
Container: "Scope",
|
||||
},
|
||||
},
|
||||
"billing": {
|
||||
{
|
||||
ResourceType: resource.Billing.String(),
|
||||
Pkg: "billing",
|
||||
HasCustomList: true,
|
||||
HasExtraCommandVars: true,
|
||||
HasExtraHelpFunc: true,
|
||||
},
|
||||
},
|
||||
"credentialstores": {
|
||||
{
|
||||
ResourceType: resource.CredentialStore.String(),
|
||||
|
||||
@@ -478,16 +478,19 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
|
||||
}
|
||||
|
||||
var resp *api.Response
|
||||
{{ if $input.StdActions -}}
|
||||
var item *{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}
|
||||
{{ if hasAction .StdActions "list" }}
|
||||
var items []*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ range $i, $action := $input.StdActions }}
|
||||
{{ if ( not ( hasAction $input.SkipClientCallActions $action) ) }}
|
||||
var {{ $action }}Result *{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}{{ camelCase $action }}Result
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if $input.StdActions }}
|
||||
switch c.Func {
|
||||
{{ range $i, $action := $input.StdActions }}
|
||||
{{ if eq $action "create" }}
|
||||
@@ -538,8 +541,9 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
resp, item, {{ if hasAction .StdActions "list" }}items, {{ end }}err = executeExtra{{ camelCase .SubActionPrefix }}Actions(c, resp, item, {{ if hasAction .StdActions "list" }}items, {{ end }}err, {{ .Pkg }}Client, version, opts)
|
||||
resp, {{ if $input.StdActions }}item, {{ if hasAction .StdActions "list" }}items, {{ end }}{{ end }}err = executeExtra{{ camelCase .SubActionPrefix }}Actions(c, resp, {{ if $input.StdActions }}item, {{ if hasAction .StdActions "list" }}items, {{ end }}{{ end }}err, {{ .Pkg }}Client, version, opts)
|
||||
if exitCode := c.checkFuncError(err); exitCode > 0 {
|
||||
return exitCode
|
||||
}
|
||||
@@ -553,6 +557,7 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
|
||||
return base.CommandSuccess
|
||||
}
|
||||
|
||||
{{ if $input.StdActions }}
|
||||
switch c.Func {
|
||||
{{ range $i, $action := .StdActions }}
|
||||
{{ if eq $action "delete" }}
|
||||
@@ -595,6 +600,7 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
|
||||
return base.CommandCliError
|
||||
}
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
return base.CommandSuccess
|
||||
}
|
||||
@@ -619,8 +625,8 @@ var (
|
||||
extra{{ camelCase .SubActionPrefix }}SynopsisFunc = func(*{{ camelCase .SubActionPrefix }}Command) string { return "" }
|
||||
extra{{ camelCase .SubActionPrefix }}FlagsFunc = func(*{{ camelCase .SubActionPrefix }}Command, *base.FlagSets, *base.FlagSet) {}
|
||||
extra{{ camelCase .SubActionPrefix }}FlagsHandlingFunc = func(*{{ camelCase .SubActionPrefix }}Command, *base.FlagSets, *[]{{ .Pkg }}.Option) bool { return true }
|
||||
executeExtra{{ camelCase .SubActionPrefix }}Actions = func(_ *{{ camelCase .SubActionPrefix }}Command, inResp *api.Response, inItem *{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ if hasAction .StdActions "list" }}inItems []*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ end }}inErr error, _ *{{ .Pkg }}.Client, _ uint32, _ []{{ .Pkg }}.Option) (*api.Response, *{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ if hasAction .StdActions "list" }}[]*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ end }}error) {
|
||||
return inResp, inItem, {{ if hasAction .StdActions "list" }}inItems, {{ end }}inErr
|
||||
executeExtra{{ camelCase .SubActionPrefix }}Actions = func(_ *{{ camelCase .SubActionPrefix }}Command, inResp *api.Response, {{ if $input.StdActions }}inItem *{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ if hasAction .StdActions "list" }}inItems []*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ end }}{{ end }}inErr error, _ *{{ .Pkg }}.Client, _ uint32, _ []{{ .Pkg }}.Option) (*api.Response, {{ if $input.StdActions }}*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ if hasAction .StdActions "list" }}[]*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}, {{ end }}{{ end }}error) {
|
||||
return inResp, {{ if $input.StdActions }}inItem, {{ if hasAction .StdActions "list" }}inItems, {{ end }}{{ end }}inErr
|
||||
}
|
||||
printCustom{{ camelCase .SubActionPrefix }}ActionOutput = func(*{{ camelCase .SubActionPrefix }}Command) (bool, error) { return false, nil }
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/auth/ldap"
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc"
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
"github.com/hashicorp/boundary/internal/billing"
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
credstatic "github.com/hashicorp/boundary/internal/credential/static"
|
||||
"github.com/hashicorp/boundary/internal/credential/vault"
|
||||
@@ -39,6 +40,7 @@ type (
|
||||
ConnectionRepoFactory func() (*session.ConnectionRepository, error)
|
||||
WorkerAuthRepoStorageFactory func() (*server.WorkerAuthRepositoryStorage, error)
|
||||
PluginStorageBucketRepoFactory func() (*pluginstorage.Repository, error)
|
||||
BillingRepoFactory func() (*billing.Repository, error)
|
||||
)
|
||||
|
||||
// Downstreamers provides at least a minimum interface that must be met by a
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc"
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
"github.com/hashicorp/boundary/internal/billing"
|
||||
"github.com/hashicorp/boundary/internal/census"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/hashicorp/boundary/internal/cmd/config"
|
||||
@@ -148,6 +149,7 @@ type Controller struct {
|
||||
PluginRepoFn common.PluginRepoFactory
|
||||
TargetRepoFn target.RepositoryFactory
|
||||
WorkerAuthRepoStorageFn common.WorkerAuthRepoStorageFactory
|
||||
BillingRepoFn common.BillingRepoFactory
|
||||
|
||||
scheduler *scheduler.Scheduler
|
||||
|
||||
@@ -448,6 +450,9 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
|
||||
c.WorkerAuthRepoStorageFn = func() (*server.WorkerAuthRepositoryStorage, error) {
|
||||
return server.NewRepositoryStorage(ctx, dbase, dbase, c.kms)
|
||||
}
|
||||
c.BillingRepoFn = func() (*billing.Repository, error) {
|
||||
return billing.NewRepository(ctx, dbase)
|
||||
}
|
||||
|
||||
// Check that credentials are available at startup, to avoid some harmless
|
||||
// but nasty-looking errors
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/accounts"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/authmethods"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/authtokens"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/billing"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentiallibraries"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentials"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentialstores"
|
||||
@@ -349,6 +350,13 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error {
|
||||
opsservices.RegisterHealthServiceServer(s, hs)
|
||||
c.HealthService = hs
|
||||
}
|
||||
if _, ok := currentServices[services.BillingService_ServiceDesc.ServiceName]; !ok {
|
||||
bs, err := billing.NewService(c.baseContext, c.BillingRepoFn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create billing handler service: %w", err)
|
||||
}
|
||||
services.RegisterBillingServiceServer(s, bs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -419,6 +427,9 @@ func registerGrpcGatewayEndpoints(ctx context.Context, gwMux *runtime.ServeMux,
|
||||
if err := services.RegisterPolicyServiceHandlerFromEndpoint(ctx, gwMux, gatewayTarget, dialOptions); err != nil {
|
||||
return fmt.Errorf("failed to register policy handler: %w", err)
|
||||
}
|
||||
if err := services.RegisterBillingServiceHandlerFromEndpoint(ctx, gwMux, gatewayTarget, dialOptions); err != nil {
|
||||
return fmt.Errorf("failed to register billing service handler: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ func TestHandleImplementedPaths(t *testing.T) {
|
||||
"v1/targets/someid",
|
||||
"v1/users",
|
||||
"v1/users/someid",
|
||||
"v1/billing:monthly-active-users",
|
||||
},
|
||||
"POST": {
|
||||
// Creation end points
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package billing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/billing"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/billing"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
var (
|
||||
// IdActions contains the set of actions that can be performed on
|
||||
// individual resources
|
||||
IdActions = action.NewActionSet()
|
||||
|
||||
// CollectionActions contains the set of actions that can be performed on
|
||||
// this collection
|
||||
CollectionActions = action.NewActionSet(
|
||||
action.MonthlyActiveUsers,
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
// TODO: refactor to remove IdActions and CollectionActions package variables
|
||||
action.RegisterResource(resource.Billing, IdActions, CollectionActions)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
pbs.UnsafeBillingServiceServer
|
||||
|
||||
repoFn common.BillingRepoFactory
|
||||
}
|
||||
|
||||
var _ pbs.BillingServiceServer = (*Service)(nil)
|
||||
|
||||
// NewService returns a billing service which handles billing related requests to boundary.
|
||||
func NewService(
|
||||
ctx context.Context,
|
||||
repoFn common.BillingRepoFactory,
|
||||
) (Service, error) {
|
||||
const op = "billing.NewService"
|
||||
if repoFn == nil {
|
||||
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing billing repository")
|
||||
}
|
||||
return Service{
|
||||
repoFn: repoFn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s Service) MonthlyActiveUsers(ctx context.Context, req *pbs.MonthlyActiveUsersRequest) (*pbs.MonthlyActiveUsersResponse, error) {
|
||||
const op = "billing.(Service).MonthlyActiveUsers"
|
||||
|
||||
authResults := s.authResult(ctx, action.MonthlyActiveUsers)
|
||||
if authResults.Error != nil {
|
||||
return nil, errors.Wrap(ctx, authResults.Error, op)
|
||||
}
|
||||
|
||||
// Get the billing information
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
var startTime, endTime *time.Time
|
||||
if req.GetStartTime() != "" {
|
||||
st, err := time.Parse("2006-01", req.GetStartTime())
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "start time is in an invalid format")
|
||||
}
|
||||
startTime = &st
|
||||
}
|
||||
if req.GetEndTime() != "" {
|
||||
et, err := time.Parse("2006-01", req.GetEndTime())
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "end time is in an invalid format")
|
||||
}
|
||||
endTime = &et
|
||||
}
|
||||
if startTime != nil && endTime != nil && !endTime.After(*startTime) {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "start time is not before end time")
|
||||
}
|
||||
|
||||
months, err := repo.MonthlyActiveUsers(
|
||||
ctx,
|
||||
billing.WithStartTime(startTime),
|
||||
billing.WithEndTime(endTime),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
var activeUsers []*pb.ActiveUsers
|
||||
for _, month := range months {
|
||||
maud := &pb.ActiveUsers{
|
||||
StartTime: timestamppb.New(month.StartTime),
|
||||
EndTime: timestamppb.New(month.EndTime),
|
||||
Count: month.ActiveUsersCount,
|
||||
}
|
||||
activeUsers = append(activeUsers, maud)
|
||||
}
|
||||
|
||||
return &pbs.MonthlyActiveUsersResponse{Items: activeUsers}, nil
|
||||
}
|
||||
|
||||
func (s Service) authResult(ctx context.Context, a action.Type) auth.VerifyResults {
|
||||
opts := []auth.Option{
|
||||
auth.WithType(resource.Billing),
|
||||
auth.WithAction(a),
|
||||
auth.WithScopeId(scope.Global.String()),
|
||||
}
|
||||
return auth.Verify(ctx, opts...)
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package billing_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/billing"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
|
||||
billingservice "github.com/hashicorp/boundary/internal/daemon/controller/handlers/billing"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/billing"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_MonthlyActiveUsers(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
|
||||
repoFn := func() (*billing.Repository, error) {
|
||||
return billing.TestRepo(t, conn), nil
|
||||
}
|
||||
billing.TestGenerateActiveUsers(t, conn)
|
||||
|
||||
wrap := db.TestWrapper(t)
|
||||
iamRepoFn := func() (*iam.Repository, error) {
|
||||
return iam.TestRepo(t, conn, wrap), nil
|
||||
}
|
||||
|
||||
today := time.Now().UTC()
|
||||
threeMonthsAgo := time.Date(today.AddDate(0, -3, 0).Year(), today.AddDate(0, -3, 0).Month(), 1, 0, 0, 0, 0, time.UTC).Format("2006-01")
|
||||
oneMonthAgo := time.Date(today.AddDate(0, -1, 0).Year(), today.AddDate(0, -1, 0).Month(), 1, 0, 0, 0, 0, time.UTC).Format("2006-01")
|
||||
badFormat := time.Date(today.Year(), today.Month(), 15, 0, 0, 0, 0, time.UTC).String()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
req *pbs.MonthlyActiveUsersRequest
|
||||
res *pbs.MonthlyActiveUsersResponse
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "Valid no options, current and previous months",
|
||||
req: &pbs.MonthlyActiveUsersRequest{},
|
||||
res: &pbs.MonthlyActiveUsersResponse{
|
||||
Items: []*pb.ActiveUsers{
|
||||
{
|
||||
Count: 0,
|
||||
StartTime: timestamppb.New(time.Date(today.Year(), today.Month(), 1, 0, 0, 0, 0, time.UTC)),
|
||||
EndTime: timestamppb.New(time.Date(today.Year(), today.Month(), today.Day(), today.Hour(), 0, 0, 0, time.UTC)),
|
||||
},
|
||||
{
|
||||
Count: 6,
|
||||
StartTime: timestamppb.New(time.Date(today.Year(), today.Month()-1, 1, 0, 0, 0, 0, time.UTC)),
|
||||
EndTime: timestamppb.New(time.Date(today.Year(), today.Month(), 1, 0, 0, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid start time",
|
||||
req: &pbs.MonthlyActiveUsersRequest{StartTime: threeMonthsAgo},
|
||||
res: &pbs.MonthlyActiveUsersResponse{
|
||||
Items: []*pb.ActiveUsers{
|
||||
{
|
||||
Count: 0,
|
||||
StartTime: timestamppb.New(time.Date(today.Year(), today.Month(), 1, 0, 0, 0, 0, time.UTC)),
|
||||
EndTime: timestamppb.New(time.Date(today.Year(), today.Month(), today.Day(), today.Hour(), 0, 0, 0, time.UTC)),
|
||||
},
|
||||
{
|
||||
Count: 6,
|
||||
StartTime: timestamppb.New(time.Date(today.Year(), today.Month()-1, 1, 0, 0, 0, 0, time.UTC)),
|
||||
EndTime: timestamppb.New(time.Date(today.Year(), today.Month(), 1, 0, 0, 0, 0, time.UTC)),
|
||||
},
|
||||
{
|
||||
Count: 6,
|
||||
StartTime: timestamppb.New(time.Date(today.Year(), today.Month()-2, 1, 0, 0, 0, 0, time.UTC)),
|
||||
EndTime: timestamppb.New(time.Date(today.Year(), today.Month()-1, 1, 0, 0, 0, 0, time.UTC)),
|
||||
},
|
||||
{
|
||||
Count: 6,
|
||||
StartTime: timestamppb.New(time.Date(today.Year(), today.Month()-3, 1, 0, 0, 0, 0, time.UTC)),
|
||||
EndTime: timestamppb.New(time.Date(today.Year(), today.Month()-2, 1, 0, 0, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid start and end time",
|
||||
req: &pbs.MonthlyActiveUsersRequest{StartTime: threeMonthsAgo, EndTime: oneMonthAgo},
|
||||
res: &pbs.MonthlyActiveUsersResponse{
|
||||
Items: []*pb.ActiveUsers{
|
||||
{
|
||||
Count: 6,
|
||||
StartTime: timestamppb.New(time.Date(today.Year(), today.Month()-2, 1, 0, 0, 0, 0, time.UTC)),
|
||||
EndTime: timestamppb.New(time.Date(today.Year(), today.Month()-1, 1, 0, 0, 0, 0, time.UTC)),
|
||||
},
|
||||
{
|
||||
Count: 6,
|
||||
StartTime: timestamppb.New(time.Date(today.Year(), today.Month()-3, 1, 0, 0, 0, 0, time.UTC)),
|
||||
EndTime: timestamppb.New(time.Date(today.Year(), today.Month()-2, 1, 0, 0, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid end time without start time",
|
||||
req: &pbs.MonthlyActiveUsersRequest{EndTime: oneMonthAgo},
|
||||
errContains: "end time set without start time",
|
||||
},
|
||||
{
|
||||
name: "Invalid end time before start time",
|
||||
req: &pbs.MonthlyActiveUsersRequest{StartTime: oneMonthAgo, EndTime: threeMonthsAgo},
|
||||
errContains: "start time is not before end time",
|
||||
},
|
||||
{
|
||||
name: "Invalid start time equals end time",
|
||||
req: &pbs.MonthlyActiveUsersRequest{StartTime: threeMonthsAgo, EndTime: threeMonthsAgo},
|
||||
errContains: "start time is not before end time",
|
||||
},
|
||||
{
|
||||
name: "Invalid start time format",
|
||||
req: &pbs.MonthlyActiveUsersRequest{StartTime: badFormat},
|
||||
errContains: "start time is in an invalid format",
|
||||
},
|
||||
{
|
||||
name: "Invalid end time format",
|
||||
req: &pbs.MonthlyActiveUsersRequest{StartTime: threeMonthsAgo, EndTime: badFormat},
|
||||
errContains: "end time is in an invalid format",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
b, err := billingservice.NewService(ctx, repoFn)
|
||||
require.NoError(t, err, "Couldn't create new billing service.")
|
||||
|
||||
got, gErr := b.MonthlyActiveUsers(auth.DisabledAuthTestContext(iamRepoFn, scope.Global.String(), auth.WithUserId(globals.AnyAuthenticatedUserId)), tc.req)
|
||||
if tc.errContains != "" {
|
||||
require.ErrorContains(t, gErr, tc.errContains)
|
||||
require.Nil(t, got)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, gErr)
|
||||
}
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
tc.res,
|
||||
protocmp.Transform(),
|
||||
protocmp.SortRepeatedFields(got),
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func Test_newRateLimiterConfig(t *testing.T) {
|
||||
ratelimit.DefaultLimiterMaxQuotas(),
|
||||
false,
|
||||
&rateLimiterConfig{
|
||||
maxSize: 322161,
|
||||
maxSize: 324162,
|
||||
configs: nil,
|
||||
disabled: false,
|
||||
limits: defaultLimits,
|
||||
|
||||
+1
-1
@@ -4075,7 +4075,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"max_size": 322161,
|
||||
"max_size": 324162,
|
||||
"msg": "controller api rate limiter"
|
||||
},
|
||||
"op": "controller.(rateLimiterConfig).writeSysEvent",
|
||||
|
||||
+1
-1
@@ -4075,7 +4075,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"max_size": 322161,
|
||||
"max_size": 324162,
|
||||
"msg": "controller api rate limiter"
|
||||
},
|
||||
"op": "controller.(rateLimiterConfig).writeSysEvent",
|
||||
|
||||
@@ -97,6 +97,7 @@ begin;
|
||||
;
|
||||
|
||||
-- replaces function from 14/01_wh_user_dimension_oidc.up.sql
|
||||
-- replaced in 82/02_wh_upsert_user_refact.up.sql
|
||||
drop function wh_upsert_user;
|
||||
create function wh_upsert_user(p_user_id wt_user_id, p_auth_token_id wt_public_id) returns wh_dim_key
|
||||
as $$
|
||||
|
||||
@@ -216,7 +216,8 @@ begin;
|
||||
create trigger wh_update_session_connection_accumulating_fact after insert on session_host_set_host
|
||||
for each row execute procedure wh_upsert_host();
|
||||
|
||||
-- replaced function in 16/04_wh_credential_dimension.up.sql
|
||||
-- Replaces function from 16/04_wh_credential_dimension.up.sql
|
||||
-- Replaced in 82/02_wh_upsert_user_refact.up.sql
|
||||
create or replace function wh_insert_session() returns trigger
|
||||
as $$
|
||||
declare
|
||||
|
||||
+1
@@ -136,6 +136,7 @@ begin;
|
||||
comment on table credential_vault_ssh_cert_library is
|
||||
'credential_vault_ssh_cert_library a credential library that issues credentials from a vault ssh secret backend.';
|
||||
|
||||
-- Replaced in 82/07_vault_ssh_cert_default.up.sql
|
||||
create function default_ssh_certificate_credential_type() returns trigger
|
||||
as $$
|
||||
begin
|
||||
|
||||
@@ -12,6 +12,7 @@ begin;
|
||||
|
||||
-- This index ensures that there will only ever be one row in the table.
|
||||
-- See: https://www.postgresql.org/docs/current/indexes-expressional.html
|
||||
-- Dropped in 82/09_census_add_metric.up.sql
|
||||
create unique index census_last_uploaded_one_row
|
||||
on census_last_uploaded((last_uploaded_at is not null));
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
|
||||
-- wh_user_id is similar to wt_user_id but for the data warehouse.
|
||||
-- Unlike wt_user_id, this allows for nulls.
|
||||
create domain wh_user_id as text
|
||||
check(
|
||||
length(trim(value)) > 10 or value in ('u_anon', 'u_auth', 'u_recovery')
|
||||
);
|
||||
comment on domain wh_user_id is
|
||||
'"u_anon", "u_auth", "u_recovery" or random ID generated with github.com/hashicorp/go-secure-stdlib/base62';
|
||||
|
||||
create table wh_auth_token_accumulating_fact (
|
||||
auth_token_id wt_public_id primary key,
|
||||
user_id wh_user_id not null,
|
||||
user_key wh_dim_key not null
|
||||
references wh_user_dimension (key)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
|
||||
-- date and time foreign keys
|
||||
auth_token_issued_date_key integer not null
|
||||
references wh_date_dimension (key)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
auth_token_issued_time_key integer not null
|
||||
references wh_time_of_day_dimension (key)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
auth_token_issued_time wh_timestamp,
|
||||
|
||||
auth_token_deleted_date_key integer default -1 not null
|
||||
references wh_date_dimension (key)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
auth_token_deleted_time_key integer default -1 not null
|
||||
references wh_time_of_day_dimension (key)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
auth_token_deleted_time wh_timestamp default 'infinity'::timestamptz,
|
||||
|
||||
auth_token_approximate_last_access_date_key integer default -1 not null
|
||||
references wh_date_dimension (key)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
auth_token_approximate_last_access_time_key integer default -1 not null
|
||||
references wh_time_of_day_dimension (key)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
auth_token_approximate_last_access_time wh_timestamp,
|
||||
|
||||
auth_token_approximate_active_time_range tstzrange not null default tstzrange(current_timestamp, current_timestamp, '[]'),
|
||||
auth_token_valid_time_range tstzrange not null default tstzrange(current_timestamp, 'infinity'::timestamptz, '[]'),
|
||||
|
||||
-- the auth_token_count must always be 1
|
||||
-- this is a common pattern in data warehouse models
|
||||
-- See The Data Warehouse Toolkit, Third Edition
|
||||
-- by Ralph Kimball and Margy Ross for more information
|
||||
auth_token_count smallint default 1 not null
|
||||
constraint auth_token_count_must_be_1
|
||||
check(auth_token_count = 1),
|
||||
|
||||
constraint last_accessed_time_lte_deleted_time
|
||||
check(auth_token_approximate_last_access_time <= auth_token_deleted_time),
|
||||
|
||||
constraint active_time_lower_eq_issued_time
|
||||
check(lower(auth_token_approximate_active_time_range) = auth_token_issued_time),
|
||||
constraint active_time_upper_eq_last_accessed_time
|
||||
check(upper(auth_token_approximate_active_time_range) = auth_token_approximate_last_access_time),
|
||||
constraint valid_time_lower_eq_issued
|
||||
check(lower(auth_token_valid_time_range) = auth_token_issued_time),
|
||||
constraint valid_time_upper_eq_deleted
|
||||
check(upper(auth_token_valid_time_range) = auth_token_deleted_time),
|
||||
constraint active_time_contained_by_valid_time
|
||||
check(auth_token_approximate_active_time_range <@ auth_token_valid_time_range)
|
||||
);
|
||||
comment on table wh_auth_token_accumulating_fact is
|
||||
'The Wh Auth Token Accumulating Fact table is an accumulating fact table. '
|
||||
'The grain of the fact table is one row per auth token.';
|
||||
|
||||
create function wh_insert_auth_token() returns trigger
|
||||
as $$
|
||||
begin
|
||||
select user_id
|
||||
into new.user_id
|
||||
from wh_user_dimension
|
||||
where key = new.user_key;
|
||||
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger wh_insert_auth_token before insert on wh_auth_token_accumulating_fact
|
||||
for each row execute function wh_insert_auth_token();
|
||||
|
||||
create trigger immutable_columns before update on wh_auth_token_accumulating_fact
|
||||
for each row execute procedure immutable_columns('user_id', 'user_key', 'auth_token_issued_time', 'auth_token_issued_time_key', 'auth_token_issued_date_key');
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,111 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
|
||||
drop trigger wh_insert_session on session;
|
||||
drop function wh_insert_session;
|
||||
drop function wh_upsert_user;
|
||||
|
||||
-- Replaces function from 15/01_wh_rename_key_columns.up.sql
|
||||
create function wh_upsert_user(p_auth_token_id wt_public_id) returns wh_dim_key
|
||||
as $$
|
||||
declare
|
||||
src whx_user_dimension_target%rowtype;
|
||||
target whx_user_dimension_target%rowtype;
|
||||
new_row wh_user_dimension%rowtype;
|
||||
acct_id wt_public_id;
|
||||
begin
|
||||
select auth_account_id into strict acct_id
|
||||
from auth_token
|
||||
where public_id = p_auth_token_id;
|
||||
|
||||
select * into target
|
||||
from whx_user_dimension_target as t
|
||||
where t.auth_account_id = acct_id;
|
||||
|
||||
select target.key, t.* into src
|
||||
from whx_user_dimension_source as t
|
||||
where t.auth_account_id = acct_id;
|
||||
|
||||
if src is distinct from target then
|
||||
|
||||
-- expire the current row
|
||||
update wh_user_dimension
|
||||
set current_row_indicator = 'Expired',
|
||||
row_expiration_time = current_timestamp
|
||||
where auth_account_id = acct_id
|
||||
and current_row_indicator = 'Current';
|
||||
|
||||
-- insert a new row
|
||||
insert into wh_user_dimension (
|
||||
user_id, user_name, user_description,
|
||||
auth_account_id, auth_account_type, auth_account_name, auth_account_description,
|
||||
auth_account_external_id, auth_account_full_name, auth_account_email,
|
||||
auth_method_id, auth_method_type, auth_method_name, auth_method_description,
|
||||
auth_method_external_id,
|
||||
user_organization_id, user_organization_name, user_organization_description,
|
||||
current_row_indicator, row_effective_time, row_expiration_time
|
||||
)
|
||||
select user_id, user_name, user_description,
|
||||
auth_account_id, auth_account_type, auth_account_name, auth_account_description,
|
||||
auth_account_external_id, auth_account_full_name, auth_account_email,
|
||||
auth_method_id, auth_method_type, auth_method_name, auth_method_description,
|
||||
auth_method_external_id,
|
||||
user_organization_id, user_organization_name, user_organization_description,
|
||||
'Current', current_timestamp, 'infinity'::timestamptz
|
||||
from whx_user_dimension_source
|
||||
where auth_account_id = acct_id
|
||||
returning * into new_row;
|
||||
|
||||
return new_row.key;
|
||||
end if;
|
||||
return target.key;
|
||||
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
comment on function wh_upsert_user is
|
||||
'wh_upsert_user a function insert or updates the wh_user_dimension table'
|
||||
'for the user that corresponds to the provided auth_token_id.';
|
||||
|
||||
-- Replaces function from 60/03_wh_sessions.up.sql
|
||||
create function wh_insert_session() returns trigger
|
||||
as $$
|
||||
declare
|
||||
new_row wh_session_accumulating_fact%rowtype;
|
||||
begin
|
||||
with
|
||||
pending_timestamp (date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key(start_time), wh_time_key(start_time), start_time
|
||||
from session_state
|
||||
where session_id = new.public_id
|
||||
and state = 'pending'
|
||||
)
|
||||
insert into wh_session_accumulating_fact (
|
||||
session_id,
|
||||
auth_token_id,
|
||||
host_key,
|
||||
user_key,
|
||||
credential_group_key,
|
||||
session_pending_date_key,
|
||||
session_pending_time_key,
|
||||
session_pending_time
|
||||
)
|
||||
select new.public_id,
|
||||
new.auth_token_id,
|
||||
'no host source', -- will be updated by wh_upsert_host
|
||||
wh_upsert_user(new.auth_token_id),
|
||||
'no credentials', -- will be updated by wh_upsert_credential_group
|
||||
pending_timestamp.date_dim_key,
|
||||
pending_timestamp.time_dim_key,
|
||||
pending_timestamp.ts
|
||||
from pending_timestamp
|
||||
returning * into strict new_row;
|
||||
return null;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger wh_insert_session after insert on session
|
||||
for each row execute procedure wh_insert_session();
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
create function auth_token_before_issued() returns trigger
|
||||
as $$
|
||||
begin
|
||||
if new.status = 'token issued' then
|
||||
new.approximate_last_access_time = now();
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger auth_token_before_inserted before insert on auth_token
|
||||
for each row execute procedure auth_token_before_issued();
|
||||
create trigger auth_token_before_updated before update on auth_token
|
||||
for each row execute procedure auth_token_before_issued();
|
||||
commit;
|
||||
@@ -0,0 +1,51 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
create function wh_auth_token_issued(p_auth_token_id wt_public_id, issued_ts wt_timestamp, last_accessed_ts wt_timestamp) returns void
|
||||
as $$
|
||||
declare
|
||||
new_row wh_auth_token_accumulating_fact%rowtype;
|
||||
begin
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id,
|
||||
user_key,
|
||||
auth_token_issued_date_key,
|
||||
auth_token_issued_time_key,
|
||||
auth_token_issued_time,
|
||||
auth_token_approximate_last_access_date_key,
|
||||
auth_token_approximate_last_access_time_key,
|
||||
auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range
|
||||
)
|
||||
select p_auth_token_id,
|
||||
wh_upsert_user(p_auth_token_id),
|
||||
wh_date_key(issued_ts),
|
||||
wh_time_key(issued_ts),
|
||||
issued_ts,
|
||||
wh_date_key(last_accessed_ts),
|
||||
wh_time_key(last_accessed_ts),
|
||||
last_accessed_ts,
|
||||
tstzrange(issued_ts, last_accessed_ts, '[]'),
|
||||
tstzrange(issued_ts, 'infinity'::wt_timestamp, '[]')
|
||||
returning * into strict new_row;
|
||||
return;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
comment on function wh_auth_token_issued is
|
||||
'wh_auth_token_issued is a function called when an auth token is issued to insert a fact into the auth token accumulating fact table.';
|
||||
|
||||
create function auth_token_inserted() returns trigger
|
||||
as $$
|
||||
begin
|
||||
if new.status = 'token issued' then
|
||||
perform wh_auth_token_issued(new.public_id, new.update_time, new.approximate_last_access_time);
|
||||
end if;
|
||||
return null;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger auth_token_inserted after insert on auth_token
|
||||
for each row execute procedure auth_token_inserted();
|
||||
commit;
|
||||
@@ -0,0 +1,38 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
create function wh_auth_token_accessed(p_auth_token_id wt_public_id, last_accessed_ts wt_timestamp) returns void
|
||||
as $$
|
||||
begin
|
||||
update wh_auth_token_accumulating_fact
|
||||
set auth_token_approximate_last_access_date_key = wh_date_key(last_accessed_ts),
|
||||
auth_token_approximate_last_access_time_key = wh_time_key(last_accessed_ts),
|
||||
auth_token_approximate_last_access_time = last_accessed_ts,
|
||||
auth_token_approximate_active_time_range = tstzrange(lower(auth_token_approximate_active_time_range), last_accessed_ts, '[]')
|
||||
where auth_token_id = p_auth_token_id;
|
||||
return;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
comment on function wh_auth_token_accessed is
|
||||
'wh_auth_token_accessed is a function that updates the wh_auth_token_accumulating_fact'
|
||||
'when an auth_token is accessed.';
|
||||
|
||||
create function auth_token_updated() returns trigger
|
||||
as $$
|
||||
begin
|
||||
case
|
||||
when new.status = 'token issued' and new.status is distinct from old.status then
|
||||
perform wh_auth_token_issued(new.public_id, new.update_time, new.approximate_last_access_time);
|
||||
when new.approximate_last_access_time is distinct from old.approximate_last_access_time then
|
||||
perform wh_auth_token_accessed(new.public_id, new.approximate_last_access_time);
|
||||
else
|
||||
return null;
|
||||
end case;
|
||||
return null;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger auth_token_updated after update on auth_token
|
||||
for each row execute procedure auth_token_updated();
|
||||
commit;
|
||||
@@ -0,0 +1,33 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
create function wh_auth_token_deleted(p_auth_token_id wt_public_id) returns void
|
||||
as $$
|
||||
begin
|
||||
update wh_auth_token_accumulating_fact
|
||||
set auth_token_deleted_date_key = wh_date_key(now()),
|
||||
auth_token_deleted_time_key = wh_time_key(now()),
|
||||
auth_token_deleted_time = now(),
|
||||
auth_token_valid_time_range = tstzrange(lower(auth_token_valid_time_range), now(), '[]')
|
||||
where auth_token_id = p_auth_token_id;
|
||||
return;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
comment on function wh_auth_token_deleted is
|
||||
'wh_auth_token_deleted is a function that updates the wh_auth_token_accumulating_fact'
|
||||
'when a previously issued auth_token is deleted.';
|
||||
|
||||
create function auth_token_deleted() returns trigger
|
||||
as $$
|
||||
begin
|
||||
if old.status = 'token issued' then
|
||||
perform wh_auth_token_deleted(old.public_id);
|
||||
end if;
|
||||
return null;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger auth_token_deleted after delete on auth_token
|
||||
for each row execute procedure auth_token_deleted();
|
||||
commit;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
drop trigger default_ssh_certificate_credential_type on credential_vault_ssh_cert_library;
|
||||
drop function default_ssh_certificate_credential_type;
|
||||
|
||||
-- Replaces trigger in 63/01_credential_vault_ssh_cert_library.up.sql
|
||||
create function default_ssh_certificate_credential_type() returns trigger
|
||||
as $$
|
||||
begin
|
||||
if new.credential_type is distinct from 'ssh_certificate' then
|
||||
new.credential_type = 'ssh_certificate';
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
comment on function default_ssh_certificate_credential_type is
|
||||
'default_ssh_certificate_credential_type ensures the credential_type is set to ssh_certificate';
|
||||
|
||||
create trigger default_ssh_certificate_credential_type before insert on credential_vault_ssh_cert_library
|
||||
for each row execute procedure default_ssh_certificate_credential_type();
|
||||
commit;
|
||||
@@ -0,0 +1,121 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
create view hcp_billing_monthly_active_users_last_2_months as
|
||||
with
|
||||
monthly_range (month) as (
|
||||
select date_trunc('month', time) as month
|
||||
from generate_series(
|
||||
date_trunc('month', now() - interval '1 month'),
|
||||
now(),
|
||||
'1 month'::interval
|
||||
) as time
|
||||
),
|
||||
final (start_time, end_time, active_users_count) as (
|
||||
select monthly_range.month, -- start
|
||||
case when monthly_range.month = date_trunc('month', now())
|
||||
then date_trunc('hour', now())
|
||||
else monthly_range.month + interval '1 month'
|
||||
end,
|
||||
count(distinct(user_id))
|
||||
from monthly_range
|
||||
left join wh_auth_token_accumulating_fact as wh_ataf
|
||||
on wh_ataf.auth_token_approximate_active_time_range && tstzrange(monthly_range.month, monthly_range.month + interval '1 month', '[)')
|
||||
group by monthly_range.month
|
||||
)
|
||||
select start_time, end_time, active_users_count
|
||||
from final
|
||||
order by start_time desc;
|
||||
comment on view hcp_billing_monthly_active_users_last_2_months is
|
||||
'hcp_billing_monthly_active_users_last_2_months is a view that contains '
|
||||
'the count of active users for the current month and the previous month. '
|
||||
'The current month is a sum from the beginning of the current month '
|
||||
'until the start of the current hour (exclusive).';
|
||||
|
||||
create view hcp_billing_monthly_active_users_all as
|
||||
with
|
||||
monthly_range (month) as (
|
||||
select date_trunc('month', time) as month
|
||||
from generate_series(
|
||||
date_trunc('month', (select min(auth_token_issued_time)
|
||||
from wh_auth_token_accumulating_fact)),
|
||||
now(),
|
||||
'1 month'::interval
|
||||
) as time
|
||||
),
|
||||
final (start_time, end_time, active_users_count) as (
|
||||
select monthly_range.month, -- start
|
||||
case when monthly_range.month = date_trunc('month', now())
|
||||
then date_trunc('hour', now())
|
||||
else monthly_range.month + interval '1 month'
|
||||
end,
|
||||
count(distinct(user_id))
|
||||
from monthly_range
|
||||
left join wh_auth_token_accumulating_fact as wh_ataf
|
||||
on wh_ataf.auth_token_approximate_active_time_range && tstzrange(monthly_range.month, monthly_range.month + interval '1 month', '[)')
|
||||
group by monthly_range.month
|
||||
)
|
||||
select start_time, end_time, active_users_count
|
||||
from final
|
||||
order by start_time desc;
|
||||
comment on view hcp_billing_monthly_active_users_all is
|
||||
'hcp_billing_monthly_active_users_all is a view that contains '
|
||||
'the count of active users for the all months.'
|
||||
'The current month is a sum from the beginning of the current month '
|
||||
'until the start of the current hour (exclusive).';
|
||||
|
||||
create function hcp_billing_monthly_active_users_last_2_months()
|
||||
returns table(start_time timestamp with time zone, end_time timestamp with time zone, active_user_count bigint)
|
||||
as $$
|
||||
select *
|
||||
from hcp_billing_monthly_active_users_last_2_months;
|
||||
$$ language sql
|
||||
immutable
|
||||
parallel safe -- all of the functions called are parallel safe
|
||||
strict -- means the function returns null on null input
|
||||
set timezone to 'utc';
|
||||
comment on function hcp_billing_monthly_active_users_last_2_months is
|
||||
'hcp_billing_monthly_active_users_last_2_months is a function that contains '
|
||||
'the count of active users for the current month and the previous month. '
|
||||
'The current month is a sum from the beginning of the current month '
|
||||
'until the start of the current hour (exclusive).'
|
||||
'All timestamps returned are in UTC.';
|
||||
|
||||
create function hcp_billing_monthly_active_users_all(p_start_time timestamptz default null,
|
||||
p_end_time timestamptz default null)
|
||||
returns setof hcp_billing_monthly_active_users_all
|
||||
as $$
|
||||
begin
|
||||
case
|
||||
when p_start_time is not null and p_end_time is not null then
|
||||
return query select *
|
||||
from hcp_billing_monthly_active_users_all
|
||||
where start_time >= p_start_time
|
||||
and end_time <= p_end_time;
|
||||
when p_start_time is not null then
|
||||
return query select *
|
||||
from hcp_billing_monthly_active_users_all
|
||||
where start_time >= p_start_time;
|
||||
when p_end_time is not null then
|
||||
return query select *
|
||||
from hcp_billing_monthly_active_users_all
|
||||
where end_time <= p_end_time;
|
||||
else
|
||||
return query select *
|
||||
from hcp_billing_monthly_active_users_all;
|
||||
end case;
|
||||
return;
|
||||
end;
|
||||
$$ language plpgsql
|
||||
immutable
|
||||
parallel safe -- all of the functions called are parallel safe
|
||||
set timezone to 'utc';
|
||||
comment on function hcp_billing_monthly_active_users_all is
|
||||
'hcp_billing_monthly_active_users_all is a function that contains '
|
||||
'the count of active users for the all months.'
|
||||
'The current month is a sum from the beginning of the current month '
|
||||
'until the start of the current hour (exclusive).'
|
||||
'It can be provided with a start time and end time to restrict the results.'
|
||||
'All timestamps returned are in UTC.';
|
||||
commit;
|
||||
@@ -0,0 +1,47 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
-- Create enum table for valid cenus metrics.
|
||||
create table census_metric_enm (
|
||||
name text primary key
|
||||
constraint only_predefined_census_metrics_allowed
|
||||
check (
|
||||
name in (
|
||||
'sessions',
|
||||
'active_users'
|
||||
)
|
||||
)
|
||||
);
|
||||
comment on table census_metric_enm is
|
||||
'census_metric_enm is an enumeration table for census metric types.';
|
||||
|
||||
insert into census_metric_enm
|
||||
(name)
|
||||
values ('sessions'),
|
||||
('active_users');
|
||||
|
||||
-- Drop constraint and index from 72/01_census.up.sql
|
||||
drop index census_last_uploaded_one_row;
|
||||
alter table census_last_uploaded drop constraint census_last_uploaded_pkey;
|
||||
|
||||
-- Add new column and populate the default value.
|
||||
alter table census_last_uploaded
|
||||
add column metric text
|
||||
default 'sessions'
|
||||
not null
|
||||
primary key
|
||||
constraint census_metric_enm_fkey
|
||||
references census_metric_enm (name)
|
||||
on delete restrict
|
||||
on update restrict;
|
||||
alter table census_last_uploaded alter column metric drop default;
|
||||
comment on table census_last_uploaded is
|
||||
'census_last_uploaded is a table which contains the timestamp '
|
||||
'of the last time census data was uploaded, per census metric.';
|
||||
|
||||
-- Add new row for active_users.
|
||||
insert into census_last_uploaded
|
||||
(last_uploaded_at, metric)
|
||||
values ('-infinity'::timestamptz, 'active_users');
|
||||
commit;
|
||||
@@ -12,7 +12,6 @@ ifneq ($(strip $(SQL_TEST_DB_PORT)),)
|
||||
DOCKER_ARGS += -p $(SQL_TEST_DB_PORT):5432
|
||||
endif
|
||||
|
||||
|
||||
# Pass through options to pg_prove
|
||||
# See: https://pgtap.org/pg_prove.html
|
||||
PROVE_OPTS ?=
|
||||
@@ -47,6 +46,11 @@ PG_TAP_DOCKER_IMAGE := $(PG_TAP_DOCKER_IMAGE_BASE):$(PG_TAP_DOCKER_TAG)
|
||||
|
||||
SQL_TEST_CONTAINER_NAME ?= boundary-sql-tests
|
||||
|
||||
log_cmd = true
|
||||
DB_LOGS ?= 0
|
||||
ifeq ($(DB_LOGS), 1)
|
||||
log_cmd = docker logs $(SQL_TEST_CONTAINER_NAME)
|
||||
endif
|
||||
|
||||
# re-write paths for docker
|
||||
dockerized_tests = $(patsubst tests/%,/test/%,$(TESTS))
|
||||
@@ -74,7 +78,7 @@ test:
|
||||
-e TESTS="$(PROVE_OPTS) $(dockerized_tests)" \
|
||||
-v "$(CWD)/tests":/test \
|
||||
$(PG_TAP_DOCKER_IMAGE); \
|
||||
(ret=$$?; docker stop $(SQL_TEST_CONTAINER_NAME) &>/dev/null && docker logs $(SQL_TEST_CONTAINER_NAME) && docker rm -v $(SQL_TEST_CONTAINER_NAME) &>/dev/null && exit $$ret)
|
||||
(ret=$$?; docker stop $(SQL_TEST_CONTAINER_NAME) > /dev/null 2>&1 && $(log_cmd) && docker rm -f -v $(SQL_TEST_CONTAINER_NAME) > /dev/null 2>&1 && exit $$ret)
|
||||
|
||||
database-up:
|
||||
@echo Using $(POSTGRES_DOCKER_IMAGE)
|
||||
|
||||
@@ -199,13 +199,13 @@ begin;
|
||||
('p____gcolors', 'hc__st_____g', 'Green Color Static Catalog');
|
||||
|
||||
insert into auth_token
|
||||
(key_id, auth_account_id, public_id, token)
|
||||
(key_id, auth_account_id, public_id, token, expiration_time, status)
|
||||
values
|
||||
('kdkv__colors', 'apa____clare', 'tok____clare', 'tok____clare'::bytea),
|
||||
('kdkv__colors', 'apa____cindy', 'tok____cindy', 'tok____cindy'::bytea),
|
||||
('kdkv__colors', 'apa____ciara', 'tok____ciara', 'tok____ciara'::bytea),
|
||||
('kdkv__colors', 'apa____carly', 'tok____carly', 'tok____carly'::bytea),
|
||||
('kdkv__colors', 'apa_____cora', 'tok_____cora', 'tok_____cora'::bytea);
|
||||
('kdkv__colors', 'apa____clare', 'tok____clare', 'tok____clare'::bytea, now() + interval '15 days', 'token issued'),
|
||||
('kdkv__colors', 'apa____cindy', 'tok____cindy', 'tok____cindy'::bytea, now() + interval '15 days', 'token issued'),
|
||||
('kdkv__colors', 'apa____ciara', 'tok____ciara', 'tok____ciara'::bytea, now() + interval '15 days', 'auth token pending'),
|
||||
('kdkv__colors', 'apa____carly', 'tok____carly', 'tok____carly'::bytea, now() + interval '15 days', 'token issued'),
|
||||
('kdkv__colors', 'apa_____cora', 'tok_____cora', 'tok_____cora'::bytea, now() + interval '15 days', 'auth token pending');
|
||||
|
||||
insert into static_host
|
||||
(catalog_id, public_id, address)
|
||||
|
||||
@@ -133,7 +133,7 @@ begin;
|
||||
(key_id, table_name, total_count)
|
||||
values
|
||||
('kdkv___widget', 'auth_token', 100);
|
||||
|
||||
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
@@ -172,13 +172,13 @@ begin;
|
||||
update auth_account set iam_user_id = 'u_____wilson' where public_id = 'apa___wilson';
|
||||
|
||||
insert into auth_token
|
||||
(key_id, auth_account_id, public_id, token)
|
||||
(key_id, auth_account_id, public_id, token, expiration_time, status)
|
||||
values
|
||||
('kdkv___widget', 'apa___walter', 'tok___walter', 'tok___walter'::bytea),
|
||||
('kdkv___widget', 'apa1__walter', 'tok1__walter', 'tok1__walter'::bytea),
|
||||
('kdkv___widget', 'apa___warren', 'tok___warren', 'tok___warren'::bytea),
|
||||
('kdkv___widget', 'apa___waylon', 'tok___waylon', 'tok___waylon'::bytea),
|
||||
('kdkv___widget', 'apa___wilson', 'tok___wilson', 'tok___wilson'::bytea);
|
||||
('kdkv___widget', 'apa___walter', 'tok___walter', 'tok___walter'::bytea, now() + interval '15 days', 'token issued'),
|
||||
('kdkv___widget', 'apa1__walter', 'tok1__walter', 'tok1__walter'::bytea, now() + interval '15 days', 'token issued'),
|
||||
('kdkv___widget', 'apa___warren', 'tok___warren', 'tok___warren'::bytea, now() + interval '15 days', 'token issued'),
|
||||
('kdkv___widget', 'apa___waylon', 'tok___waylon', 'tok___waylon'::bytea, now() + interval '15 days', 'token issued'),
|
||||
('kdkv___widget', 'apa___wilson', 'tok___wilson', 'tok___wilson'::bytea, now() + interval '15 days', 'auth token pending');
|
||||
|
||||
insert into auth_oidc_method
|
||||
(scope_id, public_id, client_id, name, state, key_id, issuer)
|
||||
@@ -206,7 +206,7 @@ begin;
|
||||
('o_____widget', 'alm___widget', 'Widget LDAP', 'active-private');
|
||||
insert into auth_ldap_url
|
||||
(ldap_method_id, url, connection_priority)
|
||||
values
|
||||
values
|
||||
('alm___widget', 'ldaps://ldap1', 1);
|
||||
|
||||
insert into auth_ldap_account
|
||||
|
||||
@@ -3,28 +3,55 @@
|
||||
|
||||
begin;
|
||||
|
||||
select plan(6);
|
||||
select plan(7);
|
||||
|
||||
select has_table('census_last_uploaded');
|
||||
select is(count(*), 1::bigint, 'census_last_uploaded should have only 1 row') from census_last_uploaded;
|
||||
select ok(not isfinite(last_uploaded_at)) from census_last_uploaded;
|
||||
select is(count(*), 2::bigint, 'census_last_uploaded should have only 2 row') from census_last_uploaded;
|
||||
select results_eq(
|
||||
$$
|
||||
select last_uploaded_at::timestamptz, metric
|
||||
from census_last_uploaded
|
||||
$$,
|
||||
$$
|
||||
values ('-infinity'::timestamptz, 'sessions'),
|
||||
('-infinity'::timestamptz, 'active_users')
|
||||
$$);
|
||||
|
||||
|
||||
prepare insert_row as
|
||||
insert into census_last_uploaded
|
||||
(last_uploaded_at)
|
||||
values
|
||||
(now());
|
||||
(last_uploaded_at, metric)
|
||||
values (now(), 'sessions');
|
||||
|
||||
select throws_ok('insert_row', '23505',
|
||||
'duplicate key value violates unique constraint "census_last_uploaded_one_row"',
|
||||
'insert into census_last_uploaded should fail');
|
||||
select throws_ok('insert_row',
|
||||
'23505',
|
||||
'duplicate key value violates unique constraint "census_last_uploaded_pkey"',
|
||||
'insert into census_last_uploaded should fail');
|
||||
|
||||
prepare insert_row_invalid_metric as
|
||||
insert into census_last_uploaded
|
||||
(last_uploaded_at, metric)
|
||||
values (now(), 'foo');
|
||||
|
||||
select throws_ok('insert_row_invalid_metric',
|
||||
'23503',
|
||||
'insert or update on table "census_last_uploaded" violates foreign key constraint "census_metric_enm_fkey"',
|
||||
'insert into census_last_uploaded should fail');
|
||||
|
||||
prepare update_census_last_uploaded as
|
||||
update census_last_uploaded
|
||||
set last_uploaded_at = now();
|
||||
|
||||
select lives_ok('update_census_last_uploaded');
|
||||
select ok(isfinite(last_uploaded_at)) from census_last_uploaded;
|
||||
select results_eq(
|
||||
$$
|
||||
select last_uploaded_at::timestamptz, metric
|
||||
from census_last_uploaded
|
||||
$$,
|
||||
$$
|
||||
values (now(), 'sessions'),
|
||||
(now(), 'active_users')
|
||||
$$);
|
||||
|
||||
select * from finish();
|
||||
|
||||
|
||||
+4
-4
@@ -42,22 +42,22 @@ begin;
|
||||
select is(count(*), 0::bigint) from credential_vault_library_ssh_private_key_mapping_override where library_id = 'vl______wvl8';
|
||||
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl8';
|
||||
|
||||
prepare insert_credential_vault_library_ssh_private_key_mapping_override as
|
||||
prepare insert_cvl_ssh_private_key_mapping_override as
|
||||
insert into credential_vault_library_ssh_private_key_mapping_override
|
||||
(library_id, username_attribute, private_key_attribute)
|
||||
values
|
||||
('vl______wvl8', 'my_username', 'my_private_key');
|
||||
select lives_ok('insert_credential_vault_library_ssh_private_key_mapping_override');
|
||||
select lives_ok('insert_cvl_ssh_private_key_mapping_override');
|
||||
|
||||
select is(count(*), 1::bigint) from credential_vault_library_ssh_private_key_mapping_override where library_id = 'vl______wvl8';
|
||||
select is(count(*), 1::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl8';
|
||||
|
||||
-- validate the delete triggers
|
||||
prepare delete_credential_vault_library_ssh_private_key_mapping_override as
|
||||
prepare delete_cvl_ssh_private_key_mapping_override as
|
||||
delete
|
||||
from credential_vault_library_ssh_private_key_mapping_override
|
||||
where library_id = 'vl______wvl8';
|
||||
select lives_ok('delete_credential_vault_library_ssh_private_key_mapping_override');
|
||||
select lives_ok('delete_cvl_ssh_private_key_mapping_override');
|
||||
|
||||
select is(count(*), 0::bigint) from credential_vault_library_ssh_private_key_mapping_override where library_id = 'vl______wvl8';
|
||||
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl8';
|
||||
|
||||
+4
-4
@@ -42,22 +42,22 @@ begin;
|
||||
select is(count(*), 0::bigint) from credential_vault_library_username_password_mapping_override where library_id = 'vl______wvl3';
|
||||
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl3';
|
||||
|
||||
prepare insert_credential_vault_library_username_password_mapping_override as
|
||||
prepare insert_cvl_username_password_mapping_override as
|
||||
insert into credential_vault_library_username_password_mapping_override
|
||||
(library_id, username_attribute, password_attribute)
|
||||
values
|
||||
('vl______wvl3', 'my_username', 'my_password');
|
||||
select lives_ok('insert_credential_vault_library_username_password_mapping_override');
|
||||
select lives_ok('insert_cvl_username_password_mapping_override');
|
||||
|
||||
select is(count(*), 1::bigint) from credential_vault_library_username_password_mapping_override where library_id = 'vl______wvl3';
|
||||
select is(count(*), 1::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl3';
|
||||
|
||||
-- validate the delete triggers
|
||||
prepare delete_credential_vault_library_username_password_mapping_override as
|
||||
prepare delete_cvl_username_password_mapping_override as
|
||||
delete
|
||||
from credential_vault_library_username_password_mapping_override
|
||||
where library_id = 'vl______wvl3';
|
||||
select lives_ok('delete_credential_vault_library_username_password_mapping_override');
|
||||
select lives_ok('delete_cvl_username_password_mapping_override');
|
||||
|
||||
select is(count(*), 0::bigint) from credential_vault_library_username_password_mapping_override where library_id = 'vl______wvl3';
|
||||
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl3';
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
select plan(3);
|
||||
|
||||
create table test_wh_user_id (
|
||||
id wh_user_id
|
||||
);
|
||||
|
||||
prepare too_short as
|
||||
insert into test_wh_user_id (id)
|
||||
values ('short');
|
||||
select throws_ok(
|
||||
'too_short',
|
||||
'23514',
|
||||
'value for domain wh_user_id violates check constraint "wh_user_id_check"'
|
||||
);
|
||||
prepare too_short_trim as
|
||||
insert into test_wh_user_id (id)
|
||||
values (' short ');
|
||||
select throws_ok(
|
||||
'too_short_trim',
|
||||
'23514',
|
||||
'value for domain wh_user_id violates check constraint "wh_user_id_check"'
|
||||
);
|
||||
|
||||
prepare valid as
|
||||
insert into test_wh_user_id (id)
|
||||
values ('u_123456789'),
|
||||
('u_anon'),
|
||||
('u_auth'),
|
||||
('u_recovery');
|
||||
select lives_ok('valid');
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -0,0 +1,470 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
select plan(39);
|
||||
|
||||
select lives_ok('truncate wh_auth_token_accumulating_fact,
|
||||
wh_user_dimension,
|
||||
wh_session_accumulating_fact,
|
||||
wh_session_connection_accumulating_fact;',
|
||||
'Truncate tables in preparation for testing');
|
||||
|
||||
-- validate the warehouse fact tables are empty
|
||||
select is(count(*), 0::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 0::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
|
||||
-- Create a user dimension for corresponding auth token's user.
|
||||
insert into wh_user_dimension (
|
||||
user_id, user_name, user_description,
|
||||
auth_account_id, auth_account_type, auth_account_name, auth_account_description,
|
||||
auth_method_id, auth_method_type, auth_method_name, auth_method_description,
|
||||
user_organization_id, user_organization_name, user_organization_description,
|
||||
current_row_indicator,
|
||||
row_effective_time, row_expiration_time,
|
||||
auth_method_external_id, auth_account_external_id, auth_account_full_name, auth_account_email
|
||||
) values (
|
||||
'u_____user1', 'None', 'None',
|
||||
'a______acc1', 'None', 'None', 'None',
|
||||
'am______am1', 'None', 'None', 'None',
|
||||
'o______org1', 'None', 'None',
|
||||
'current',
|
||||
now(), 'infinity'::timestamptz,
|
||||
'None', 'None', 'None', 'None'
|
||||
);
|
||||
|
||||
-- validate view returns 0 active users for last two months
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_last_2_months',
|
||||
$$
|
||||
values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint),
|
||||
(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 0::bigint)
|
||||
$$);
|
||||
|
||||
|
||||
-- Now insert some edge cases to make sure we count them correctly
|
||||
|
||||
-- active_time_range spans start of a month
|
||||
with month_range (previous) as (
|
||||
select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)')
|
||||
),
|
||||
range (active, valid) as (
|
||||
select tstzrange(lower(previous) - interval '5 minutes', lower(previous) + interval '5 minutes', '[]'),
|
||||
tstzrange(lower(previous) - interval '5 minutes', 'infinity'::timestamptz)
|
||||
from month_range
|
||||
),
|
||||
user_key (key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'at____atok1', user_key.key,
|
||||
wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid),
|
||||
coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid),
|
||||
wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active),
|
||||
range.active,
|
||||
range.valid,
|
||||
1
|
||||
from range, user_key;
|
||||
|
||||
select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_last_2_months',
|
||||
$$
|
||||
values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint),
|
||||
(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint)
|
||||
$$);
|
||||
|
||||
-- active_time_range within start/end of month
|
||||
select lives_ok('truncate wh_auth_token_accumulating_fact');
|
||||
with month_range (previous) as (
|
||||
select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)')
|
||||
),
|
||||
range (active, valid) as (
|
||||
select tstzrange(lower(previous) + interval '5 minutes', lower(previous) + interval '10 minutes', '[]'),
|
||||
tstzrange(lower(previous) + interval '5 minutes', 'infinity'::timestamptz)
|
||||
from month_range
|
||||
),
|
||||
user_key (key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'at____atok1', user_key.key,
|
||||
wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid),
|
||||
coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid),
|
||||
wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active),
|
||||
range.active,
|
||||
range.valid,
|
||||
1
|
||||
from range, user_key;
|
||||
|
||||
select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_last_2_months',
|
||||
$$
|
||||
values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint),
|
||||
(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint)
|
||||
$$);
|
||||
|
||||
-- active_time_range spances end of month
|
||||
select lives_ok('truncate wh_auth_token_accumulating_fact');
|
||||
with month_range (previous) as (
|
||||
select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)')
|
||||
),
|
||||
range (active, valid) as (
|
||||
select tstzrange(upper(previous) - interval '5 minutes', upper(previous) + interval '5 minutes', '[]'),
|
||||
tstzrange(upper(previous) - interval '5 minutes', 'infinity'::timestamptz)
|
||||
from month_range
|
||||
),
|
||||
user_key (key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'at____atok1', user_key.key,
|
||||
wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid),
|
||||
coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid),
|
||||
wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active),
|
||||
range.active,
|
||||
range.valid,
|
||||
1
|
||||
from range, user_key;
|
||||
|
||||
select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_last_2_months',
|
||||
$$
|
||||
values (date_trunc('month', now()), date_trunc('hour', now()), 1::bigint),
|
||||
(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint)
|
||||
$$);
|
||||
|
||||
-- active_time_range starts on start of month
|
||||
select lives_ok('truncate wh_auth_token_accumulating_fact');
|
||||
with month_range (previous) as (
|
||||
select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)')
|
||||
),
|
||||
range (active, valid) as (
|
||||
select tstzrange(lower(previous), lower(previous) + interval '5 minutes', '[]'),
|
||||
tstzrange(lower(previous), 'infinity'::timestamptz)
|
||||
from month_range
|
||||
),
|
||||
user_key (key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'at____atok1', user_key.key,
|
||||
wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid),
|
||||
coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid),
|
||||
wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active),
|
||||
range.active,
|
||||
range.valid,
|
||||
1
|
||||
from range, user_key;
|
||||
|
||||
select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_last_2_months',
|
||||
$$
|
||||
values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint),
|
||||
(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint)
|
||||
$$);
|
||||
|
||||
-- active_time_range ends on end of month
|
||||
select lives_ok('truncate wh_auth_token_accumulating_fact');
|
||||
with month_range (previous) as (
|
||||
select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)')
|
||||
),
|
||||
range (active, valid) as (
|
||||
-- the resolution on a timestamptz is 1 microsecond, so subtracting one microsecond from the upper bound of
|
||||
-- the month range puts us as close to the "end of month" as possible, wthout going over.
|
||||
-- https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME
|
||||
select tstzrange(lower(previous) + interval '5 minutes', upper(previous) - interval '1 microsecond', '[]'),
|
||||
tstzrange(lower(previous) + interval '5 minutes', 'infinity'::timestamptz)
|
||||
from month_range
|
||||
),
|
||||
user_key (key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'at____atok1', user_key.key,
|
||||
wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid),
|
||||
coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid),
|
||||
wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active),
|
||||
range.active,
|
||||
range.valid,
|
||||
1
|
||||
from range, user_key;
|
||||
|
||||
select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_last_2_months',
|
||||
$$
|
||||
values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint),
|
||||
(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint)
|
||||
$$);
|
||||
|
||||
-- active_time_range starts on end of month
|
||||
select lives_ok('truncate wh_auth_token_accumulating_fact');
|
||||
with month_range (previous) as (
|
||||
select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)')
|
||||
),
|
||||
range (active, valid) as (
|
||||
-- the resolution on a timestamptz is 1 microsecond, so subtracting one microsecond from the upper bound of
|
||||
-- the month range puts us as close to the "end of month" as possible, wthout going over.
|
||||
-- https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME
|
||||
select tstzrange(upper(previous) - interval '1 microsecond', upper(previous) + '5 minutes', '[]'),
|
||||
tstzrange(upper(previous) - interval '1 microsecond', 'infinity'::timestamptz)
|
||||
from month_range
|
||||
),
|
||||
user_key (key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'at____atok1', user_key.key,
|
||||
wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid),
|
||||
coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid),
|
||||
wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active),
|
||||
range.active,
|
||||
range.valid,
|
||||
1
|
||||
from range, user_key;
|
||||
|
||||
select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_last_2_months',
|
||||
$$
|
||||
values (date_trunc('month', now()), date_trunc('hour', now()), 1::bigint),
|
||||
(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint)
|
||||
$$);
|
||||
|
||||
-- active_time_range ends on start of month
|
||||
select lives_ok('truncate wh_auth_token_accumulating_fact');
|
||||
with month_range (previous) as (
|
||||
select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)')
|
||||
),
|
||||
range (active, valid) as (
|
||||
select tstzrange(upper(previous) - interval '5 minutes', upper(previous), '[]'),
|
||||
tstzrange(upper(previous) - interval '5 minutes', 'infinity'::timestamptz)
|
||||
from month_range
|
||||
),
|
||||
user_key (key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'at____atok1', user_key.key,
|
||||
wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid),
|
||||
coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid),
|
||||
wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active),
|
||||
range.active,
|
||||
range.valid,
|
||||
1
|
||||
from range, user_key;
|
||||
|
||||
select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_last_2_months',
|
||||
$$
|
||||
values (date_trunc('month', now()), date_trunc('hour', now()), 1::bigint),
|
||||
(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint)
|
||||
$$);
|
||||
|
||||
-- multiple tokens for a user within month count as single user
|
||||
select lives_ok('truncate wh_auth_token_accumulating_fact');
|
||||
with token (public_id, active) as (
|
||||
select *
|
||||
from (values
|
||||
('at____atok1', tstzrange(date_trunc('month', now() - interval '1 month'),
|
||||
date_trunc('month', now() - interval '1 month') + interval '5 minutes')),
|
||||
('at____atok2', tstzrange(date_trunc('month', now() - interval '1 month') + interval '10 minutes',
|
||||
date_trunc('month', now() - interval '1 month') + interval '15 minutes'))
|
||||
) as t (public_id, active)
|
||||
),
|
||||
user_key (key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select token.public_id, user_key.key,
|
||||
wh_date_key(lower(token.active)), wh_time_key(lower(token.active)), lower(token.active),
|
||||
coalesce(wh_date_key(upper(token.active)), -1), coalesce(wh_time_key(upper(token.active)), -1), upper(token.active),
|
||||
wh_date_key(upper(token.active)), wh_time_key(upper(token.active)), upper(token.active),
|
||||
token.active,
|
||||
token.active,
|
||||
1
|
||||
from token, user_key;
|
||||
|
||||
select is(count(*), 2::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_last_2_months',
|
||||
$$
|
||||
values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint),
|
||||
(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint)
|
||||
$$);
|
||||
|
||||
-- multiple tokens for multiple users should get counted correctly
|
||||
-- Create a user dimension for corresponding auth token's user.
|
||||
insert into wh_user_dimension (
|
||||
user_id, user_name, user_description,
|
||||
auth_account_id, auth_account_type, auth_account_name, auth_account_description,
|
||||
auth_method_id, auth_method_type, auth_method_name, auth_method_description,
|
||||
user_organization_id, user_organization_name, user_organization_description,
|
||||
current_row_indicator,
|
||||
row_effective_time, row_expiration_time,
|
||||
auth_method_external_id, auth_account_external_id, auth_account_full_name, auth_account_email
|
||||
) values (
|
||||
'u_____user2', 'None', 'None',
|
||||
'a______acc2', 'None', 'None', 'None',
|
||||
'am______am1', 'None', 'None', 'None',
|
||||
'o______org1', 'None', 'None',
|
||||
'current',
|
||||
now(), 'infinity'::timestamptz,
|
||||
'None', 'None', 'None', 'None'
|
||||
),(
|
||||
'u_____user3', 'None', 'None',
|
||||
'a______acc3', 'None', 'None', 'None',
|
||||
'am______am1', 'None', 'None', 'None',
|
||||
'o______org1', 'None', 'None',
|
||||
'current',
|
||||
now(), 'infinity'::timestamptz,
|
||||
'None', 'None', 'None', 'None'
|
||||
);
|
||||
select lives_ok('truncate wh_auth_token_accumulating_fact');
|
||||
with token (user_id, public_id, active) as (
|
||||
select *
|
||||
from (values
|
||||
('u_____user1', 'at___u1tok1', tstzrange(date_trunc('month', now() - interval '1 month'),
|
||||
date_trunc('month', now() - interval '1 month') + interval '5 minutes')),
|
||||
('u_____user1', 'at___u1tok2', tstzrange(date_trunc('month', now() - interval '1 month') + interval '10 minutes',
|
||||
date_trunc('month', now() - interval '1 month') + interval '15 minutes')),
|
||||
('u_____user2', 'at___u2tok1', tstzrange(date_trunc('month', now() - interval '1 month'),
|
||||
date_trunc('month', now() - interval '1 month') + interval '5 minutes')),
|
||||
('u_____user2', 'at___u2tok2', tstzrange(date_trunc('month', now() - interval '1 month') + interval '10 minutes',
|
||||
date_trunc('month', now() - interval '1 month') + interval '15 minutes')),
|
||||
('u_____user3', 'at___u3tok1', tstzrange(date_trunc('month', now() - interval '1 month'),
|
||||
date_trunc('month', now() - interval '1 month') + interval '5 minutes')),
|
||||
('u_____user3', 'at___u3tok2', tstzrange(date_trunc('month', now() - interval '1 month') + interval '10 minutes',
|
||||
date_trunc('month', now() - interval '1 month') + interval '15 minutes')),
|
||||
('u_____user3', 'at___u3tok3', tstzrange(date_trunc('month', now() - interval '1 month') + interval '10 minutes',
|
||||
date_trunc('month', now() - interval '1 month') + interval '15 minutes'))
|
||||
) as t (user_id, public_id, active)
|
||||
),
|
||||
token_key (user_key, public_id, active) as (
|
||||
select whud.key, token.public_id, token.active
|
||||
from wh_user_dimension as whud
|
||||
join token
|
||||
on whud.user_id = token.user_id
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select token_key.public_id, token_key.user_key,
|
||||
wh_date_key(lower(token_key.active)), wh_time_key(lower(token_key.active)), lower(token_key.active),
|
||||
coalesce(wh_date_key(upper(token_key.active)), -1), coalesce(wh_time_key(upper(token_key.active)), -1), upper(token_key.active),
|
||||
wh_date_key(upper(token_key.active)), wh_time_key(upper(token_key.active)), upper(token_key.active),
|
||||
token_key.active,
|
||||
token_key.active,
|
||||
1
|
||||
from token_key;
|
||||
|
||||
select is(count(*), 7::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 3::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_last_2_months',
|
||||
$$
|
||||
values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint),
|
||||
(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 3::bigint)
|
||||
$$);
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -0,0 +1,100 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
select plan(4);
|
||||
select lives_ok('truncate wh_auth_token_accumulating_fact,
|
||||
wh_user_dimension,
|
||||
wh_session_accumulating_fact,
|
||||
wh_session_connection_accumulating_fact;',
|
||||
'Truncate tables in preparation for testing');
|
||||
|
||||
with
|
||||
month_range (date_key, time_key, month) as (
|
||||
select wh_date_key(s), wh_time_key(s), s
|
||||
from generate_series(date_trunc('month', now()) - interval '1 year',
|
||||
date_trunc('month', now()) - interval '1 month',
|
||||
interval '1 month') as s
|
||||
),
|
||||
users (user_id, u) as (
|
||||
select 'u_____user'||u, u
|
||||
from generate_series(1, 6, 1) as u
|
||||
),
|
||||
user_key (key, user_id) as (
|
||||
insert into wh_user_dimension (
|
||||
user_id, user_name, user_description,
|
||||
auth_account_id, auth_account_type, auth_account_name, auth_account_description,
|
||||
auth_method_id, auth_method_type, auth_method_name, auth_method_description,
|
||||
user_organization_id, user_organization_name, user_organization_description,
|
||||
current_row_indicator,
|
||||
row_effective_time, row_expiration_time,
|
||||
auth_method_external_id, auth_account_external_id, auth_account_full_name, auth_account_email)
|
||||
select users.user_id, 'None', 'None',
|
||||
'a______acc1', 'None', 'None', 'None',
|
||||
'am______am1', 'None', 'None', 'None',
|
||||
'o______org1', 'None', 'None',
|
||||
'current',
|
||||
now(), 'infinity'::timestamptz,
|
||||
'None', 'None', 'None', 'None'
|
||||
from users
|
||||
returning key, user_id
|
||||
),
|
||||
tokens (date_key, time_key, user_id, token_id) as (
|
||||
select wh_date_key(s), wh_time_key(s), users.user_id, 't_____u'||users.u||'tok'||s as token_id
|
||||
from users,
|
||||
generate_series(date_trunc('month', now()) - interval '1 year',
|
||||
date_trunc('month', now()) - interval '1 month',
|
||||
interval '1 month') as s
|
||||
),
|
||||
tokens_user_keys (date_key, time_key, user_id, token_id, user_key) as (
|
||||
select tokens.date_key, tokens.time_key, tokens.user_id, tokens.token_id, user_key.key
|
||||
from tokens
|
||||
join user_key
|
||||
on user_key.user_id = tokens.user_id
|
||||
),
|
||||
auth_tokens (user_key, user_id, token_id, valid_range) as (
|
||||
select tokens_user_keys.user_key, tokens_user_keys.user_id, tokens_user_keys.token_id, tstzrange(month_range.month, month_range.month + interval '5 minutes', '[)')
|
||||
from tokens_user_keys
|
||||
join month_range
|
||||
on month_range.date_key = tokens_user_keys.date_key
|
||||
and month_range.time_key = tokens_user_keys.time_key
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select auth_tokens.token_id, auth_tokens.user_key,
|
||||
wh_date_key(lower(auth_tokens.valid_range)), wh_time_key(lower(auth_tokens.valid_range)), lower(auth_tokens.valid_range),
|
||||
coalesce(wh_date_key(upper(auth_tokens.valid_range)), -1), coalesce(wh_time_key(upper(auth_tokens.valid_range)), -1), upper(auth_tokens.valid_range),
|
||||
wh_date_key(upper(auth_tokens.valid_range)), wh_time_key(upper(auth_tokens.valid_range)), upper(auth_tokens.valid_range),
|
||||
auth_tokens.valid_range,
|
||||
auth_tokens.valid_range,
|
||||
1
|
||||
from auth_tokens;
|
||||
select is(count(*), 72::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 6::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_all',
|
||||
$$
|
||||
values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint),
|
||||
(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 6::bigint),
|
||||
(date_trunc('month', now() - interval '2 month'), date_trunc('month', now() - interval '1 month'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '3 month'), date_trunc('month', now() - interval '2 month'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '4 month'), date_trunc('month', now() - interval '3 month'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '5 month'), date_trunc('month', now() - interval '4 month'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '6 month'), date_trunc('month', now() - interval '5 month'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '7 month'), date_trunc('month', now() - interval '6 month'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '8 month'), date_trunc('month', now() - interval '7 month'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '9 month'), date_trunc('month', now() - interval '8 month'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '10 month'), date_trunc('month', now() - interval '9 month'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '11 month'), date_trunc('month', now() - interval '10 month'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '12 month'), date_trunc('month', now() - interval '11 month'), 6::bigint)
|
||||
$$);
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -0,0 +1,179 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
select plan(11);
|
||||
select lives_ok('truncate wh_auth_token_accumulating_fact,
|
||||
wh_user_dimension,
|
||||
wh_session_accumulating_fact,
|
||||
wh_session_connection_accumulating_fact;',
|
||||
'Truncate tables in preparation for testing');
|
||||
|
||||
-- generate auth tokens for users simulating the server being configured to use a different time zone.
|
||||
set time zone 'NZ';
|
||||
with
|
||||
month_range (date_key, time_key, month) as (
|
||||
select wh_date_key(s), wh_time_key(s), s
|
||||
from generate_series(date_trunc('month', now()) - interval '1 year',
|
||||
date_trunc('month', now()) - interval '1 month',
|
||||
interval '1 month') as s
|
||||
),
|
||||
users (user_id, u) as (
|
||||
select 'u_____user'||u, u
|
||||
from generate_series(1, 6, 1) as u
|
||||
),
|
||||
user_key (key, user_id) as (
|
||||
insert into wh_user_dimension (
|
||||
user_id, user_name, user_description,
|
||||
auth_account_id, auth_account_type, auth_account_name, auth_account_description,
|
||||
auth_method_id, auth_method_type, auth_method_name, auth_method_description,
|
||||
user_organization_id, user_organization_name, user_organization_description,
|
||||
current_row_indicator,
|
||||
row_effective_time, row_expiration_time,
|
||||
auth_method_external_id, auth_account_external_id, auth_account_full_name, auth_account_email)
|
||||
select users.user_id, 'None', 'None',
|
||||
'a______acc1', 'None', 'None', 'None',
|
||||
'am______am1', 'None', 'None', 'None',
|
||||
'o______org1', 'None', 'None',
|
||||
'current',
|
||||
now(), 'infinity'::timestamptz,
|
||||
'None', 'None', 'None', 'None'
|
||||
from users
|
||||
returning key, user_id
|
||||
),
|
||||
tokens (date_key, time_key, user_id, token_id) as (
|
||||
select wh_date_key(s), wh_time_key(s), users.user_id, 't_____u'||users.u||'tok'||s as token_id
|
||||
from users,
|
||||
generate_series(date_trunc('month', now()) - interval '1 year',
|
||||
date_trunc('month', now()) - interval '1 month',
|
||||
interval '1 month') as s
|
||||
),
|
||||
tokens_user_keys (date_key, time_key, user_id, token_id, user_key) as (
|
||||
select tokens.date_key, tokens.time_key, tokens.user_id, tokens.token_id, user_key.key
|
||||
from tokens
|
||||
join user_key
|
||||
on user_key.user_id = tokens.user_id
|
||||
),
|
||||
auth_tokens (user_key, user_id, token_id, valid_range) as (
|
||||
select tokens_user_keys.user_key, tokens_user_keys.user_id, tokens_user_keys.token_id, tstzrange(month_range.month, month_range.month + interval '5 minutes', '[)')
|
||||
from tokens_user_keys
|
||||
join month_range
|
||||
on month_range.date_key = tokens_user_keys.date_key
|
||||
and month_range.time_key = tokens_user_keys.time_key
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id, user_key,
|
||||
auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time,
|
||||
auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select auth_tokens.token_id, auth_tokens.user_key,
|
||||
wh_date_key(lower(auth_tokens.valid_range)), wh_time_key(lower(auth_tokens.valid_range)), lower(auth_tokens.valid_range),
|
||||
coalesce(wh_date_key(upper(auth_tokens.valid_range)), -1), coalesce(wh_time_key(upper(auth_tokens.valid_range)), -1), upper(auth_tokens.valid_range),
|
||||
wh_date_key(upper(auth_tokens.valid_range)), wh_time_key(upper(auth_tokens.valid_range)), upper(auth_tokens.valid_range),
|
||||
auth_tokens.valid_range,
|
||||
auth_tokens.valid_range,
|
||||
1
|
||||
from auth_tokens;
|
||||
select is(count(*), 72::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact;
|
||||
select is(count(*), 6::bigint, 'wh_user_dimension is not empty') from wh_user_dimension;
|
||||
|
||||
-- calling the view directly yields results at NZ month boundaries.
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_all',
|
||||
$$
|
||||
values (date_trunc('month', now(), 'nz'), date_trunc('hour', now(), 'nz'), 0::bigint),
|
||||
(date_trunc('month', now() - interval '1 month', 'nz'), date_trunc('month', now(), 'nz'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '2 month', 'nz'), date_trunc('month', now() - interval '1 month', 'nz'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '3 month', 'nz'), date_trunc('month', now() - interval '2 month', 'nz'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '4 month', 'nz'), date_trunc('month', now() - interval '3 month', 'nz'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '5 month', 'nz'), date_trunc('month', now() - interval '4 month', 'nz'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '6 month', 'nz'), date_trunc('month', now() - interval '5 month', 'nz'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '7 month', 'nz'), date_trunc('month', now() - interval '6 month', 'nz'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '8 month', 'nz'), date_trunc('month', now() - interval '7 month', 'nz'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '9 month', 'nz'), date_trunc('month', now() - interval '8 month', 'nz'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '10 month', 'nz'), date_trunc('month', now() - interval '9 month', 'nz'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '11 month', 'nz'), date_trunc('month', now() - interval '10 month', 'nz'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '12 month', 'nz'), date_trunc('month', now() - interval '11 month', 'nz'), 6::bigint)
|
||||
$$);
|
||||
|
||||
-- calling the function yields results at UTC month boundaries.
|
||||
select results_eq(
|
||||
'select count(*) from hcp_billing_monthly_active_users_all()',
|
||||
$$
|
||||
values (14::bigint);
|
||||
$$);
|
||||
select results_eq(
|
||||
'select * from hcp_billing_monthly_active_users_all()',
|
||||
$$
|
||||
values (date_trunc('month', now(), 'utc'), date_trunc('hour', now(), 'utc'), 0::bigint),
|
||||
(date_trunc('month', now() - interval '1 month', 'utc'), date_trunc('month', now(), 'utc'), 0::bigint),
|
||||
(date_trunc('month', now() - interval '2 month', 'utc'), date_trunc('month', now() - interval '1 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '3 month', 'utc'), date_trunc('month', now() - interval '2 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '4 month', 'utc'), date_trunc('month', now() - interval '3 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '5 month', 'utc'), date_trunc('month', now() - interval '4 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '6 month', 'utc'), date_trunc('month', now() - interval '5 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '7 month', 'utc'), date_trunc('month', now() - interval '6 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '8 month', 'utc'), date_trunc('month', now() - interval '7 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '9 month', 'utc'), date_trunc('month', now() - interval '8 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '10 month', 'utc'), date_trunc('month', now() - interval '9 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '11 month', 'utc'), date_trunc('month', now() - interval '10 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '12 month', 'utc'), date_trunc('month', now() - interval '11 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '13 month', 'utc'), date_trunc('month', now() - interval '12 month', 'utc'), 6::bigint)
|
||||
$$);
|
||||
select results_ne(
|
||||
'select * from hcp_billing_monthly_active_users_all()',
|
||||
'select * from hcp_billing_monthly_active_users_all');
|
||||
-- can provide a start time (inclusive) to limit the results.
|
||||
select results_eq(
|
||||
$$
|
||||
select * from hcp_billing_monthly_active_users_all(date_trunc('month', now() - interval '5 month', 'utc'));
|
||||
$$,
|
||||
$$
|
||||
values (date_trunc('month', now(), 'utc'), date_trunc('hour', now(), 'utc'), 0::bigint),
|
||||
(date_trunc('month', now() - interval '1 month', 'utc'), date_trunc('month', now(), 'utc'), 0::bigint),
|
||||
(date_trunc('month', now() - interval '2 month', 'utc'), date_trunc('month', now() - interval '1 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '3 month', 'utc'), date_trunc('month', now() - interval '2 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '4 month', 'utc'), date_trunc('month', now() - interval '3 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '5 month', 'utc'), date_trunc('month', now() - interval '4 month', 'utc'), 6::bigint)
|
||||
$$);
|
||||
-- can provide a start time (inclusive) and end time (exclusive) to limit results.
|
||||
select results_eq(
|
||||
$$
|
||||
select * from hcp_billing_monthly_active_users_all(date_trunc('month', now() - interval '5 month', 'utc'),
|
||||
date_trunc('month', now() - interval '3 month', 'utc'));
|
||||
$$,
|
||||
$$
|
||||
values (date_trunc('month', now() - interval '4 month', 'utc'), date_trunc('month', now() - interval '3 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '5 month', 'utc'), date_trunc('month', now() - interval '4 month', 'utc'), 6::bigint)
|
||||
$$);
|
||||
-- can provide an end time (exclusive) to limit results.
|
||||
select results_eq(
|
||||
$$
|
||||
select * from hcp_billing_monthly_active_users_all(null,
|
||||
date_trunc('month', now() - interval '3 month', 'utc'));
|
||||
$$,
|
||||
$$
|
||||
values (date_trunc('month', now() - interval '4 month', 'utc'), date_trunc('month', now() - interval '3 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '5 month', 'utc'), date_trunc('month', now() - interval '4 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '6 month', 'utc'), date_trunc('month', now() - interval '5 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '7 month', 'utc'), date_trunc('month', now() - interval '6 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '8 month', 'utc'), date_trunc('month', now() - interval '7 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '9 month', 'utc'), date_trunc('month', now() - interval '8 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '10 month', 'utc'), date_trunc('month', now() - interval '9 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '11 month', 'utc'), date_trunc('month', now() - interval '10 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '12 month', 'utc'), date_trunc('month', now() - interval '11 month', 'utc'), 6::bigint),
|
||||
(date_trunc('month', now() - interval '13 month', 'utc'), date_trunc('month', now() - interval '12 month', 'utc'), 6::bigint)
|
||||
$$);
|
||||
-- an end time that is before the start time will yield no results.
|
||||
select is_empty(
|
||||
$$
|
||||
select * from hcp_billing_monthly_active_users_all(date_trunc('month', now() - interval '2 month', 'utc'),
|
||||
date_trunc('month', now() - interval '5 month', 'utc'));
|
||||
$$);
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
begin;
|
||||
|
||||
select plan(13);
|
||||
select plan(10);
|
||||
|
||||
-- Verify the trigger functions exist and are declared properly
|
||||
select has_function('insert_deleted_id');
|
||||
@@ -25,17 +25,6 @@ begin;
|
||||
select is(count(*), 0::bigint) from auth_token where public_id = 'tok____clare';
|
||||
select is(count(*), 1::bigint) from auth_token_deleted;
|
||||
|
||||
-- Add new auth token with duplicate public_id, delete and check that there's no error
|
||||
insert into auth_token
|
||||
(key_id, auth_account_id, public_id, token)
|
||||
values
|
||||
('kdkv__colors', 'apa____clare', 'tok____clare', 'tok____clare'::bytea);
|
||||
prepare delete_again as
|
||||
delete from auth_token where public_id = 'tok____clare';
|
||||
select lives_ok('delete_again');
|
||||
select is(count(*), 0::bigint) from auth_token where public_id = 'tok____clare';
|
||||
select is(count(*), 1::bigint) from auth_token_deleted;
|
||||
|
||||
select * from finish();
|
||||
|
||||
rollback;
|
||||
@@ -0,0 +1,507 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
select plan(13);
|
||||
|
||||
-- Create a user dimension for corresponding auth token's user.
|
||||
insert into wh_user_dimension (
|
||||
user_id, user_name, user_description,
|
||||
auth_account_id, auth_account_type, auth_account_name, auth_account_description,
|
||||
auth_method_id, auth_method_type, auth_method_name, auth_method_description,
|
||||
user_organization_id, user_organization_name, user_organization_description,
|
||||
current_row_indicator,
|
||||
row_effective_time, row_expiration_time,
|
||||
auth_method_external_id, auth_account_external_id, auth_account_full_name, auth_account_email
|
||||
) values (
|
||||
'u_____user1', 'None', 'None',
|
||||
'a______acc1', 'None', 'None', 'None',
|
||||
'am______am1', 'None', 'None', 'None',
|
||||
'o______org1', 'None', 'None',
|
||||
'current',
|
||||
now(), 'infinity'::timestamptz,
|
||||
'None', 'None', 'None', 'None'
|
||||
);
|
||||
|
||||
|
||||
prepare insert_access_time_after_delete as
|
||||
with
|
||||
issued_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 10:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 10:00:00'::timestamptz),
|
||||
'2023-12-13 10:00:00'::timestamptz
|
||||
),
|
||||
accessed_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 12:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 12:00:00'::timestamptz),
|
||||
'2023-12-13 12:00:00'::timestamptz
|
||||
),
|
||||
deleted_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 11:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 11:00:00'::timestamptz),
|
||||
'2023-12-13 11:00:00'::timestamptz
|
||||
),
|
||||
user_dim(key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id,
|
||||
user_key,
|
||||
auth_token_issued_date_key,
|
||||
auth_token_issued_time_key,
|
||||
auth_token_issued_time,
|
||||
auth_token_deleted_date_key,
|
||||
auth_token_deleted_time_key,
|
||||
auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key,
|
||||
auth_token_approximate_last_access_time_key,
|
||||
auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'tok___token1',
|
||||
user_dim.key,
|
||||
issued_timestamp.date_dim_key,
|
||||
issued_timestamp.time_dim_key,
|
||||
issued_timestamp.ts,
|
||||
deleted_timestamp.date_dim_key,
|
||||
deleted_timestamp.time_dim_key,
|
||||
deleted_timestamp.ts,
|
||||
accessed_timestamp.date_dim_key,
|
||||
accessed_timestamp.time_dim_key,
|
||||
accessed_timestamp.ts,
|
||||
tstzrange(issued_timestamp.ts, accessed_timestamp.ts),
|
||||
tstzrange(issued_timestamp.ts, accessed_timestamp.ts),
|
||||
1
|
||||
from user_dim,
|
||||
issued_timestamp,
|
||||
deleted_timestamp,
|
||||
accessed_timestamp;
|
||||
select throws_ok(
|
||||
'insert_access_time_after_delete',
|
||||
'23514',
|
||||
'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "last_accessed_time_lte_deleted_time"'
|
||||
);
|
||||
|
||||
prepare insert_active_time_after_issued as
|
||||
with
|
||||
issued_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 10:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 10:00:00'::timestamptz),
|
||||
'2023-12-13 10:00:00'::timestamptz
|
||||
),
|
||||
accessed_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 12:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 12:00:00'::timestamptz),
|
||||
'2023-12-13 12:00:00'::timestamptz
|
||||
),
|
||||
deleted_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 13:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 13:00:00'::timestamptz),
|
||||
'2023-12-13 13:00:00'::timestamptz
|
||||
),
|
||||
user_dim(key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id,
|
||||
user_key,
|
||||
auth_token_issued_date_key,
|
||||
auth_token_issued_time_key,
|
||||
auth_token_issued_time,
|
||||
auth_token_deleted_date_key,
|
||||
auth_token_deleted_time_key,
|
||||
auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key,
|
||||
auth_token_approximate_last_access_time_key,
|
||||
auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'tok___token2',
|
||||
user_dim.key,
|
||||
issued_timestamp.date_dim_key,
|
||||
issued_timestamp.time_dim_key,
|
||||
issued_timestamp.ts,
|
||||
deleted_timestamp.date_dim_key,
|
||||
deleted_timestamp.time_dim_key,
|
||||
deleted_timestamp.ts,
|
||||
accessed_timestamp.date_dim_key,
|
||||
accessed_timestamp.time_dim_key,
|
||||
accessed_timestamp.ts,
|
||||
tstzrange(issued_timestamp.ts + interval '10 minutes', accessed_timestamp.ts),
|
||||
tstzrange(issued_timestamp.ts, deleted_timestamp.ts),
|
||||
1
|
||||
from user_dim,
|
||||
issued_timestamp,
|
||||
deleted_timestamp,
|
||||
accessed_timestamp;
|
||||
select throws_ok(
|
||||
'insert_active_time_after_issued',
|
||||
'23514',
|
||||
'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "active_time_lower_eq_issued_time"'
|
||||
);
|
||||
|
||||
prepare insert_active_time_before_access as
|
||||
with
|
||||
issued_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 10:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 10:00:00'::timestamptz),
|
||||
'2023-12-13 10:00:00'::timestamptz
|
||||
),
|
||||
accessed_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 12:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 12:00:00'::timestamptz),
|
||||
'2023-12-13 12:00:00'::timestamptz
|
||||
),
|
||||
deleted_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 13:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 13:00:00'::timestamptz),
|
||||
'2023-12-13 13:00:00'::timestamptz
|
||||
),
|
||||
user_dim(key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id,
|
||||
user_key,
|
||||
auth_token_issued_date_key,
|
||||
auth_token_issued_time_key,
|
||||
auth_token_issued_time,
|
||||
auth_token_deleted_date_key,
|
||||
auth_token_deleted_time_key,
|
||||
auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key,
|
||||
auth_token_approximate_last_access_time_key,
|
||||
auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'tok___token2',
|
||||
user_dim.key,
|
||||
issued_timestamp.date_dim_key,
|
||||
issued_timestamp.time_dim_key,
|
||||
issued_timestamp.ts,
|
||||
deleted_timestamp.date_dim_key,
|
||||
deleted_timestamp.time_dim_key,
|
||||
deleted_timestamp.ts,
|
||||
accessed_timestamp.date_dim_key,
|
||||
accessed_timestamp.time_dim_key,
|
||||
accessed_timestamp.ts,
|
||||
tstzrange(issued_timestamp.ts, accessed_timestamp.ts - interval '10 minutes'),
|
||||
tstzrange(issued_timestamp.ts, deleted_timestamp.ts),
|
||||
1
|
||||
from user_dim,
|
||||
issued_timestamp,
|
||||
deleted_timestamp,
|
||||
accessed_timestamp;
|
||||
select throws_ok(
|
||||
'insert_active_time_before_access',
|
||||
'23514',
|
||||
'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "active_time_upper_eq_last_accessed_time"'
|
||||
);
|
||||
|
||||
prepare insert_active_not_contained_by_valid as
|
||||
with
|
||||
issued_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 10:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 10:00:00'::timestamptz),
|
||||
'2023-12-13 10:00:00'::timestamptz
|
||||
),
|
||||
accessed_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 12:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 12:00:00'::timestamptz),
|
||||
'2023-12-13 12:00:00'::timestamptz
|
||||
),
|
||||
deleted_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 11:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 11:00:00'::timestamptz),
|
||||
'2023-12-13 11:00:00'::timestamptz
|
||||
),
|
||||
user_dim(key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id,
|
||||
user_key,
|
||||
auth_token_issued_date_key,
|
||||
auth_token_issued_time_key,
|
||||
auth_token_issued_time,
|
||||
auth_token_deleted_date_key,
|
||||
auth_token_deleted_time_key,
|
||||
auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key,
|
||||
auth_token_approximate_last_access_time_key,
|
||||
auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'tok___token1',
|
||||
user_dim.key,
|
||||
issued_timestamp.date_dim_key,
|
||||
issued_timestamp.time_dim_key,
|
||||
issued_timestamp.ts,
|
||||
deleted_timestamp.date_dim_key,
|
||||
deleted_timestamp.time_dim_key,
|
||||
deleted_timestamp.ts,
|
||||
accessed_timestamp.date_dim_key,
|
||||
accessed_timestamp.time_dim_key,
|
||||
accessed_timestamp.ts,
|
||||
tstzrange(issued_timestamp.ts, accessed_timestamp.ts),
|
||||
tstzrange(issued_timestamp.ts, deleted_timestamp.ts),
|
||||
1
|
||||
from user_dim,
|
||||
issued_timestamp,
|
||||
deleted_timestamp,
|
||||
accessed_timestamp;
|
||||
select throws_ok(
|
||||
'insert_active_not_contained_by_valid',
|
||||
'23514',
|
||||
'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "active_time_contained_by_valid_time"'
|
||||
);
|
||||
|
||||
prepare insert_valid_time_before_issued as
|
||||
with
|
||||
issued_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 10:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 10:00:00'::timestamptz),
|
||||
'2023-12-13 10:00:00'::timestamptz
|
||||
),
|
||||
accessed_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 12:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 12:00:00'::timestamptz),
|
||||
'2023-12-13 12:00:00'::timestamptz
|
||||
),
|
||||
deleted_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 13:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 13:00:00'::timestamptz),
|
||||
'2023-12-13 13:00:00'::timestamptz
|
||||
),
|
||||
user_dim(key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id,
|
||||
user_key,
|
||||
auth_token_issued_date_key,
|
||||
auth_token_issued_time_key,
|
||||
auth_token_issued_time,
|
||||
auth_token_deleted_date_key,
|
||||
auth_token_deleted_time_key,
|
||||
auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key,
|
||||
auth_token_approximate_last_access_time_key,
|
||||
auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'tok___token2',
|
||||
user_dim.key,
|
||||
issued_timestamp.date_dim_key,
|
||||
issued_timestamp.time_dim_key,
|
||||
issued_timestamp.ts,
|
||||
deleted_timestamp.date_dim_key,
|
||||
deleted_timestamp.time_dim_key,
|
||||
deleted_timestamp.ts,
|
||||
accessed_timestamp.date_dim_key,
|
||||
accessed_timestamp.time_dim_key,
|
||||
accessed_timestamp.ts,
|
||||
tstzrange(issued_timestamp.ts, accessed_timestamp.ts),
|
||||
tstzrange(issued_timestamp.ts - interval '10 minutes', deleted_timestamp.ts),
|
||||
1
|
||||
from user_dim,
|
||||
issued_timestamp,
|
||||
deleted_timestamp,
|
||||
accessed_timestamp;
|
||||
select throws_ok(
|
||||
'insert_valid_time_before_issued',
|
||||
'23514',
|
||||
'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "valid_time_lower_eq_issued"'
|
||||
);
|
||||
|
||||
prepare insert_valid_time_before_deleted as
|
||||
with
|
||||
issued_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 10:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 10:00:00'::timestamptz),
|
||||
'2023-12-13 10:00:00'::timestamptz
|
||||
),
|
||||
accessed_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 12:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 12:00:00'::timestamptz),
|
||||
'2023-12-13 12:00:00'::timestamptz
|
||||
),
|
||||
deleted_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 13:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 13:00:00'::timestamptz),
|
||||
'2023-12-13 13:00:00'::timestamptz
|
||||
),
|
||||
user_dim(key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id,
|
||||
user_key,
|
||||
auth_token_issued_date_key,
|
||||
auth_token_issued_time_key,
|
||||
auth_token_issued_time,
|
||||
auth_token_deleted_date_key,
|
||||
auth_token_deleted_time_key,
|
||||
auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key,
|
||||
auth_token_approximate_last_access_time_key,
|
||||
auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'tok___token2',
|
||||
user_dim.key,
|
||||
issued_timestamp.date_dim_key,
|
||||
issued_timestamp.time_dim_key,
|
||||
issued_timestamp.ts,
|
||||
deleted_timestamp.date_dim_key,
|
||||
deleted_timestamp.time_dim_key,
|
||||
deleted_timestamp.ts,
|
||||
accessed_timestamp.date_dim_key,
|
||||
accessed_timestamp.time_dim_key,
|
||||
accessed_timestamp.ts,
|
||||
tstzrange(issued_timestamp.ts, accessed_timestamp.ts),
|
||||
tstzrange(issued_timestamp.ts, deleted_timestamp.ts - interval '10 minutes'),
|
||||
1
|
||||
from user_dim,
|
||||
issued_timestamp,
|
||||
deleted_timestamp,
|
||||
accessed_timestamp;
|
||||
select throws_ok(
|
||||
'insert_valid_time_before_deleted',
|
||||
'23514',
|
||||
'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "valid_time_upper_eq_deleted"'
|
||||
);
|
||||
|
||||
prepare insert_success as
|
||||
with
|
||||
issued_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 10:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 10:00:00'::timestamptz),
|
||||
'2023-12-13 10:00:00'::timestamptz
|
||||
),
|
||||
accessed_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 12:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 12:00:00'::timestamptz),
|
||||
'2023-12-13 12:00:00'::timestamptz
|
||||
),
|
||||
deleted_timestamp(date_dim_key, time_dim_key, ts) as (
|
||||
select wh_date_key('2023-12-13 13:00:00'::timestamptz),
|
||||
wh_time_key('2023-12-13 13:00:00'::timestamptz),
|
||||
'2023-12-13 13:00:00'::timestamptz
|
||||
),
|
||||
user_dim(key) as (
|
||||
select key
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____user1'
|
||||
)
|
||||
insert into wh_auth_token_accumulating_fact (
|
||||
auth_token_id,
|
||||
user_key,
|
||||
auth_token_issued_date_key,
|
||||
auth_token_issued_time_key,
|
||||
auth_token_issued_time,
|
||||
auth_token_deleted_date_key,
|
||||
auth_token_deleted_time_key,
|
||||
auth_token_deleted_time,
|
||||
auth_token_approximate_last_access_date_key,
|
||||
auth_token_approximate_last_access_time_key,
|
||||
auth_token_approximate_last_access_time,
|
||||
auth_token_approximate_active_time_range,
|
||||
auth_token_valid_time_range,
|
||||
auth_token_count
|
||||
)
|
||||
select 'tok___token2',
|
||||
user_dim.key,
|
||||
issued_timestamp.date_dim_key,
|
||||
issued_timestamp.time_dim_key,
|
||||
issued_timestamp.ts,
|
||||
deleted_timestamp.date_dim_key,
|
||||
deleted_timestamp.time_dim_key,
|
||||
deleted_timestamp.ts,
|
||||
accessed_timestamp.date_dim_key,
|
||||
accessed_timestamp.time_dim_key,
|
||||
accessed_timestamp.ts,
|
||||
tstzrange(issued_timestamp.ts, accessed_timestamp.ts),
|
||||
tstzrange(issued_timestamp.ts, deleted_timestamp.ts),
|
||||
1
|
||||
from user_dim,
|
||||
issued_timestamp,
|
||||
deleted_timestamp,
|
||||
accessed_timestamp;
|
||||
select lives_ok('insert_success');
|
||||
-- ensure user_id was properly set
|
||||
select is(wh_auth_token_accumulating_fact.user_id, 'u_____user1')
|
||||
from wh_auth_token_accumulating_fact
|
||||
where auth_token_id = 'tok___token2';
|
||||
|
||||
prepare update_user_id as
|
||||
update wh_auth_token_accumulating_fact set user_id = 'u_____user2'
|
||||
where auth_token_id = 'tok___token2';
|
||||
select throws_ok(
|
||||
'update_user_id',
|
||||
'23601',
|
||||
'immutable column: wh_auth_token_accumulating_fact.user_id'
|
||||
);
|
||||
|
||||
prepare update_user_key as
|
||||
update wh_auth_token_accumulating_fact set user_key = 'key_2'
|
||||
where auth_token_id = 'tok___token2';
|
||||
select throws_ok(
|
||||
'update_user_key',
|
||||
'23601',
|
||||
'immutable column: wh_auth_token_accumulating_fact.user_key'
|
||||
);
|
||||
|
||||
prepare update_auth_token_issued_time as
|
||||
update wh_auth_token_accumulating_fact set auth_token_issued_time = '2023-12-13 11:01:00'::timestamptz
|
||||
where auth_token_id = 'tok___token2';
|
||||
select throws_ok(
|
||||
'update_auth_token_issued_time',
|
||||
'23601',
|
||||
'immutable column: wh_auth_token_accumulating_fact.auth_token_issued_time'
|
||||
);
|
||||
|
||||
prepare update_auth_token_issued_time_key as
|
||||
update wh_auth_token_accumulating_fact set auth_token_issued_time_key = wh_time_key('2023-12-13 11:01:00'::timestamptz)
|
||||
where auth_token_id = 'tok___token2';
|
||||
select throws_ok(
|
||||
'update_auth_token_issued_time_key',
|
||||
'23601',
|
||||
'immutable column: wh_auth_token_accumulating_fact.auth_token_issued_time_key'
|
||||
);
|
||||
|
||||
prepare update_auth_token_issued_date_key as
|
||||
update wh_auth_token_accumulating_fact set auth_token_issued_date_key = wh_date_key('2023-12-14 11:00:00'::timestamptz)
|
||||
where auth_token_id = 'tok___token2';
|
||||
select throws_ok(
|
||||
'update_auth_token_issued_date_key',
|
||||
'23601',
|
||||
'immutable column: wh_auth_token_accumulating_fact.auth_token_issued_date_key'
|
||||
);
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -0,0 +1,75 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
select plan(14);
|
||||
|
||||
-- should have an auth token for carly
|
||||
select is(count(*), 1::bigint)
|
||||
from auth_token
|
||||
where public_id = 'tok____carly';
|
||||
|
||||
-- should have one fact for carly.
|
||||
select is(count(*), 1::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
-- the fact should not have a deleted time.
|
||||
select is(auth_token_deleted_date_key, -1)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select is(auth_token_deleted_time_key, -1)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select is(auth_token_deleted_time, 'infinity'::wh_timestamp)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select is(upper(auth_token_valid_time_range), 'infinity'::timestamptz)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
|
||||
-- Now delete the auth token and confirm the fact was updated properly.
|
||||
delete
|
||||
from auth_token
|
||||
where public_id = 'tok____carly';
|
||||
|
||||
-- should still have one fact
|
||||
select is(count(*), 1::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
-- times should be updated to have a deleted time.
|
||||
select is(auth_token_deleted_date_key, wh_date_key(now()))
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select is(auth_token_deleted_time_key, wh_time_key(now()))
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select is(auth_token_deleted_time, now()::wh_timestamp)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select is(upper(auth_token_valid_time_range), now())
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
|
||||
-- should have an auth token for cora that is in pending status
|
||||
select is(count(*), 1::bigint)
|
||||
from auth_token
|
||||
where public_id = 'tok_____cora'
|
||||
and status = 'auth token pending';
|
||||
|
||||
-- since the auth token is pending, there should be no fact for cora.
|
||||
select is(count(*), 0::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u_______cora';
|
||||
|
||||
-- Now delete the pending auth token, this should not result in a fact.
|
||||
delete
|
||||
from auth_token
|
||||
where public_id = 'tok_____cora';
|
||||
|
||||
select is(count(*), 0::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u_______cora';
|
||||
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -0,0 +1,81 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
select plan(4);
|
||||
|
||||
select wtt_load('widgets', 'iam', 'kms');
|
||||
|
||||
-- Create a user dimension for corresponding auth token's user.
|
||||
insert into wh_user_dimension (
|
||||
user_id, user_name, user_description,
|
||||
auth_account_id, auth_account_type, auth_account_name, auth_account_description,
|
||||
auth_method_id, auth_method_type, auth_method_name, auth_method_description,
|
||||
user_organization_id, user_organization_name, user_organization_description,
|
||||
current_row_indicator,
|
||||
row_effective_time, row_expiration_time,
|
||||
auth_method_external_id, auth_account_external_id, auth_account_full_name, auth_account_email
|
||||
) values (
|
||||
'u_____user1', 'None', 'None',
|
||||
'a______acc1', 'None', 'None', 'None',
|
||||
'am______am1', 'None', 'None', 'None',
|
||||
'o______org1', 'None', 'None',
|
||||
'current',
|
||||
now(), 'infinity'::timestamptz,
|
||||
'None', 'None', 'None', 'None'
|
||||
);
|
||||
|
||||
-- Create auth method and account to use for making auth tokens.
|
||||
insert into auth_password_conf
|
||||
(password_method_id, private_id)
|
||||
values
|
||||
('apm___widget', 'apmc__widget');
|
||||
insert into auth_password_method
|
||||
(scope_id, public_id, password_conf_id, name)
|
||||
values
|
||||
('o_____widget', 'apm___widget', 'apmc__widget', 'Widget Auth Password');
|
||||
insert into auth_password_account
|
||||
(auth_method_id, public_id, login_name)
|
||||
values
|
||||
('apm___widget', 'apa___walter', 'walter');
|
||||
update auth_account set iam_user_id = 'u_____walter' where public_id = 'apa___walter';
|
||||
|
||||
-- should start with no facts for this user.
|
||||
select is(count(*), 0::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u_____walter';
|
||||
|
||||
-- Insert with status 'auth token pending' should not create a auth token fact.
|
||||
insert into auth_token
|
||||
(key_id, auth_account_id, public_id, token, status)
|
||||
values
|
||||
('kdkv___widget', 'apa___walter', 'tok___walter1', 'tok___walter1'::bytea, 'auth token pending');
|
||||
select is(count(*), 0::bigint) from wh_auth_token_accumulating_fact where user_id = 'u_____walter';
|
||||
|
||||
-- Insert an auth token wuth the 'token issued' status.
|
||||
insert into auth_token
|
||||
(key_id, auth_account_id, public_id, token, status)
|
||||
values
|
||||
('kdkv___widget', 'apa___walter', 'tok___walter2', 'tok___walter2'::bytea, 'token issued');
|
||||
|
||||
-- should have one fact for this user.
|
||||
select is(count(*), 1::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u_____walter';
|
||||
select is(
|
||||
wh_auth_token_accumulating_fact.*,
|
||||
row('tok___walter2',
|
||||
'u_____walter', (select key from wh_user_dimension where user_id = 'u_____walter'),
|
||||
wh_date_key(now()), wh_time_key(now()), now(),
|
||||
-1, -1, 'infinity'::timestamptz,
|
||||
wh_date_key(now()), wh_time_key(now()), now(),
|
||||
tstzrange(now(), now(), '[]'),
|
||||
tstzrange(now(), 'infinity'::timestamptz, '[]'),
|
||||
1
|
||||
)::wh_auth_token_accumulating_fact
|
||||
)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u_____walter';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -0,0 +1,114 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
select plan(20);
|
||||
|
||||
-- should have an auth token for carly
|
||||
select is(count(*), 1::bigint)
|
||||
from auth_token
|
||||
where public_id = 'tok____carly';
|
||||
|
||||
-- should have one fact for carly.
|
||||
select is(count(*), 1::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
-- the fact should have an access time that is less than now.
|
||||
select cmp_ok(auth_token_approximate_last_access_date_key, '<=', wh_date_key(now()))
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select cmp_ok(auth_token_approximate_last_access_time_key, '<', wh_time_key(now()))
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select cmp_ok(auth_token_approximate_last_access_time, '<', now()::wh_timestamp)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select cmp_ok(lower(auth_token_approximate_active_time_range), '<', now())
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select cmp_ok(upper(auth_token_approximate_active_time_range), '<', now())
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
|
||||
-- update access time for carly's auth token.
|
||||
update auth_token
|
||||
set approximate_last_access_time = now()
|
||||
where public_id = 'tok____carly';
|
||||
|
||||
-- should still only have one fact for carly.
|
||||
select is(count(*), 1::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
-- the fact should have an access time of now
|
||||
select is(auth_token_approximate_last_access_date_key, wh_date_key(now()))
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select is(auth_token_approximate_last_access_time_key, wh_time_key(now()))
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select is(auth_token_approximate_last_access_time, now()::wh_timestamp)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select cmp_ok(lower(auth_token_approximate_active_time_range), '<', now())
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
select is(upper(auth_token_approximate_active_time_range), now())
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______carly';
|
||||
|
||||
-- should have an auth token for cora that is in pending status
|
||||
select is(count(*), 1::bigint)
|
||||
from auth_token
|
||||
where public_id = 'tok_____cora'
|
||||
and status = 'auth token pending';
|
||||
|
||||
-- since the auth token is pending, there should be no fact for cora.
|
||||
select is(count(*), 0::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u_______cora';
|
||||
|
||||
-- updating a pending token to issued should result in a fact for cora.
|
||||
update auth_token
|
||||
set status = 'token issued'
|
||||
where public_id = 'tok_____cora';
|
||||
|
||||
select is(count(*), 1::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u_______cora';
|
||||
select is(
|
||||
wh_auth_token_accumulating_fact.*,
|
||||
row('tok_____cora',
|
||||
'u_______cora', (select key from wh_user_dimension where user_id = 'u_______cora'),
|
||||
wh_date_key(now()), wh_time_key(now()), now(),
|
||||
-1, -1, 'infinity'::timestamptz,
|
||||
wh_date_key(now()), wh_time_key(now()), now(),
|
||||
tstzrange(now(), now(), '[]'),
|
||||
tstzrange(now(), 'infinity'::timestamptz, '[]'),
|
||||
1
|
||||
)::wh_auth_token_accumulating_fact
|
||||
)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u_______cora';
|
||||
|
||||
-- should have an auth token for ciara that is in pending status
|
||||
select is(count(*), 1::bigint)
|
||||
from auth_token
|
||||
where public_id = 'tok____ciara'
|
||||
and status = 'auth token pending';
|
||||
|
||||
-- since the auth token is pending, there should be no fact for ciara.
|
||||
select is(count(*), 0::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______ciara';
|
||||
|
||||
-- updating a pending token to something other than issued should not result in a fact.
|
||||
update auth_token
|
||||
set status = 'authentication failed'
|
||||
where public_id = 'tok____ciara';
|
||||
|
||||
select is(count(*), 0::bigint)
|
||||
from wh_auth_token_accumulating_fact
|
||||
where user_id = 'u______ciara';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -9,12 +9,12 @@ begin;
|
||||
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets');
|
||||
|
||||
-- ensure no existing dimensions
|
||||
select is(count(*), 0::bigint) from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(count(*), 0::bigint) from wh_user_dimension where user_id = 'u_____wilson';
|
||||
|
||||
select lives_ok($$select wh_upsert_user('u_____walter', 'tok___walter')$$);
|
||||
select lives_ok($$select wh_upsert_user('tok___wilson')$$);
|
||||
|
||||
-- upsert should insert a user_dimension
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____wilson';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -9,7 +9,7 @@ begin;
|
||||
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets');
|
||||
|
||||
-- ensure no existing dimensions
|
||||
select is(count(*), 0::bigint) from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(count(*), 0::bigint) from wh_user_dimension where user_id = 'u_____wilson';
|
||||
|
||||
insert into wh_user_dimension
|
||||
(
|
||||
@@ -25,8 +25,8 @@ begin;
|
||||
values
|
||||
(
|
||||
'wud_____1',
|
||||
'u_____walter', 'Walter', 'This is Walter',
|
||||
'apa___walter', 'password auth account', 'walter', 'Account for Walter',
|
||||
'u_____wilson', 'Wilson', 'This is Wilson',
|
||||
'apa___wilson', 'password auth account', 'wilson', 'Account for Wilson',
|
||||
'None', 'None', 'None',
|
||||
'apm___widget', 'password auth method', 'Widget Auth Password', 'None',
|
||||
'None',
|
||||
@@ -34,11 +34,11 @@ begin;
|
||||
'Current', '2021-07-21T12:01'::timestamptz, 'infinity'::timestamptz
|
||||
);
|
||||
|
||||
select lives_ok($$select wh_upsert_user('u_____walter', 'tok___walter')$$);
|
||||
select lives_ok($$select wh_upsert_user('tok___wilson')$$);
|
||||
|
||||
-- upsert should insert a user_dimension
|
||||
select is(count(*), 2::bigint) from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(count(*), 2::bigint) from wh_user_dimension where user_id = 'u_____wilson';
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____wilson' and current_row_indicator = 'Current';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -1,15 +1,23 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
-- oidc_auth_new_session tests the wh_user_dimesion when
|
||||
-- a new session is created using the oidc auth method.
|
||||
-- ldap_auth_new_session tests the wh_user_dimesion when
|
||||
-- a new session is created using the ldap auth method.
|
||||
begin;
|
||||
select plan(40);
|
||||
select plan(41);
|
||||
|
||||
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets');
|
||||
|
||||
-- ensure no existing dimensions
|
||||
select is(count(*), 0::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
-- existing dimension from auth tokens
|
||||
select is(count(*), 2::bigint)
|
||||
from wh_user_dimension
|
||||
where user_id in ('u_____walter', 'u_____warren')
|
||||
and auth_method_id = 'alm___widget';
|
||||
|
||||
-- update walter and warren so the user dimension is changed when the session is inserted
|
||||
update iam_user
|
||||
set description = 'LDAP Auth New Session'
|
||||
where public_id in ('u_____walter', 'u_____warren');
|
||||
|
||||
-- insert first session, should result in a new user dimension
|
||||
insert into session
|
||||
@@ -21,30 +29,43 @@ begin;
|
||||
values
|
||||
('s1____walter', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_id, 'u_____walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
-- inserting the session should result in a new user dimension.
|
||||
select is(count(*), 1::bigint)
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____walter'
|
||||
and auth_method_id = 'alm___widget'
|
||||
and user_description = 'LDAP Auth New Session';
|
||||
|
||||
select is(auth_account_id, 'ala___walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_type, 'ldap auth account') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_name, 'walter account') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_description, 'Walter LDAP Account') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_external_id, 'walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_full_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_email, 'walter@widget.test') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(user_id, 'u_____walter') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(user_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(user_description, 'LDAP Auth New Session') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
|
||||
select is(auth_method_id, 'alm___widget') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_type, 'ldap auth method') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_name, 'Widget LDAP') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_id, 'ala___walter') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_type, 'ldap auth account') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_name, 'walter account') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_description, 'Walter LDAP Account') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_external_id, 'walter') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_full_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_email, 'walter@widget.test') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_id, 'alm___widget') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_method_type, 'ldap auth method') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_method_name, 'Widget LDAP') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_method_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____walter' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
|
||||
select is(session_fact.user_key, user_dim.key)
|
||||
from wh_session_connection_accumulating_fact as session_fact,
|
||||
wh_user_dimension as user_dim
|
||||
where session_fact.session_id = 's1____walter'
|
||||
and user_dim.user_id = 'u_____walter';
|
||||
|
||||
-- insert session without full name or email
|
||||
insert into session
|
||||
@@ -56,29 +77,29 @@ begin;
|
||||
values
|
||||
('s1____warren', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(user_name, 'Warren') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(user_description, 'None') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(user_name, 'Warren') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(user_description, 'LDAP Auth New Session') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
|
||||
select is(auth_account_id, 'ala___warren') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_type, 'ldap auth account') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_name, 'warren account') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_description, 'Warren LDAP Account') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_external_id, 'warren') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_full_name, 'None') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_email, 'None') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_id, 'ala___warren') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_type, 'ldap auth account') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_name, 'warren account') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_description, 'Warren LDAP Account') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_external_id, 'warren') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_full_name, 'None') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_account_email, 'None') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
|
||||
select is(auth_method_id, 'alm___widget') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_method_type, 'ldap auth method') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_method_name, 'Widget LDAP') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_method_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_method_id, 'alm___widget') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_method_type, 'ldap auth method') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_method_name, 'Widget LDAP') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(auth_method_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____warren' and auth_method_id = 'alm___widget' and user_description = 'LDAP Auth New Session';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -8,8 +8,16 @@ begin;
|
||||
|
||||
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets');
|
||||
|
||||
-- ensure no existing dimensions
|
||||
select is(count(*), 0::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
-- existing dimension from auth tokens
|
||||
select is(count(*), 2::bigint)
|
||||
from wh_user_dimension
|
||||
where user_id in ('u_____walter', 'u_____warren')
|
||||
and auth_method_id = 'aom___widget';
|
||||
|
||||
-- update walter and warren so the user dimension is changed when the session is inserted
|
||||
update iam_user
|
||||
set description = 'OIDC Auth New Session'
|
||||
where public_id in ('u_____walter', 'u_____warren');
|
||||
|
||||
-- insert first session, should result in a new user dimension
|
||||
insert into session
|
||||
@@ -21,30 +29,30 @@ begin;
|
||||
values
|
||||
('s1____walter', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_id, 'u_____walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(user_id, 'u_____walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(user_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(user_description, 'OIDC Auth New Session') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
|
||||
select is(auth_account_id, 'aoa___walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_type, 'oidc auth account') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_name, 'walter account') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_description, 'Walter OIDC Account') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_external_id, 'sub___walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_full_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_email, 'walter@widget.test') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_id, 'aoa___walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_type, 'oidc auth account') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_name, 'walter account') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_description, 'Walter OIDC Account') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_external_id, 'sub___walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_full_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_email, 'walter@widget.test') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
|
||||
select is(auth_method_id, 'aom___widget') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_type, 'oidc auth method') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_name, 'Widget OIDC') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_external_id, 'https://oidc.widget.test') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_id, 'aom___widget') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_method_type, 'oidc auth method') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_method_name, 'Widget OIDC') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_method_external_id, 'https://oidc.widget.test') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'OIDC Auth New Session';
|
||||
|
||||
-- insert session without full name or email
|
||||
insert into session
|
||||
@@ -56,29 +64,29 @@ begin;
|
||||
values
|
||||
('s1____warren', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(user_name, 'Warren') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(user_description, 'None') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(user_name, 'Warren') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(user_description, 'OIDC Auth New Session') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
|
||||
select is(auth_account_id, 'aoa___warren') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_type, 'oidc auth account') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_name, 'warren account') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_description, 'Warren OIDC Account') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_external_id, 'sub___warren') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_full_name, 'None') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_email, 'None') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_account_id, 'aoa___warren') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_type, 'oidc auth account') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_name, 'warren account') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_description, 'Warren OIDC Account') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_external_id, 'sub___warren') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_full_name, 'None') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_account_email, 'None') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
|
||||
select is(auth_method_id, 'aom___widget') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_method_type, 'oidc auth method') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_method_name, 'Widget OIDC') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_method_external_id, 'https://oidc.widget.test') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(auth_method_id, 'aom___widget') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_method_type, 'oidc auth method') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_method_name, 'Widget OIDC') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(auth_method_external_id, 'https://oidc.widget.test') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____warren';
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____warren' and user_description = 'OIDC Auth New Session';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -5,12 +5,20 @@
|
||||
-- sessions are created using the password auth method
|
||||
-- after the auth_password_account has its description change.
|
||||
begin;
|
||||
select plan(40);
|
||||
select plan(41);
|
||||
|
||||
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets');
|
||||
|
||||
-- ensure no existing dimensions
|
||||
select is(count(*), 0::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
-- existing dimension from auth tokens
|
||||
select is(count(*), 1::bigint)
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____walter'
|
||||
and auth_method_id = 'apm___widget';
|
||||
|
||||
-- update walter so the user dimension is changed when the session is inserted
|
||||
update iam_user
|
||||
set description = 'Passwd Change Description'
|
||||
where public_id = 'u_____walter';
|
||||
|
||||
-- insert first session, should result in a new user dimension
|
||||
insert into session
|
||||
@@ -22,36 +30,42 @@ begin;
|
||||
values
|
||||
('s1____walter', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_id, 'u_____walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
-- inserting the session should result in a new user dimension.
|
||||
select is(count(*), 1::bigint)
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____walter'
|
||||
and auth_method_id = 'apm___widget'
|
||||
and user_description = 'Passwd Change Description';
|
||||
|
||||
select is(auth_account_id, 'apa___walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_type, 'password auth account') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_name, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_full_name, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_email, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(user_id, 'u_____walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(user_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(user_description, 'Passwd Change Description') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
|
||||
select is(auth_method_id, 'apm___widget') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_type, 'password auth method') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_name, 'Widget Auth Password') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_id, 'apa___walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(auth_account_type, 'password auth account') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(auth_account_name, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(auth_account_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(auth_account_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(auth_account_full_name, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(auth_account_email, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_id, 'apm___widget') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(auth_method_type, 'password auth method') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(auth_method_name, 'Widget Auth Password') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(auth_method_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description';
|
||||
|
||||
-- change auth description
|
||||
update auth_password_account set
|
||||
description = 'Walter Password Account'
|
||||
where
|
||||
public_id = 'apa___walter';
|
||||
update auth_password_account
|
||||
set description = 'Walter Password Account'
|
||||
where public_id = 'apa___walter';
|
||||
|
||||
-- another session with:
|
||||
-- * same user
|
||||
@@ -67,29 +81,33 @@ begin;
|
||||
values
|
||||
('s2____walter', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 2::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
select is(count(*), 2::bigint)
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____walter'
|
||||
and auth_method_id = 'apm___widget'
|
||||
and user_description = 'Passwd Change Description';
|
||||
|
||||
select is(user_id, 'u_____walter') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(user_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(user_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(user_id, 'u_____walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(user_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(user_description, 'Passwd Change Description') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
|
||||
select is(auth_account_id, 'apa___walter') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_account_type, 'password auth account') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_account_name, 'None') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_account_description, 'Walter Password Account') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_account_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_account_full_name, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_account_email, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_account_id, 'apa___walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(auth_account_type, 'password auth account') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(auth_account_name, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(auth_account_description, 'Walter Password Account') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(auth_account_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(auth_account_full_name, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(auth_account_email, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
|
||||
select is(auth_method_id, 'apm___widget') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_method_type, 'password auth method') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_method_name, 'Widget Auth Password') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_method_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(auth_method_id, 'apm___widget') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(auth_method_type, 'password auth method') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(auth_method_name, 'Widget Auth Password') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(auth_method_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and current_row_indicator = 'Current';
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Change Description' and current_row_indicator = 'Current';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -4,12 +4,20 @@
|
||||
-- password_auth_new_session tests the wh_user_dimesion when
|
||||
-- a new session is created using the password auth method.
|
||||
begin;
|
||||
select plan(21);
|
||||
select plan(22);
|
||||
|
||||
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets');
|
||||
|
||||
-- ensure no existing dimensions
|
||||
select is(count(*), 0::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
-- existing dimensions from auth tokens
|
||||
select is(count(*), 1::bigint)
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____walter'
|
||||
and auth_method_id = 'apm___widget';
|
||||
|
||||
-- update walter so the user dimension is changed when the session is inserted
|
||||
update iam_user
|
||||
set description = 'Passwd Auth New Session'
|
||||
where public_id = 'u_____walter';
|
||||
|
||||
-- insert first session, should result in a new user dimension
|
||||
insert into session
|
||||
@@ -21,30 +29,37 @@ begin;
|
||||
values
|
||||
('s1____walter', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_id, 'u_____walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
-- inserting the session should result in a new user dimension.
|
||||
select is(count(*), 1::bigint)
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____walter'
|
||||
and auth_method_id = 'apm___widget'
|
||||
and user_description = 'Passwd Auth New Session';
|
||||
|
||||
select is(auth_account_id, 'apa___walter') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_type, 'password auth account') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_name, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_full_name, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_email, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(user_id, 'u_____walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(user_name, 'Walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(user_description, 'Passwd Auth New Session') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
|
||||
select is(auth_method_id, 'apm___widget') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_type, 'password auth method') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_name, 'Widget Auth Password') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_account_id, 'apa___walter') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(auth_account_type, 'password auth account') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(auth_account_name, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(auth_account_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(auth_account_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(auth_account_full_name, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(auth_account_email, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(auth_method_id, 'apm___widget') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(auth_method_type, 'password auth method') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(auth_method_name, 'Widget Auth Password') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(auth_method_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(auth_method_external_id, 'Not Applicable') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____walter';
|
||||
select is(user_organization_id, 'o_____widget') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(user_organization_name, 'Widget Inc') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
select is(user_organization_description, 'None') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
|
||||
select is(current_row_indicator, 'Current') from wh_user_dimension where user_id = 'u_____walter' and user_description = 'Passwd Auth New Session';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -8,8 +8,8 @@ begin;
|
||||
|
||||
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets');
|
||||
|
||||
-- ensure no existing dimensions
|
||||
select is(count(*), 0::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
-- existing dimensions from auth tokens
|
||||
select is(count(*), 8::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
|
||||
-- insert first session, should result in a new user dimension
|
||||
insert into session
|
||||
@@ -21,7 +21,7 @@ begin;
|
||||
values
|
||||
('s1____walter', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
select is(count(*), 8::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
|
||||
-- another session with:
|
||||
-- * same user
|
||||
@@ -37,7 +37,7 @@ begin;
|
||||
values
|
||||
('s2____walter', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
select is(count(*), 8::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
|
||||
-- another session with:
|
||||
-- * same user
|
||||
@@ -53,7 +53,7 @@ begin;
|
||||
values
|
||||
('s3____walter', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
select is(count(*), 8::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
+4
-4
@@ -9,8 +9,8 @@ begin;
|
||||
|
||||
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets');
|
||||
|
||||
-- ensure no existing dimensions
|
||||
select is(count(*), 0::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
-- existing dimensions from auth tokens
|
||||
select is(count(*), 8::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
|
||||
-- insert first session, should result in a new user dimension
|
||||
insert into session
|
||||
@@ -22,7 +22,7 @@ begin;
|
||||
values
|
||||
('s1____walter', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
select is(count(*), 8::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
|
||||
-- another session with:
|
||||
-- * same user
|
||||
@@ -38,7 +38,7 @@ begin;
|
||||
values
|
||||
('s4____walter', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 2::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
select is(count(*), 8::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_id = 'u_____walter' and auth_account_id = 'apa1__walter';
|
||||
|
||||
select is(user_id, 'u_____walter') from wh_user_dimension where user_id = 'u_____walter' and auth_account_id = 'apa1__walter';
|
||||
|
||||
@@ -8,8 +8,10 @@ begin;
|
||||
|
||||
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets');
|
||||
|
||||
-- ensure no existing dimensions
|
||||
select is(count(*), 0::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
-- existing dimension via auth tokens
|
||||
select is(count(*), 4::bigint)
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____walter';
|
||||
|
||||
-- insert first session, should result in a new user dimension
|
||||
insert into session
|
||||
@@ -21,7 +23,9 @@ begin;
|
||||
values
|
||||
('s1____walter', 's___1wb-sths', 'h_____wb__01');
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
select is(count(*), 4::bigint)
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____walter';
|
||||
|
||||
-- update session, should not impact wh_user_dimension
|
||||
update session set
|
||||
@@ -29,7 +33,9 @@ begin;
|
||||
where
|
||||
public_id = 's1____walter';
|
||||
|
||||
select is(count(*), 1::bigint) from wh_user_dimension where user_organization_id = 'o_____widget';
|
||||
select is(count(*), 4::bigint)
|
||||
from wh_user_dimension
|
||||
where user_id = 'u_____walter';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -37,10 +37,16 @@ func (ts *Timestamp) Scan(value any) error {
|
||||
|
||||
// Scan implements driver.Valuer for protobuf Timestamp.
|
||||
func (ts *Timestamp) Value() (driver.Value, error) {
|
||||
if ts == nil {
|
||||
switch {
|
||||
case ts == nil:
|
||||
return nil, nil
|
||||
case ts.AsTime().Equal(NegativeInfinityTS):
|
||||
return "-infinity", nil
|
||||
case ts.AsTime().Equal(PositiveInfinityTS):
|
||||
return "infinity", nil
|
||||
default:
|
||||
return ts.Timestamp.AsTime(), nil
|
||||
}
|
||||
return ts.Timestamp.AsTime(), nil
|
||||
}
|
||||
|
||||
// GormDataType gorm common data type (required)
|
||||
|
||||
@@ -33,6 +33,18 @@ func Test_TimestampValue(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, v, utcDate(10000, 1, 1))
|
||||
})
|
||||
t.Run("negativeInfinity", func(t *testing.T) {
|
||||
ts := New(NegativeInfinityTS)
|
||||
v, err := ts.Value()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, v, "-infinity")
|
||||
})
|
||||
t.Run("positiveInfinity", func(t *testing.T) {
|
||||
ts := New(PositiveInfinityTS)
|
||||
v, err := ts.Value()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, v, "infinity")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_TimestampScan(t *testing.T) {
|
||||
@@ -65,6 +77,20 @@ func Test_TimestampScan(t *testing.T) {
|
||||
err := ts.Scan(v)
|
||||
require.Nil(err)
|
||||
})
|
||||
t.Run("negativeInfinity", func(t *testing.T) {
|
||||
v := "-infinity"
|
||||
ts := Timestamp{}
|
||||
err := ts.Scan(v)
|
||||
require.NoError(err)
|
||||
assert.Equal(ts.AsTime(), NegativeInfinityTS)
|
||||
})
|
||||
t.Run("positiveInfinity", func(t *testing.T) {
|
||||
v := "infinity"
|
||||
ts := Timestamp{}
|
||||
err := ts.Scan(v)
|
||||
require.NoError(err)
|
||||
assert.Equal(ts.AsTime(), PositiveInfinityTS)
|
||||
})
|
||||
}
|
||||
|
||||
const maxValidSeconds = 253402300800
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
{
|
||||
"name": "controller.api.services.v1.AuthTokenService"
|
||||
},
|
||||
{
|
||||
"name": "controller.api.services.v1.BillingService"
|
||||
},
|
||||
{
|
||||
"name": "controller.api.services.v1.CredentialLibraryService"
|
||||
},
|
||||
@@ -635,6 +638,39 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/billing:monthly-active-users": {
|
||||
"get": {
|
||||
"summary": "Returns monthly active users.",
|
||||
"operationId": "BillingService_MonthlyActiveUsers",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.api.services.v1.MonthlyActiveUsersResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "start_time",
|
||||
"description": "An optional start time of the billing period to query, in the format of YYYY-MM.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "end_time",
|
||||
"description": "An optional end time of the billing period to query, in the format of YYYY-MM.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"controller.api.services.v1.BillingService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/credential-libraries": {
|
||||
"get": {
|
||||
"summary": "Lists all Credential Library.",
|
||||
@@ -4754,6 +4790,29 @@
|
||||
},
|
||||
"title": "AuthToken contains all fields related to an Auth Token resource"
|
||||
},
|
||||
"controller.api.resources.billing.v1.ActiveUsers": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Output only. The number of active users between the start time and end time.",
|
||||
"readOnly": true
|
||||
},
|
||||
"start_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Output only. The start time of the active users count, inclusive.",
|
||||
"readOnly": true
|
||||
},
|
||||
"end_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Output only. The end time of the active users count, exclusive.",
|
||||
"readOnly": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.api.resources.credentiallibraries.v1.CredentialLibrary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -8776,6 +8835,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.api.services.v1.MonthlyActiveUsersResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/controller.api.resources.billing.v1.ActiveUsers"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.api.services.v1.ReApplyStoragePolicyResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.32.0
|
||||
// protoc (unknown)
|
||||
// source: controller/api/services/v1/billing_service.proto
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
|
||||
billing "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/billing"
|
||||
_ "google.golang.org/genproto/googleapis/api/annotations"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type MonthlyActiveUsersRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// An optional start time of the billing period to query, in the format of YYYY-MM.
|
||||
StartTime string `protobuf:"bytes,1,opt,name=start_time,proto3" json:"start_time,omitempty" class:"public"` // @gotags: class:"public"
|
||||
// An optional end time of the billing period to query, in the format of YYYY-MM.
|
||||
EndTime string `protobuf:"bytes,2,opt,name=end_time,proto3" json:"end_time,omitempty" class:"public"` // @gotags: class:"public"
|
||||
}
|
||||
|
||||
func (x *MonthlyActiveUsersRequest) Reset() {
|
||||
*x = MonthlyActiveUsersRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_services_v1_billing_service_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MonthlyActiveUsersRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MonthlyActiveUsersRequest) ProtoMessage() {}
|
||||
|
||||
func (x *MonthlyActiveUsersRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_services_v1_billing_service_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use MonthlyActiveUsersRequest.ProtoReflect.Descriptor instead.
|
||||
func (*MonthlyActiveUsersRequest) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_services_v1_billing_service_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *MonthlyActiveUsersRequest) GetStartTime() string {
|
||||
if x != nil {
|
||||
return x.StartTime
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *MonthlyActiveUsersRequest) GetEndTime() string {
|
||||
if x != nil {
|
||||
return x.EndTime
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type MonthlyActiveUsersResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Items []*billing.ActiveUsers `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MonthlyActiveUsersResponse) Reset() {
|
||||
*x = MonthlyActiveUsersResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_services_v1_billing_service_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MonthlyActiveUsersResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MonthlyActiveUsersResponse) ProtoMessage() {}
|
||||
|
||||
func (x *MonthlyActiveUsersResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_services_v1_billing_service_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use MonthlyActiveUsersResponse.ProtoReflect.Descriptor instead.
|
||||
func (*MonthlyActiveUsersResponse) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_services_v1_billing_service_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *MonthlyActiveUsersResponse) GetItems() []*billing.ActiveUsers {
|
||||
if x != nil {
|
||||
return x.Items
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_controller_api_services_v1_billing_service_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_api_services_v1_billing_service_proto_rawDesc = []byte{
|
||||
0x0a, 0x30, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69,
|
||||
0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x69, 0x6c,
|
||||
0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x12, 0x1a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61,
|
||||
0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x31,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72,
|
||||
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67,
|
||||
0x2f, 0x76, 0x31, 0x2f, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e,
|
||||
0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e,
|
||||
0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e,
|
||||
0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
||||
0x57, 0x0a, 0x19, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65,
|
||||
0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a,
|
||||
0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08,
|
||||
0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
|
||||
0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x64, 0x0a, 0x1a, 0x4d, 0x6f, 0x6e, 0x74,
|
||||
0x68, 0x6c, 0x79, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18,
|
||||
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
|
||||
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
|
||||
0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x74, 0x69,
|
||||
0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x32, 0xe2,
|
||||
0x01, 0x0a, 0x0e, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x12, 0xcf, 0x01, 0x0a, 0x12, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x41, 0x63, 0x74,
|
||||
0x69, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x41, 0x63, 0x74,
|
||||
0x69, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x6e,
|
||||
0x74, 0x68, 0x6c, 0x79, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4a, 0x92, 0x41, 0x1f, 0x12, 0x1d, 0x52, 0x65,
|
||||
0x74, 0x75, 0x72, 0x6e, 0x73, 0x20, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x20, 0x61, 0x63,
|
||||
0x74, 0x69, 0x76, 0x65, 0x20, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02,
|
||||
0x22, 0x12, 0x20, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x3a, 0x6d,
|
||||
0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x2d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x2d, 0x75, 0x73,
|
||||
0x65, 0x72, 0x73, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e,
|
||||
0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65,
|
||||
0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69,
|
||||
0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_controller_api_services_v1_billing_service_proto_rawDescOnce sync.Once
|
||||
file_controller_api_services_v1_billing_service_proto_rawDescData = file_controller_api_services_v1_billing_service_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_controller_api_services_v1_billing_service_proto_rawDescGZIP() []byte {
|
||||
file_controller_api_services_v1_billing_service_proto_rawDescOnce.Do(func() {
|
||||
file_controller_api_services_v1_billing_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_services_v1_billing_service_proto_rawDescData)
|
||||
})
|
||||
return file_controller_api_services_v1_billing_service_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_api_services_v1_billing_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_controller_api_services_v1_billing_service_proto_goTypes = []interface{}{
|
||||
(*MonthlyActiveUsersRequest)(nil), // 0: controller.api.services.v1.MonthlyActiveUsersRequest
|
||||
(*MonthlyActiveUsersResponse)(nil), // 1: controller.api.services.v1.MonthlyActiveUsersResponse
|
||||
(*billing.ActiveUsers)(nil), // 2: controller.api.resources.billing.v1.ActiveUsers
|
||||
}
|
||||
var file_controller_api_services_v1_billing_service_proto_depIdxs = []int32{
|
||||
2, // 0: controller.api.services.v1.MonthlyActiveUsersResponse.items:type_name -> controller.api.resources.billing.v1.ActiveUsers
|
||||
0, // 1: controller.api.services.v1.BillingService.MonthlyActiveUsers:input_type -> controller.api.services.v1.MonthlyActiveUsersRequest
|
||||
1, // 2: controller.api.services.v1.BillingService.MonthlyActiveUsers:output_type -> controller.api.services.v1.MonthlyActiveUsersResponse
|
||||
2, // [2:3] is the sub-list for method output_type
|
||||
1, // [1:2] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_api_services_v1_billing_service_proto_init() }
|
||||
func file_controller_api_services_v1_billing_service_proto_init() {
|
||||
if File_controller_api_services_v1_billing_service_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_controller_api_services_v1_billing_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MonthlyActiveUsersRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_api_services_v1_billing_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MonthlyActiveUsersResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_api_services_v1_billing_service_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_controller_api_services_v1_billing_service_proto_goTypes,
|
||||
DependencyIndexes: file_controller_api_services_v1_billing_service_proto_depIdxs,
|
||||
MessageInfos: file_controller_api_services_v1_billing_service_proto_msgTypes,
|
||||
}.Build()
|
||||
File_controller_api_services_v1_billing_service_proto = out.File
|
||||
file_controller_api_services_v1_billing_service_proto_rawDesc = nil
|
||||
file_controller_api_services_v1_billing_service_proto_goTypes = nil
|
||||
file_controller_api_services_v1_billing_service_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
|
||||
// source: controller/api/services/v1/billing_service.proto
|
||||
|
||||
/*
|
||||
Package services is a reverse proxy.
|
||||
|
||||
It translates gRPC into RESTful JSON APIs.
|
||||
*/
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Suppress "imported and not used" errors
|
||||
var _ codes.Code
|
||||
var _ io.Reader
|
||||
var _ status.Status
|
||||
var _ = runtime.String
|
||||
var _ = utilities.NewDoubleArray
|
||||
var _ = metadata.Join
|
||||
|
||||
var (
|
||||
filter_BillingService_MonthlyActiveUsers_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_BillingService_MonthlyActiveUsers_0(ctx context.Context, marshaler runtime.Marshaler, client BillingServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MonthlyActiveUsersRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BillingService_MonthlyActiveUsers_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.MonthlyActiveUsers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_BillingService_MonthlyActiveUsers_0(ctx context.Context, marshaler runtime.Marshaler, server BillingServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MonthlyActiveUsersRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BillingService_MonthlyActiveUsers_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.MonthlyActiveUsers(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterBillingServiceHandlerServer registers the http handlers for service BillingService to "mux".
|
||||
// UnaryRPC :call BillingServiceServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterBillingServiceHandlerFromEndpoint instead.
|
||||
func RegisterBillingServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server BillingServiceServer) error {
|
||||
|
||||
mux.Handle("GET", pattern_BillingService_MonthlyActiveUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.BillingService/MonthlyActiveUsers", runtime.WithHTTPPathPattern("/v1/billing:monthly-active-users"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_BillingService_MonthlyActiveUsers_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_BillingService_MonthlyActiveUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterBillingServiceHandlerFromEndpoint is same as RegisterBillingServiceHandler but
|
||||
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
||||
func RegisterBillingServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
||||
conn, err := grpc.DialContext(ctx, endpoint, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
return RegisterBillingServiceHandler(ctx, mux, conn)
|
||||
}
|
||||
|
||||
// RegisterBillingServiceHandler registers the http handlers for service BillingService to "mux".
|
||||
// The handlers forward requests to the grpc endpoint over "conn".
|
||||
func RegisterBillingServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||
return RegisterBillingServiceHandlerClient(ctx, mux, NewBillingServiceClient(conn))
|
||||
}
|
||||
|
||||
// RegisterBillingServiceHandlerClient registers the http handlers for service BillingService
|
||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "BillingServiceClient".
|
||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "BillingServiceClient"
|
||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||
// "BillingServiceClient" to call the correct interceptors.
|
||||
func RegisterBillingServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client BillingServiceClient) error {
|
||||
|
||||
mux.Handle("GET", pattern_BillingService_MonthlyActiveUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.BillingService/MonthlyActiveUsers", runtime.WithHTTPPathPattern("/v1/billing:monthly-active-users"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_BillingService_MonthlyActiveUsers_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_BillingService_MonthlyActiveUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pattern_BillingService_MonthlyActiveUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "billing"}, "monthly-active-users"))
|
||||
)
|
||||
|
||||
var (
|
||||
forward_BillingService_MonthlyActiveUsers_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc (unknown)
|
||||
// source: controller/api/services/v1/billing_service.proto
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
const (
|
||||
BillingService_MonthlyActiveUsers_FullMethodName = "/controller.api.services.v1.BillingService/MonthlyActiveUsers"
|
||||
)
|
||||
|
||||
// BillingServiceClient is the client API for BillingService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type BillingServiceClient interface {
|
||||
// MonthlyActiveUsers returns the monthly active users for the given time period.
|
||||
// If no time period is provided, the current and previous months are returned.
|
||||
// If the provided request contains a start time but no end time, it will return
|
||||
// up to the current month. If the provided request contains an end time and no start time,
|
||||
// or if the end time is prior to the start time, an error will be returned.
|
||||
MonthlyActiveUsers(ctx context.Context, in *MonthlyActiveUsersRequest, opts ...grpc.CallOption) (*MonthlyActiveUsersResponse, error)
|
||||
}
|
||||
|
||||
type billingServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewBillingServiceClient(cc grpc.ClientConnInterface) BillingServiceClient {
|
||||
return &billingServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *billingServiceClient) MonthlyActiveUsers(ctx context.Context, in *MonthlyActiveUsersRequest, opts ...grpc.CallOption) (*MonthlyActiveUsersResponse, error) {
|
||||
out := new(MonthlyActiveUsersResponse)
|
||||
err := c.cc.Invoke(ctx, BillingService_MonthlyActiveUsers_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// BillingServiceServer is the server API for BillingService service.
|
||||
// All implementations must embed UnimplementedBillingServiceServer
|
||||
// for forward compatibility
|
||||
type BillingServiceServer interface {
|
||||
// MonthlyActiveUsers returns the monthly active users for the given time period.
|
||||
// If no time period is provided, the current and previous months are returned.
|
||||
// If the provided request contains a start time but no end time, it will return
|
||||
// up to the current month. If the provided request contains an end time and no start time,
|
||||
// or if the end time is prior to the start time, an error will be returned.
|
||||
MonthlyActiveUsers(context.Context, *MonthlyActiveUsersRequest) (*MonthlyActiveUsersResponse, error)
|
||||
mustEmbedUnimplementedBillingServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedBillingServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedBillingServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedBillingServiceServer) MonthlyActiveUsers(context.Context, *MonthlyActiveUsersRequest) (*MonthlyActiveUsersResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method MonthlyActiveUsers not implemented")
|
||||
}
|
||||
func (UnimplementedBillingServiceServer) mustEmbedUnimplementedBillingServiceServer() {}
|
||||
|
||||
// UnsafeBillingServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to BillingServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeBillingServiceServer interface {
|
||||
mustEmbedUnimplementedBillingServiceServer()
|
||||
}
|
||||
|
||||
func RegisterBillingServiceServer(s grpc.ServiceRegistrar, srv BillingServiceServer) {
|
||||
s.RegisterService(&BillingService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _BillingService_MonthlyActiveUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MonthlyActiveUsersRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BillingServiceServer).MonthlyActiveUsers(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: BillingService_MonthlyActiveUsers_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BillingServiceServer).MonthlyActiveUsers(ctx, req.(*MonthlyActiveUsersRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// BillingService_ServiceDesc is the grpc.ServiceDesc for BillingService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var BillingService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "controller.api.services.v1.BillingService",
|
||||
HandlerType: (*BillingServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "MonthlyActiveUsers",
|
||||
Handler: _BillingService_MonthlyActiveUsers_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "controller/api/services/v1/billing_service.proto",
|
||||
}
|
||||
@@ -932,7 +932,7 @@ func Test_AnonRestrictions(t *testing.T) {
|
||||
if i == resource.Controller || i == resource.Worker {
|
||||
continue
|
||||
}
|
||||
for j := action.Type(1); j <= action.RemoveGrantScopes; j++ {
|
||||
for j := action.Type(1); j <= action.MonthlyActiveUsers; j++ {
|
||||
id := "foobar"
|
||||
prefixes := globals.ResourcePrefixesFromType(resource.Type(i))
|
||||
if len(prefixes) > 0 {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package controller.api.resources.billing.v1;
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option go_package = "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/billing;billing";
|
||||
|
||||
message ActiveUsers {
|
||||
// Output only. The number of active users between the start time and end time.
|
||||
uint32 count = 1; // @gotags: `class:"public"`
|
||||
|
||||
// Output only. The start time of the active users count, inclusive.
|
||||
google.protobuf.Timestamp start_time = 2 [json_name = "start_time"]; // @gotags: class:"public"
|
||||
|
||||
// Output only. The end time of the active users count, exclusive.
|
||||
google.protobuf.Timestamp end_time = 3 [json_name = "end_time"]; // @gotags: class:"public"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package controller.api.services.v1;
|
||||
|
||||
import "controller/api/resources/billing/v1/billing.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
|
||||
option go_package = "github.com/hashicorp/boundary/internal/gen/controller/api/services;services";
|
||||
|
||||
service BillingService {
|
||||
// MonthlyActiveUsers returns the monthly active users for the given time period.
|
||||
// If no time period is provided, the current and previous months are returned.
|
||||
// If the provided request contains a start time but no end time, it will return
|
||||
// up to the current month. If the provided request contains an end time and no start time,
|
||||
// or if the end time is prior to the start time, an error will be returned.
|
||||
rpc MonthlyActiveUsers(MonthlyActiveUsersRequest) returns (MonthlyActiveUsersResponse) {
|
||||
option (google.api.http) = {get: "/v1/billing:monthly-active-users"};
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {summary: "Returns monthly active users."};
|
||||
}
|
||||
}
|
||||
|
||||
message MonthlyActiveUsersRequest {
|
||||
// An optional start time of the billing period to query, in the format of YYYY-MM.
|
||||
string start_time = 1 [json_name = "start_time"]; // @gotags: class:"public"
|
||||
|
||||
// An optional end time of the billing period to query, in the format of YYYY-MM.
|
||||
string end_time = 2 [json_name = "end_time"]; // @gotags: class:"public"
|
||||
}
|
||||
|
||||
message MonthlyActiveUsersResponse {
|
||||
repeated resources.billing.v1.ActiveUsers items = 1;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/accounts"
|
||||
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/authmethods"
|
||||
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/authtokens"
|
||||
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/billing"
|
||||
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentiallibraries"
|
||||
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentials"
|
||||
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentialstores"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
function active_users_last_two_months() {
|
||||
boundary billing monthly-active-users -format json
|
||||
}
|
||||
|
||||
function active_users_start_time() {
|
||||
boundary billing monthly-active-users -start-time=$1 -format json
|
||||
}
|
||||
|
||||
function active_users_start_time_and_end_time() {
|
||||
boundary billing monthly-active-users -start-time=$1 -end-time=$2 -format json
|
||||
}
|
||||
|
||||
function active_users_end_time() {
|
||||
boundary billing monthly-active-users -end-time=$1 -format json
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load _auth
|
||||
load _billing
|
||||
load _helpers
|
||||
|
||||
@test "boundary/billing: can login as admin user" {
|
||||
run login $DEFAULT_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/billing: admin user can get last two months" {
|
||||
run active_users_last_two_months
|
||||
[ "$status" -eq 0 ]
|
||||
run has_status_code "$output" "200"
|
||||
}
|
||||
|
||||
@test "boundary/billing: admin user can get report with start time" {
|
||||
run active_users_start_time "2023-09"
|
||||
[ "$status" -eq 0 ]
|
||||
run has_status_code "$output" "200"
|
||||
}
|
||||
|
||||
@test "boundary/billing: admin user can get report with start and end times" {
|
||||
run active_users_start_time_and_end_time "2023-09" "2023-12"
|
||||
[ "$status" -eq 0 ]
|
||||
run has_status_code "$output" "200"
|
||||
}
|
||||
|
||||
@test "boundary/billing: cannot get report with end time before start time" {
|
||||
run active_users_start_time_and_end_time "2023-09" "2023-08"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "boundary/billing: cannot get report with only end time" {
|
||||
run active_users_end_time "2023-09"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
# unpriv tests
|
||||
@test "boundary/billing: can login as unpriv user" {
|
||||
run login $DEFAULT_UNPRIVILEGED_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/billing: default user cannot get last two months" {
|
||||
run active_users_last_two_months
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "boundary/billing: default user cannot get report with start time" {
|
||||
run active_users_start_time "2023-09"
|
||||
[ "$status" -eq 1 ]
|
||||
run has_status_code "$output" "200"
|
||||
}
|
||||
|
||||
@test "boundary/billing: default user cannot get report with start and end times" {
|
||||
run active_users_start_time_and_end_time "2023-09" "2023-12"
|
||||
[ "$status" -eq 1 ]
|
||||
run has_status_code "$output" "200"
|
||||
}
|
||||
@@ -74,6 +74,7 @@ const (
|
||||
AddGrantScopes Type = 60
|
||||
SetGrantScopes Type = 61
|
||||
RemoveGrantScopes Type = 62
|
||||
MonthlyActiveUsers Type = 63
|
||||
|
||||
// When adding new actions, be sure to update:
|
||||
//
|
||||
@@ -144,6 +145,7 @@ var Map = map[string]Type{
|
||||
AddGrantScopes.String(): AddGrantScopes,
|
||||
SetGrantScopes.String(): SetGrantScopes,
|
||||
RemoveGrantScopes.String(): RemoveGrantScopes,
|
||||
MonthlyActiveUsers.String(): MonthlyActiveUsers,
|
||||
}
|
||||
|
||||
var DeprecatedMap = map[string]Type{
|
||||
@@ -220,6 +222,7 @@ func (a Type) String() string {
|
||||
"add-grant-scopes",
|
||||
"set-grant-scopes",
|
||||
"remove-grant-scopes",
|
||||
"monthly-active-users",
|
||||
}[a]
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ const (
|
||||
Credential
|
||||
StorageBucket
|
||||
Policy
|
||||
Billing
|
||||
// NOTE: When adding a new type, be sure to update:
|
||||
//
|
||||
// * The Grant.validateType function and test
|
||||
@@ -74,6 +75,7 @@ func (r Type) String() string {
|
||||
"credential",
|
||||
"storage-bucket",
|
||||
"policy",
|
||||
"billing",
|
||||
}[r]
|
||||
}
|
||||
|
||||
@@ -83,6 +85,8 @@ func (r Type) PluralString() string {
|
||||
return "credential-libraries"
|
||||
case Policy:
|
||||
return "policies"
|
||||
case Billing: // never pluralized
|
||||
return "billing"
|
||||
default:
|
||||
return r.String() + "s"
|
||||
}
|
||||
@@ -94,6 +98,8 @@ func FromPlural(s string) (Type, bool) {
|
||||
return CredentialLibrary, true
|
||||
case "policies":
|
||||
return Policy, true
|
||||
case "billing":
|
||||
return Billing, true
|
||||
default:
|
||||
t, ok := Map[strings.TrimSuffix(s, "s")]
|
||||
return t, ok
|
||||
@@ -124,6 +130,7 @@ var Map = map[string]Type{
|
||||
Credential.String(): Credential,
|
||||
StorageBucket.String(): StorageBucket,
|
||||
Policy.String(): Policy,
|
||||
Billing.String(): Billing,
|
||||
}
|
||||
|
||||
// Parent returns the parent type for a given type; if there is no parent, it
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.32.0
|
||||
// protoc (unknown)
|
||||
// source: controller/api/resources/billing/v1/billing.proto
|
||||
|
||||
package billing
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type ActiveUsers struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Output only. The number of active users between the start time and end time.
|
||||
Count uint32 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// Output only. The start time of the active users count, inclusive.
|
||||
StartTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=start_time,proto3" json:"start_time,omitempty" class:"public"` // @gotags: class:"public"
|
||||
// Output only. The end time of the active users count, exclusive.
|
||||
EndTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=end_time,proto3" json:"end_time,omitempty" class:"public"` // @gotags: class:"public"
|
||||
}
|
||||
|
||||
func (x *ActiveUsers) Reset() {
|
||||
*x = ActiveUsers{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_resources_billing_v1_billing_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ActiveUsers) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ActiveUsers) ProtoMessage() {}
|
||||
|
||||
func (x *ActiveUsers) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_resources_billing_v1_billing_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ActiveUsers.ProtoReflect.Descriptor instead.
|
||||
func (*ActiveUsers) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_resources_billing_v1_billing_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ActiveUsers) GetCount() uint32 {
|
||||
if x != nil {
|
||||
return x.Count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ActiveUsers) GetStartTime() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.StartTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ActiveUsers) GetEndTime() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.EndTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_controller_api_resources_billing_v1_billing_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_api_resources_billing_v1_billing_proto_rawDesc = []byte{
|
||||
0x0a, 0x31, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69,
|
||||
0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x62, 0x69, 0x6c, 0x6c, 0x69,
|
||||
0x6e, 0x67, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x12, 0x23, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x62, 0x69,
|
||||
0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x97, 0x01, 0x0a, 0x0b, 0x41, 0x63,
|
||||
0x74, 0x69, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12,
|
||||
0x3a, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
|
||||
0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x36, 0x0a, 0x08, 0x65,
|
||||
0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x42, 0x50, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e,
|
||||
0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f,
|
||||
0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x3b, 0x62, 0x69,
|
||||
0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_controller_api_resources_billing_v1_billing_proto_rawDescOnce sync.Once
|
||||
file_controller_api_resources_billing_v1_billing_proto_rawDescData = file_controller_api_resources_billing_v1_billing_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_controller_api_resources_billing_v1_billing_proto_rawDescGZIP() []byte {
|
||||
file_controller_api_resources_billing_v1_billing_proto_rawDescOnce.Do(func() {
|
||||
file_controller_api_resources_billing_v1_billing_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_resources_billing_v1_billing_proto_rawDescData)
|
||||
})
|
||||
return file_controller_api_resources_billing_v1_billing_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_api_resources_billing_v1_billing_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_controller_api_resources_billing_v1_billing_proto_goTypes = []interface{}{
|
||||
(*ActiveUsers)(nil), // 0: controller.api.resources.billing.v1.ActiveUsers
|
||||
(*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp
|
||||
}
|
||||
var file_controller_api_resources_billing_v1_billing_proto_depIdxs = []int32{
|
||||
1, // 0: controller.api.resources.billing.v1.ActiveUsers.start_time:type_name -> google.protobuf.Timestamp
|
||||
1, // 1: controller.api.resources.billing.v1.ActiveUsers.end_time:type_name -> google.protobuf.Timestamp
|
||||
2, // [2:2] is the sub-list for method output_type
|
||||
2, // [2:2] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_api_resources_billing_v1_billing_proto_init() }
|
||||
func file_controller_api_resources_billing_v1_billing_proto_init() {
|
||||
if File_controller_api_resources_billing_v1_billing_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_controller_api_resources_billing_v1_billing_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ActiveUsers); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_api_resources_billing_v1_billing_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_controller_api_resources_billing_v1_billing_proto_goTypes,
|
||||
DependencyIndexes: file_controller_api_resources_billing_v1_billing_proto_depIdxs,
|
||||
MessageInfos: file_controller_api_resources_billing_v1_billing_proto_msgTypes,
|
||||
}.Build()
|
||||
File_controller_api_resources_billing_v1_billing_proto = out.File
|
||||
file_controller_api_resources_billing_v1_billing_proto_rawDesc = nil
|
||||
file_controller_api_resources_billing_v1_billing_proto_goTypes = nil
|
||||
file_controller_api_resources_billing_v1_billing_proto_depIdxs = nil
|
||||
}
|
||||
Reference in new issue
Block a user