Files
plane/monitor/lib/api/api.go
Nikhil 4b31dd0c01 dev: new license activation workflow for one and pro users (#739)
* feat: added feature flag package for decrypting and refreshing the feature flags value

* feat: added feature flagging route for route handlers

* feat: added feature flag worker for managing and distributing feature flags

* feat: implemented feature flag api implementation inside ff handler

* feat: added exponential retry interval inside feature flagging

* fix: route pattern of feature flag endpoint from monitor

* feat: created endpoint to activate workspace from monitor

* feat: made changes to accomodate db and activate endpoint

* dev: update multi tenant environment settings

* dev: workspace license activation flow

* dev: update license activate endpoint

* dev: update workspace license activate endpoint

* feat: added feature flag user sync logic

* feat: added constants and sync logic with private key inside

* feat: added port as env variable

* feat: added modification for removing license key in healthcheck

* feat: example env pushed

* fix: update monitor endpoints and license activation function

* dev: initiate free workspace only for self hosted instances

* dev: self hosting license endpoint updates

* dev: remove monitor db

* dev: update handlers

* dev: self hosted license subscription web

* dev: removed computed in self hosted subscription

* fix: prime server url and delete old license while creating new license in sqlite

* chore: subscription activation alert after successfull activation.

* dev: update feature flag endpoints

* fix: handled several cases of feature flags

* fix: linting errors

* dev: update monitor to handle workspace activation

* dev: update decryption algorithm

* feat: separated activation payload with the sync payload

* dev: update feature flags endpoint

* dev: change type for workspace sync

* feat: added isActive flag for user activation

* feat: removed creation of free users in monitor

* feat: added method for workspace product

* dev: add user email in types

* feat: added method for workspace product

* fix: uer license feature flag fetch

* fix: handler fixed for feature key

* chore: self managed license activation

* chore: update validation license flow.

* chore: license activation success.

* dev: resync workspace license on activation

* chore: plane one success modal.

* feat: added constructs to inject private key build time

* feat: updated the docker file with private key build argument

* feat: added consumption od DEFAULT_PRIVATE_KEY when building monitor

* dev: update private key

---------

Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-08-22 17:39:19 +05:30

285 lines
7.9 KiB
Go

package prime_api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
type IPrimeMonitorApi interface {
PostServiceStatus(StatusPayload) ErrorCode
GetFeatureFlags(licenseKey string) (*FlagDataResponse, ErrorCode)
ActivateInstance() ErrorCode
DeactivateInstance() ErrorCode
SyncWorkspace(WorkspaceSyncPayload) (*WorkspaceActivationResponse, ErrorCode)
ActivateWorkspace(WorkspaceActivationPayload) (*WorkspaceActivationResponse, ErrorCode)
ActivateFreeWorkspace(WorkspaceActivationPayload) (*WorkspaceActivationResponse, ErrorCode)
}
type PrimeMonitorApi struct {
host string
instanceId string
appVersion string
client string
version string
machineSignature string
}
type FlagsPayload struct {
EncryptedData string `json:"encrypted_data"`
}
func NewMonitorApi(host, machineSignature, instanceId, appVersion string) IPrimeMonitorApi {
return &PrimeMonitorApi{
host: host,
instanceId: instanceId,
appVersion: appVersion,
client: "Prime-Monitor",
version: appVersion,
machineSignature: machineSignature,
}
}
func (api *PrimeMonitorApi) SetClient(client string) {
api.client = client
}
var (
API_PREFIX = "/api/v2"
MONITOR_ENDPOINT = API_PREFIX + "/monitor/"
FEATURE_FLAGS = API_PREFIX + "/flags/"
INSTANCE_PREFIX = API_PREFIX + "/instances"
ACTIVATE_INSTANCE = INSTANCE_PREFIX + "/activate/"
DEACTIVATE_INSTANCE = INSTANCE_PREFIX + "/deactivate/"
UPGRADE_INSTANCE = INSTANCE_PREFIX + "/upgrade/"
FREE_WORKSPACE_ACTIVATE = API_PREFIX + "/licenses/initialize/"
WORKSPACE_ACTIVATE = API_PREFIX + "/licenses/activate/"
SYNC_WORKSPACE = API_PREFIX + "/licenses/sync/"
)
/* ----------------------- Controller Methods ------------------------------ */
// Posts the status of the services given, to the prime server, returns error if
// hinderer, else doesn't return anything
func (api *PrimeMonitorApi) PostServiceStatus(payload StatusPayload) ErrorCode {
_, err := api.post(api.host+MONITOR_ENDPOINT, payload)
if err != nil {
return UNABLE_TO_POST_SERVICE_STATUS
}
return 0
}
func (api *PrimeMonitorApi) ActivateFreeWorkspace(payload WorkspaceActivationPayload) (*WorkspaceActivationResponse, ErrorCode) {
resp, err := api.post(api.host+FREE_WORKSPACE_ACTIVATE, payload)
if err != nil {
return nil, UNABLE_TO_ACTIVATE_WORKSPACE
}
data := WorkspaceActivationResponse{}
err = json.Unmarshal(resp, &data)
if err != nil {
return nil, UNABLE_TO_ACTIVATE_WORKSPACE
}
return &data, 0
}
// Activating a paid licensed workspace
func (api *PrimeMonitorApi) ActivateWorkspace(payload WorkspaceActivationPayload) (*WorkspaceActivationResponse, ErrorCode) {
resp, err := api.post(api.host+WORKSPACE_ACTIVATE, payload)
if err != nil {
return nil, UNABLE_TO_ACTIVATE_WORKSPACE
}
data := WorkspaceActivationResponse{}
err = json.Unmarshal(resp, &data)
if err != nil {
return nil, UNABLE_TO_ACTIVATE_WORKSPACE
}
return &data, 0
}
func (api *PrimeMonitorApi) GetFeatureFlags(licenseKey string) (*FlagDataResponse, ErrorCode) {
flagData, err := api.post(api.host+FEATURE_FLAGS, map[string]string{
"license_key": licenseKey,
"version": api.version,
})
if err != nil {
return nil, UNABLE_TO_GET_FEATURE_FLAGS
}
data := FlagDataResponse{}
err = json.Unmarshal(flagData, &data)
if err != nil {
return nil, UNABLE_TO_PARSE_FEATURE_FLAGS
}
return &data, 0
}
func (api *PrimeMonitorApi) SyncWorkspace(payload WorkspaceSyncPayload) (*WorkspaceActivationResponse, ErrorCode) {
resp, err := api.post(api.host+SYNC_WORKSPACE, payload)
if err != nil {
return nil, UNABLE_TO_SYNC_WORKSPACE
}
data := WorkspaceActivationResponse{}
err = json.Unmarshal(resp, &data)
if err != nil {
return nil, UNABLE_TO_SYNC_WORKSPACE
}
return &data, 0
}
func (api *PrimeMonitorApi) ActivateInstance() ErrorCode {
_, err := api.post(api.host+ACTIVATE_INSTANCE, nil)
if err != nil {
return UNABLE_TO_ACTIVATE_WORKSPACE
}
return 0
}
func (api *PrimeMonitorApi) DeactivateInstance() ErrorCode {
_, err := api.post(api.host+DEACTIVATE_INSTANCE, nil)
if err != nil {
return UNABLE_TO_ACTIVATE_WORKSPACE
}
return 0
}
func (api *PrimeMonitorApi) UpgradeInstance() ErrorCode {
_, err := api.post(api.host+UPGRADE_INSTANCE, nil)
if err != nil {
return UNABLE_TO_ACTIVATE_WORKSPACE
}
return 0
}
// ------------------------ Helper Methods ----------------------------------
/*
prepareRequest prepares an HTTP request with the necessary headers and parameters.
Parameters:
- method: string specifying the HTTP method (e.g., "GET", "POST").
- urlStr: string specifying the URL for the request.
- body: io.Reader containing the request body.
- params: map[string]string containing the query parameters.
Returns:
- *http.Request: The prepared HTTP request.
- error: An error if any occurs during the preparation.
*/
func (api *PrimeMonitorApi) prepareRequest(method, urlStr string, body io.Reader, params map[string]string) (*http.Request, error) {
if method == "GET" && params != nil {
parsedURL, err := url.Parse(urlStr)
if err != nil {
return nil, fmt.Errorf("error parsing URL: %v", err)
}
query := parsedURL.Query()
for key, value := range params {
query.Set(key, value)
}
parsedURL.RawQuery = query.Encode()
urlStr = parsedURL.String()
}
req, err := http.NewRequest(method, urlStr, body)
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
headers := map[string]string{
"X-Instance-Id": api.instanceId,
"X-Machine-Signature": api.machineSignature,
"X-Client": api.client,
"X-License-Version": api.version,
"Content-Type": "application/json",
}
for key, value := range headers {
req.Header.Add(key, value)
}
return req, nil
}
/*
doRequest executes the HTTP request and handles common response scenarios.
Parameters:
- req: *http.Request specifying the HTTP request to execute.
Returns:
- []byte: The response body.
- error: An error if any occurs during the request execution.
*/
func (api *PrimeMonitorApi) doRequest(req *http.Request) ([]byte, error) {
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request: %v", err)
}
defer resp.Body.Close()
switch {
case resp.StatusCode >= 200 && resp.StatusCode <= 227:
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %v", err)
}
return body, nil
case resp.StatusCode >= 300 && resp.StatusCode <= 308:
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %v", err)
}
return body, nil
case resp.StatusCode >= 400 && resp.StatusCode <= 451:
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
default:
return nil, fmt.Errorf("unexpected status code: %v", resp.StatusCode)
}
}
/*
get performs a GET request.
Parameters:
- baseURL: string specifying the base URL for the request.
- params: map[string]string containing the query parameters.
Returns:
- []byte: The response body.
- error: An error if any occurs during the request.
*/
func (api *PrimeMonitorApi) get(baseURL string, params map[string]string) ([]byte, error) {
req, err := api.prepareRequest("GET", baseURL, nil, params)
if err != nil {
return nil, err
}
return api.doRequest(req)
}
/*
post performs a POST request with JSON body.
Parameters:
- baseURL: string specifying the base URL for the request.
- data: interface{} containing the data to be sent in the request body.
Returns:
- []byte: The response body.
- error: An error if any occurs during the request.
*/
func (api *PrimeMonitorApi) post(baseURL string, data interface{}) ([]byte, error) {
jsonData, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("error marshaling data: %v", err)
}
req, err := api.prepareRequest("POST", baseURL, bytes.NewBuffer(jsonData), nil)
if err != nil {
return nil, err
}
return api.doRequest(req)
}