Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added vercel support for deployments + domains #567

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading