Skip to content

Commit

Permalink
feat: added vercel support for deployments + domains
Browse files Browse the repository at this point in the history
  • Loading branch information
Ice3man543 committed Aug 30, 2024
1 parent c8d29f7 commit eaad08d
Show file tree
Hide file tree
Showing 4 changed files with 457 additions and 0 deletions.
4 changes: 4 additions & 0 deletions pkg/inventory/inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/projectdiscovery/cloudlist/pkg/providers/openstack"
"github.com/projectdiscovery/cloudlist/pkg/providers/scaleway"
"github.com/projectdiscovery/cloudlist/pkg/providers/terraform"
"github.com/projectdiscovery/cloudlist/pkg/providers/vercel"
"github.com/projectdiscovery/cloudlist/pkg/schema"
mapsutil "github.com/projectdiscovery/utils/maps"
)
Expand Down Expand Up @@ -66,6 +67,7 @@ var Providers = map[string][]string{
"hetzner": hetzner.Services,
"openstack": openstack.Services,
"kubernetes": k8s.Services,
"vercel": vercel.Services,
}

func GetProviders() []string {
Expand Down Expand Up @@ -119,6 +121,8 @@ func nameToProvider(value string, block schema.OptionBlock) (schema.Provider, er
return openstack.New(block)
case "kubernetes":
return k8s.New(block)
case "vercel":
return vercel.New(block)
default:
return nil, fmt.Errorf("invalid provider name found: %s", value)
}
Expand Down
141 changes: 141 additions & 0 deletions pkg/providers/vercel/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package vercel

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"

"github.com/pkg/errors"
)

type vercelClient struct {
config *newClientConfig
url string
httpClient *http.Client
}

type newClientConfig struct {
Token string
Teamid string
}

func newAPIClient(config newClientConfig) *vercelClient {
return &vercelClient{
config: &config,
url: "https://api.vercel.com",
httpClient: &http.Client{
Transport: &http.Transport{},
Timeout: 60 * time.Second,
},
}
}

type apiRequest struct {
Method string
Path string
Body interface{}
Query url.Values
ResponseTarget interface{}
}

func newApiRequest(method string, path string, ResponseTarget interface{}) apiRequest {
return apiRequest{
Method: method,
Path: path,
Body: nil,
Query: url.Values{},
ResponseTarget: ResponseTarget,
}
}

// Call the Vercel API and unmarshal its response directly
func (c *vercelClient) Call(req apiRequest) error {
path := req.Path
query := req.Query.Encode()
if query != "" {
path = fmt.Sprintf("%s?%s", path, query)
}

httpResponse, err := c.request(req.Method, path, req.Body)
if err != nil {
return err
}
defer httpResponse.Body.Close()
if req.ResponseTarget == nil {
return nil
}
err = json.NewDecoder(httpResponse.Body).Decode(req.ResponseTarget)
if err != nil {
return errors.Wrap(err, "unable to decode response body")
}
return nil
}

// Perform a request and return its response
func (c *vercelClient) request(method string, path string, body interface{}) (*http.Response, error) {
payload, err := marshalBody(body)
if err != nil {
return nil, errors.Wrap(err, "unable to marshal request body")
}
req, err := http.NewRequest(method, fmt.Sprintf("%s%s", c.url, path), payload)
if err != nil {
return nil, errors.Wrap(err, "unable to create request")
}
req.Header.Set("User-Agent", "cloudlist-go")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.config.Token))

res, err := c.httpClient.Do(req)
if err != nil {
return nil, errors.Wrap(err, "unable to perform request")
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
var responseBody map[string]interface{}
err = json.NewDecoder(res.Body).Decode(&responseBody)
if err != nil {
return nil, fmt.Errorf("response returned status code %d, path: %s", res.StatusCode, path)
}

// Try to prettyprint the response body
// If that is not possible we return the raw body
pretty, err := json.MarshalIndent(responseBody, "", " ")
if err != nil {
return nil, fmt.Errorf("response returned status code %d: %+v, path: %s", res.StatusCode, responseBody, path)
}
return nil, fmt.Errorf("response returned status code %d: %+v, path: %s", res.StatusCode, string(pretty), path)
}
return res, nil

}

// JSON marshal the body if present
func marshalBody(body interface{}) (io.Reader, error) {
var payload io.Reader = nil
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return nil, err
}
payload = bytes.NewBuffer(b)
}
return payload, nil
}

// Endpoints implementations below
// ------------------------------

func (c *vercelClient) ListProjects(req ListProjectsRequest) (res ListProjectsResponse, err error) {
apiRequest := newApiRequest("GET", "/v8/projects", &res)
if c.config.Teamid != "" {
apiRequest.Query.Add("teamId", c.config.Teamid)
}
err = c.Call(apiRequest)
if err != nil {
return ListProjectsResponse{}, fmt.Errorf("unable to fetch projects: %w", err)
}
return res, nil
}
206 changes: 206 additions & 0 deletions pkg/providers/vercel/api_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package vercel

type ListProjectsRequest struct {
// Limit the number of projects returned.
// Required: No
Limit int64 `json:"limit,omitempty"`

// The updatedAt point64where the list should start.
// Required: No
Since int64 `json:"since,omitempty"`

// The updatedAt point64where the list should end.
// Required: No
Until int64 `json:"until,omitempty"`

// Search projects by the name field.
// Required: No
Search string `json:"string,omitempty"`
}

type ListProjectsResponse struct {
Projects []Project `json:"projects"`
}

// Project houses all the information vercel offers about a project via their api
type Project struct {
Accountid string `json:"accountid"`
Alias []struct {
ConfiguredBy string `json:"configuredBy"`
ConfiguredChangedAt int64 `json:"configuredChangedAt"`
CreatedAt int64 `json:"createdAt"`
Deployment struct {
Alias []string `json:"alias"`
AliasAssigned int64 `json:"aliasAssigned"`
Builds []interface{} `json:"builds"`
CreatedAt int64 `json:"createdAt"`
CreatedIn string `json:"createdIn"`
Creator struct {
Uid string `json:"uid"`
Email string `json:"email"`
Username string `json:"username"`
GithubLogin string `json:"githubLogin"`
} `json:"creator"`
DeploymentHostname string `json:"deploymentHostname"`
Forced bool `json:"forced"`
Id string `json:"id"`
Meta struct {
GithubCommitRef string `json:"githubCommitRef"`
GithubRepo string `json:"githubRepo"`
GithubOrg string `json:"githubOrg"`
GithubCommitSha string `json:"githubCommitSha"`
GithubRepoid string `json:"githubRepoid"`
GithubCommitMessage string `json:"githubCommitMessage"`
GithubCommitAuthorLogin string `json:"githubCommitAuthorLogin"`
GithubDeployment string `json:"githubDeployment"`
GithubCommitOrg string `json:"githubCommitOrg"`
GithubCommitAuthorName string `json:"githubCommitAuthorName"`
GithubCommitRepo string `json:"githubCommitRepo"`
GithubCommitRepoid string `json:"githubCommitRepoid"`
} `json:"meta"`
Name string `json:"name"`
Plan string `json:"plan"`
Private bool `json:"private"`
ReadyState string `json:"readyState"`
Target string `json:"target"`
Teamid string `json:"teamid"`
Type string `json:"type"`
URL string `json:"url"`
Userid string `json:"userid"`
WithCache bool `json:"withCache"`
} `json:"deployment"`
Domain string `json:"domain"`
Environment string `json:"environment"`
Target string `json:"target"`
} `json:"alias"`
Analytics struct {
Id string `json:"id"`
EnabledAt int64 `json:"enabledAt"`
DisabledAt int64 `json:"disabledAt"`
CanceledAt int64 `json:"canceledAt"`
} `json:"analytics"`
AutoExposeSystemEnvs bool `json:"autoExposeSystemEnvs"`
BuildCommand string `json:"buildCommand"`
CreatedAt int64 `json:"createdAt"`
DevCommand string `json:"devCommand"`
DirectoryListing bool `json:"directoryListing"`
Env []struct {
Type string `json:"type"`
Id string `json:"id"`
Key string `json:"key"`
Value string `json:"value"`
Target []string `json:"target"`
Configurationid interface{} `json:"configurationid"`
UpdatedAt int64 `json:"updatedAt"`
CreatedAt int64 `json:"createdAt"`
} `json:"env"`
Framework string `json:"framework"`
Id string `json:"id"`
InstallCommand string `json:"installCommand"`
Name string `json:"name"`
NodeVersion string `json:"nodeVersion"`
OutputDirectory string `json:"outputDirectory"`
PublicSource bool `json:"publicSource"`
RootDirectory string `json:"rootDirectory"`
ServerlessFunctionRegion string `json:"serverlessFunctionRegion"`
SourceFilesOutsideRootDirectory bool `json:"sourceFilesOutsideRootDirectory"`
UpdatedAt int64 `json:"updatedAt"`
Link struct {
Type string `json:"type"`
Repo string `json:"repo"`
Repoid int64 `json:"repoid"`
Org string `json:"org"`
GitCredentialid string `json:"gitCredentialid"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
Sourceless bool `json:"sourceless"`
ProductionBranch string `json:"productionBranch"`
DeployHooks []interface{} `json:"deployHooks"`
ProjectName string `json:"projectName"`
ProjectNamespace string `json:"projectNamespace"`
Owner string `json:"owner"`
Slug string `json:"slug"`
} `json:"link"`
LatestDeployments []struct {
Alias []string `json:"alias"`
AliasAssigned int64 `json:"aliasAssigned"`
Builds []interface{} `json:"builds"`
CreatedAt int64 `json:"createdAt"`
CreatedIn string `json:"createdIn"`
Creator struct {
Uid string `json:"uid"`
Email string `json:"email"`
Username string `json:"username"`
GithubLogin string `json:"githubLogin"`
} `json:"creator"`
DeploymentHostname string `json:"deploymentHostname"`
Forced bool `json:"forced"`
Id string `json:"id"`
Meta struct {
GithubCommitRef string `json:"githubCommitRef"`
GithubRepo string `json:"githubRepo"`
GithubOrg string `json:"githubOrg"`
GithubCommitSha string `json:"githubCommitSha"`
GithubCommitAuthorLogin string `json:"githubCommitAuthorLogin"`
GithubCommitMessage string `json:"githubCommitMessage"`
GithubRepoid string `json:"githubRepoid"`
GithubDeployment string `json:"githubDeployment"`
GithubCommitOrg string `json:"githubCommitOrg"`
GithubCommitAuthorName string `json:"githubCommitAuthorName"`
GithubCommitRepo string `json:"githubCommitRepo"`
GithubCommitRepoid string `json:"githubCommitRepoid"`
} `json:"meta"`
Name string `json:"name"`
Plan string `json:"plan"`
Private bool `json:"private"`
ReadyState string `json:"readyState"`
Target interface{} `json:"target"`
Teamid string `json:"teamid"`
Type string `json:"type"`
URL string `json:"url"`
Userid string `json:"userid"`
WithCache bool `json:"withCache"`
} `json:"latestDeployments"`
Targets struct {
Production struct {
Alias []string `json:"alias"`
AliasAssigned int64 `json:"aliasAssigned"`
Builds []interface{} `json:"builds"`
CreatedAt int64 `json:"createdAt"`
CreatedIn string `json:"createdIn"`
Creator struct {
Uid string `json:"uid"`
Email string `json:"email"`
Username string `json:"username"`
GithubLogin string `json:"githubLogin"`
} `json:"creator"`
DeploymentHostname string `json:"deploymentHostname"`
Forced bool `json:"forced"`
Id string `json:"id"`
Meta struct {
GithubCommitRef string `json:"githubCommitRef"`
GithubRepo string `json:"githubRepo"`
GithubOrg string `json:"githubOrg"`
GithubCommitSha string `json:"githubCommitSha"`
GithubRepoid string `json:"githubRepoid"`
GithubCommitMessage string `json:"githubCommitMessage"`
GithubCommitAuthorLogin string `json:"githubCommitAuthorLogin"`
GithubDeployment string `json:"githubDeployment"`
GithubCommitOrg string `json:"githubCommitOrg"`
GithubCommitAuthorName string `json:"githubCommitAuthorName"`
GithubCommitRepo string `json:"githubCommitRepo"`
GithubCommitRepoid string `json:"githubCommitRepoid"`
} `json:"meta"`
Name string `json:"name"`
Plan string `json:"plan"`
Private bool `json:"private"`
ReadyState string `json:"readyState"`
Target string `json:"target"`
Teamid string `json:"teamid"`
Type string `json:"type"`
URL string `json:"url"`
Userid string `json:"userid"`
WithCache bool `json:"withCache"`
} `json:"production"`
} `json:"targets"`
}
Loading

0 comments on commit eaad08d

Please sign in to comment.