mirror of https://github.com/coder/coder.git
136 lines
3.4 KiB
Go
136 lines
3.4 KiB
Go
package codersdk
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/xerrors"
|
|
)
|
|
|
|
const (
|
|
LicenseExpiryClaim = "license_expires"
|
|
)
|
|
|
|
type AddLicenseRequest struct {
|
|
License string `json:"license" validate:"required"`
|
|
}
|
|
|
|
type License struct {
|
|
ID int32 `json:"id"`
|
|
UUID uuid.UUID `json:"uuid" format:"uuid"`
|
|
UploadedAt time.Time `json:"uploaded_at" format:"date-time"`
|
|
// Claims are the JWT claims asserted by the license. Here we use
|
|
// a generic string map to ensure that all data from the server is
|
|
// parsed verbatim, not just the fields this version of Coder
|
|
// understands.
|
|
Claims map[string]interface{} `json:"claims" table:"claims"`
|
|
}
|
|
|
|
// ExpiresAt returns the expiration time of the license.
|
|
// If the claim is missing or has an unexpected type, an error is returned.
|
|
func (l *License) ExpiresAt() (time.Time, error) {
|
|
expClaim, ok := l.Claims[LicenseExpiryClaim]
|
|
if !ok {
|
|
return time.Time{}, xerrors.New("license_expires claim is missing")
|
|
}
|
|
|
|
// This claim should be a unix timestamp.
|
|
// Everything is already an interface{}, so we need to do some type
|
|
// assertions to figure out what we're dealing with.
|
|
if unix, ok := expClaim.(json.Number); ok {
|
|
i64, err := unix.Int64()
|
|
if err != nil {
|
|
return time.Time{}, xerrors.Errorf("license_expires claim is not a valid unix timestamp: %w", err)
|
|
}
|
|
return time.Unix(i64, 0), nil
|
|
}
|
|
|
|
return time.Time{}, xerrors.Errorf("license_expires claim has unexpected type %T", expClaim)
|
|
}
|
|
|
|
func (l *License) Trial() bool {
|
|
if trail, ok := l.Claims["trail"].(bool); ok {
|
|
return trail
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (l *License) AllFeaturesClaim() bool {
|
|
if all, ok := l.Claims["all_features"].(bool); ok {
|
|
return all
|
|
}
|
|
return false
|
|
}
|
|
|
|
// FeaturesClaims provides the feature claims in license.
|
|
// This only returns the explicit claims. If checking for actual usage,
|
|
// also check `AllFeaturesClaim`.
|
|
func (l *License) FeaturesClaims() (map[FeatureName]int64, error) {
|
|
strMap, ok := l.Claims["features"].(map[string]interface{})
|
|
if !ok {
|
|
return nil, xerrors.New("features key is unexpected type")
|
|
}
|
|
fMap := make(map[FeatureName]int64)
|
|
for k, v := range strMap {
|
|
jn, ok := v.(json.Number)
|
|
if !ok {
|
|
return nil, xerrors.Errorf("feature %q has unexpected type", k)
|
|
}
|
|
|
|
n, err := jn.Int64()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fMap[FeatureName(k)] = n
|
|
}
|
|
|
|
return fMap, nil
|
|
}
|
|
|
|
func (c *Client) AddLicense(ctx context.Context, r AddLicenseRequest) (License, error) {
|
|
res, err := c.Request(ctx, http.MethodPost, "/api/v2/licenses", r)
|
|
if err != nil {
|
|
return License{}, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusCreated {
|
|
return License{}, ReadBodyAsError(res)
|
|
}
|
|
var l License
|
|
d := json.NewDecoder(res.Body)
|
|
d.UseNumber()
|
|
return l, d.Decode(&l)
|
|
}
|
|
|
|
func (c *Client) Licenses(ctx context.Context) ([]License, error) {
|
|
res, err := c.Request(ctx, http.MethodGet, "/api/v2/licenses", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
return nil, ReadBodyAsError(res)
|
|
}
|
|
var licenses []License
|
|
d := json.NewDecoder(res.Body)
|
|
d.UseNumber()
|
|
return licenses, d.Decode(&licenses)
|
|
}
|
|
|
|
func (c *Client) DeleteLicense(ctx context.Context, id int32) error {
|
|
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/licenses/%d", id), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
return ReadBodyAsError(res)
|
|
}
|
|
return nil
|
|
}
|