6dccf494f8
cleanup permit policies and correct documentation * docs * policy cleanup * refactor * Merge remote-tracking branch 'origin/feature/permit-policy-cleanup' into feature/permit-policy-cleanup * docs and edits
603 lines
19 KiB
Go
603 lines
19 KiB
Go
// setup_permit.go - CLI tool to configure Permit.io roles and resources
|
||
// This tool reads a YAML configuration file and creates resources, actions, and roles
|
||
// in a Permit.io project/environment using the Permit.io REST API.
|
||
//
|
||
// Usage:
|
||
//
|
||
// PERMIT_API_KEY=<key> go run setup_permit.go -config permit_policies.yaml -project <proj_id> -env <env_id>
|
||
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"flag"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"os"
|
||
"strings"
|
||
"time"
|
||
|
||
"gopkg.in/yaml.v3"
|
||
)
|
||
|
||
// Config structures matching the YAML file
|
||
type Config struct {
|
||
Resources []Resource `yaml:"resources"`
|
||
Roles []Role `yaml:"roles"`
|
||
}
|
||
|
||
type Resource struct {
|
||
Name string `yaml:"name"`
|
||
Description string `yaml:"description"`
|
||
Actions []string `yaml:"actions"`
|
||
}
|
||
|
||
type Role struct {
|
||
Name string `yaml:"name"`
|
||
Description string `yaml:"description"`
|
||
Permissions map[string][]string `yaml:"permissions"`
|
||
Notes string `yaml:"notes"`
|
||
}
|
||
|
||
// Permit.io API request/response types
|
||
type ResourceCreateRequest struct {
|
||
Key string `json:"key"`
|
||
Name string `json:"name"`
|
||
Description string `json:"description,omitempty"`
|
||
Actions map[string]ActionDef `json:"actions"`
|
||
}
|
||
|
||
type ResourceUpdateRequest struct {
|
||
Name string `json:"name,omitempty"`
|
||
Description string `json:"description,omitempty"`
|
||
Actions map[string]ActionDef `json:"actions"`
|
||
}
|
||
|
||
type ActionDef struct {
|
||
Name string `json:"name,omitempty"`
|
||
Description string `json:"description,omitempty"`
|
||
}
|
||
|
||
type RoleCreateRequest struct {
|
||
Key string `json:"key"`
|
||
Name string `json:"name"`
|
||
Description string `json:"description,omitempty"`
|
||
Permissions []string `json:"permissions,omitempty"`
|
||
}
|
||
|
||
type RoleUpdateRequest struct {
|
||
Name string `json:"name,omitempty"`
|
||
Description string `json:"description,omitempty"`
|
||
Permissions []string `json:"permissions,omitempty"`
|
||
}
|
||
|
||
func main() {
|
||
// Command-line flags
|
||
configFile := flag.String("config", "permit_policies.yaml", "Path to YAML config file")
|
||
projectID := flag.String("project", "", "Permit.io project ID or key")
|
||
envID := flag.String("env", "", "Permit.io environment ID or key")
|
||
dryRun := flag.Bool("dry-run", false, "Print what would be done without making changes")
|
||
listProjects := flag.Bool("list", false, "List available projects and environments")
|
||
flag.Parse()
|
||
|
||
// Get API key from environment
|
||
apiKey := os.Getenv("PERMIT_API_KEY")
|
||
if apiKey == "" {
|
||
fmt.Fprintf(os.Stderr, "Error: PERMIT_API_KEY environment variable must be set\n")
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Handle list command
|
||
if *listProjects {
|
||
client := &http.Client{Timeout: 30 * time.Second}
|
||
baseURL := "https://api.permit.io"
|
||
listProjectsAndEnvironments(client, baseURL, apiKey)
|
||
return
|
||
}
|
||
|
||
// Validate required parameters
|
||
if *projectID == "" || *envID == "" {
|
||
fmt.Fprintf(os.Stderr, "Error: -project and -env are required\n")
|
||
fmt.Fprintf(os.Stderr, "Usage: PERMIT_API_KEY=<key> %s -config <yaml> -project <proj_id> -env <env_id>\n", os.Args[0])
|
||
fmt.Fprintf(os.Stderr, "\nTo list available projects and environments:\n")
|
||
fmt.Fprintf(os.Stderr, " PERMIT_API_KEY=<key> %s -list\n", os.Args[0])
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Read and parse YAML config
|
||
config, err := loadConfig(*configFile)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Create HTTP client
|
||
client := &http.Client{Timeout: 30 * time.Second}
|
||
baseURL := "https://api.permit.io"
|
||
|
||
fmt.Printf("Setting up Permit.io configuration for project=%s, env=%s\n", *projectID, *envID)
|
||
fmt.Printf("Dry run: %v\n\n", *dryRun)
|
||
|
||
// Step 1: Create resources and their actions
|
||
fmt.Println("=" + strings.Repeat("=", 70))
|
||
fmt.Println("STEP 1: Creating Resources and Actions")
|
||
fmt.Println("=" + strings.Repeat("=", 70))
|
||
|
||
hasErrors := false
|
||
for _, resource := range config.Resources {
|
||
if err := createResource(client, baseURL, apiKey, *projectID, *envID, resource, *dryRun); err != nil {
|
||
fmt.Fprintf(os.Stderr, "\n❌ Error creating resource '%s': %v\n", resource.Name, err)
|
||
hasErrors = true
|
||
// If we get a 404 on the first resource, the project/env is wrong - fail fast
|
||
if strings.Contains(err.Error(), "404") && strings.Contains(err.Error(), "could not find") {
|
||
fmt.Fprintf(os.Stderr, "\n💡 Project or environment not found. Use -list to see available options.\n")
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Step 2: Create roles with permissions
|
||
fmt.Println("\n" + strings.Repeat("=", 70))
|
||
fmt.Println("STEP 2: Creating Roles with Permissions")
|
||
fmt.Println("=" + strings.Repeat("=", 70))
|
||
|
||
for _, role := range config.Roles {
|
||
if err := createRole(client, baseURL, apiKey, *projectID, *envID, role, *dryRun); err != nil {
|
||
fmt.Fprintf(os.Stderr, "\n❌ Error creating role '%s': %v\n", role.Name, err)
|
||
hasErrors = true
|
||
}
|
||
}
|
||
|
||
fmt.Println("\n" + strings.Repeat("=", 70))
|
||
if hasErrors {
|
||
fmt.Println("⚠️ Setup completed with errors")
|
||
fmt.Println("=" + strings.Repeat("=", 70))
|
||
os.Exit(1)
|
||
} else {
|
||
fmt.Println("✅ Setup Complete!")
|
||
fmt.Println("=" + strings.Repeat("=", 70))
|
||
if *dryRun {
|
||
fmt.Println("NOTE: This was a dry run. No changes were made.")
|
||
fmt.Println("Run without -dry-run flag to apply changes.")
|
||
}
|
||
|
||
// Print IDs for use in other scripts
|
||
fmt.Println("\n# Export these for use in other scripts:")
|
||
fmt.Printf("PERMIT_PROJECT_ID=%s\n", *projectID)
|
||
fmt.Printf("PERMIT_ENVIRONMENT_ID=%s\n", *envID)
|
||
}
|
||
}
|
||
|
||
// loadConfig reads and parses the YAML configuration file
|
||
func loadConfig(filepath string) (*Config, error) {
|
||
data, err := os.ReadFile(filepath)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to read config file: %w", err)
|
||
}
|
||
|
||
var config Config
|
||
if err := yaml.Unmarshal(data, &config); err != nil {
|
||
return nil, fmt.Errorf("failed to parse YAML: %w", err)
|
||
}
|
||
|
||
return &config, nil
|
||
}
|
||
|
||
// createResource creates or updates a resource with its actions in Permit.io (idempotent)
|
||
func createResource(client *http.Client, baseURL, apiKey, projectID, envID string, resource Resource, dryRun bool) error {
|
||
fmt.Printf("\nConfiguring resource: %s\n", resource.Name)
|
||
fmt.Printf(" Description: %s\n", resource.Description)
|
||
fmt.Printf(" Actions: %v\n", resource.Actions)
|
||
|
||
if dryRun {
|
||
fmt.Println(" [DRY RUN] Would create/update resource")
|
||
return nil
|
||
}
|
||
|
||
// Build actions map
|
||
actions := make(map[string]ActionDef)
|
||
for _, action := range resource.Actions {
|
||
actions[action] = ActionDef{Name: action}
|
||
}
|
||
|
||
// Check if resource exists
|
||
exists, err := resourceExists(client, baseURL, apiKey, projectID, envID, resource.Name)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if exists {
|
||
return updateResourceAPI(client, baseURL, apiKey, projectID, envID, resource.Name, resource.Description, actions)
|
||
}
|
||
return createResourceAPI(client, baseURL, apiKey, projectID, envID, resource.Name, resource.Description, actions)
|
||
}
|
||
|
||
// resourceExists checks if a resource already exists in Permit.io
|
||
func resourceExists(client *http.Client, baseURL, apiKey, projectID, envID, resourceName string) (bool, error) {
|
||
url := fmt.Sprintf("%s/v2/schema/%s/%s/resources/%s", baseURL, projectID, envID, resourceName)
|
||
req, err := http.NewRequest("GET", url, nil)
|
||
if err != nil {
|
||
return false, fmt.Errorf("error creating GET request: %w", err)
|
||
}
|
||
|
||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return false, fmt.Errorf("error making GET request: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode == http.StatusOK {
|
||
return true, nil
|
||
}
|
||
if resp.StatusCode == http.StatusNotFound {
|
||
return false, nil
|
||
}
|
||
|
||
body, _ := io.ReadAll(resp.Body)
|
||
return false, fmt.Errorf("unexpected response (status %d): %s", resp.StatusCode, string(body))
|
||
}
|
||
|
||
// updateResourceAPI updates an existing resource in Permit.io
|
||
func updateResourceAPI(client *http.Client, baseURL, apiKey, projectID, envID, name, description string, actions map[string]ActionDef) error {
|
||
fmt.Println(" ℹ️ Resource exists, updating...")
|
||
|
||
payload := ResourceUpdateRequest{
|
||
Name: name,
|
||
Description: description,
|
||
Actions: actions,
|
||
}
|
||
|
||
jsonData, err := json.Marshal(payload)
|
||
if err != nil {
|
||
return fmt.Errorf("error marshaling update data: %w", err)
|
||
}
|
||
|
||
url := fmt.Sprintf("%s/v2/schema/%s/%s/resources/%s", baseURL, projectID, envID, name)
|
||
req, err := http.NewRequest("PATCH", url, bytes.NewBuffer(jsonData))
|
||
if err != nil {
|
||
return fmt.Errorf("error creating PATCH request: %w", err)
|
||
}
|
||
|
||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return fmt.Errorf("error making PATCH request: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, _ := io.ReadAll(resp.Body)
|
||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
||
}
|
||
|
||
fmt.Println(" ✓ Resource updated successfully")
|
||
return nil
|
||
}
|
||
|
||
// createResourceAPI creates a new resource in Permit.io
|
||
func createResourceAPI(client *http.Client, baseURL, apiKey, projectID, envID, name, description string, actions map[string]ActionDef) error {
|
||
fmt.Println(" ℹ️ Resource does not exist, creating...")
|
||
|
||
payload := ResourceCreateRequest{
|
||
Key: name,
|
||
Name: name,
|
||
Description: description,
|
||
Actions: actions,
|
||
}
|
||
|
||
jsonData, err := json.Marshal(payload)
|
||
if err != nil {
|
||
return fmt.Errorf("error marshaling resource data: %w", err)
|
||
}
|
||
|
||
url := fmt.Sprintf("%s/v2/schema/%s/%s/resources", baseURL, projectID, envID)
|
||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||
if err != nil {
|
||
return fmt.Errorf("error creating POST request: %w", err)
|
||
}
|
||
|
||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return fmt.Errorf("error making POST request: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, _ := io.ReadAll(resp.Body)
|
||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
||
}
|
||
|
||
fmt.Println(" ✓ Resource created successfully")
|
||
return nil
|
||
}
|
||
|
||
// createRole creates or updates a role with its permissions in Permit.io (idempotent)
|
||
func createRole(client *http.Client, baseURL, apiKey, projectID, envID string, role Role, dryRun bool) error {
|
||
fmt.Printf("\nConfiguring role: %s\n", role.Name)
|
||
fmt.Printf(" Description: %s\n", role.Description)
|
||
|
||
// Build permissions list in format "resource:action"
|
||
permissions := buildPermissionsList(role.Permissions)
|
||
fmt.Printf(" Permissions: %v\n", permissions)
|
||
|
||
if dryRun {
|
||
fmt.Println(" [DRY RUN] Would create/update role")
|
||
return nil
|
||
}
|
||
|
||
// Check if role exists
|
||
exists, err := roleExists(client, baseURL, apiKey, projectID, envID, role.Name)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if exists {
|
||
return updateRoleAPI(client, baseURL, apiKey, projectID, envID, role.Name, role.Description, permissions)
|
||
}
|
||
return createRoleAPI(client, baseURL, apiKey, projectID, envID, role.Name, role.Description, permissions)
|
||
}
|
||
|
||
// buildPermissionsList converts role permissions map to permission strings
|
||
func buildPermissionsList(permissions map[string][]string) []string {
|
||
var result []string
|
||
for resourceName, actions := range permissions {
|
||
for _, action := range actions {
|
||
result = append(result, fmt.Sprintf("%s:%s", resourceName, action))
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// roleExists checks if a role already exists in Permit.io
|
||
func roleExists(client *http.Client, baseURL, apiKey, projectID, envID, roleName string) (bool, error) {
|
||
url := fmt.Sprintf("%s/v2/schema/%s/%s/roles/%s", baseURL, projectID, envID, roleName)
|
||
req, err := http.NewRequest("GET", url, nil)
|
||
if err != nil {
|
||
return false, fmt.Errorf("error creating GET request: %w", err)
|
||
}
|
||
|
||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return false, fmt.Errorf("error making GET request: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode == http.StatusOK {
|
||
return true, nil
|
||
}
|
||
if resp.StatusCode == http.StatusNotFound {
|
||
return false, nil
|
||
}
|
||
|
||
body, _ := io.ReadAll(resp.Body)
|
||
return false, fmt.Errorf("unexpected response (status %d): %s", resp.StatusCode, string(body))
|
||
}
|
||
|
||
// updateRoleAPI updates an existing role in Permit.io
|
||
func updateRoleAPI(client *http.Client, baseURL, apiKey, projectID, envID, name, description string, permissions []string) error {
|
||
fmt.Println(" ℹ️ Role exists, updating...")
|
||
|
||
payload := RoleUpdateRequest{
|
||
Name: name,
|
||
Description: description,
|
||
Permissions: permissions,
|
||
}
|
||
|
||
jsonData, err := json.Marshal(payload)
|
||
if err != nil {
|
||
return fmt.Errorf("error marshaling update data: %w", err)
|
||
}
|
||
|
||
url := fmt.Sprintf("%s/v2/schema/%s/%s/roles/%s", baseURL, projectID, envID, name)
|
||
req, err := http.NewRequest("PATCH", url, bytes.NewBuffer(jsonData))
|
||
if err != nil {
|
||
return fmt.Errorf("error creating PATCH request: %w", err)
|
||
}
|
||
|
||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return fmt.Errorf("error making PATCH request: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, _ := io.ReadAll(resp.Body)
|
||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
||
}
|
||
|
||
fmt.Println(" ✓ Role updated successfully")
|
||
return nil
|
||
}
|
||
|
||
// createRoleAPI creates a new role in Permit.io
|
||
func createRoleAPI(client *http.Client, baseURL, apiKey, projectID, envID, name, description string, permissions []string) error {
|
||
fmt.Println(" ℹ️ Role does not exist, creating...")
|
||
|
||
payload := RoleCreateRequest{
|
||
Key: name,
|
||
Name: name,
|
||
Description: description,
|
||
Permissions: permissions,
|
||
}
|
||
|
||
jsonData, err := json.Marshal(payload)
|
||
if err != nil {
|
||
return fmt.Errorf("error marshaling role data: %w", err)
|
||
}
|
||
|
||
url := fmt.Sprintf("%s/v2/schema/%s/%s/roles", baseURL, projectID, envID)
|
||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||
if err != nil {
|
||
return fmt.Errorf("error creating POST request: %w", err)
|
||
}
|
||
|
||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return fmt.Errorf("error making POST request: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, _ := io.ReadAll(resp.Body)
|
||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
||
}
|
||
|
||
fmt.Println(" ✓ Role created successfully")
|
||
return nil
|
||
}
|
||
|
||
// listProjectsAndEnvironments lists all available projects and environments for the API key
|
||
func listProjectsAndEnvironments(client *http.Client, baseURL, apiKey string) {
|
||
fmt.Println("Fetching available projects and environments...")
|
||
fmt.Println(strings.Repeat("=", 70))
|
||
|
||
projects, err := fetchProjects(client, baseURL, apiKey)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "Error fetching projects: %v\n", err)
|
||
return
|
||
}
|
||
|
||
if len(projects) == 0 {
|
||
fmt.Println("No projects found or unable to list projects.")
|
||
fmt.Println("Your API key might be environment-scoped.")
|
||
return
|
||
}
|
||
|
||
fmt.Printf("\nFound %d project(s):\n\n", len(projects))
|
||
|
||
for i, proj := range projects {
|
||
printProject(i+1, proj)
|
||
printProjectEnvironments(client, baseURL, apiKey, proj)
|
||
fmt.Println(strings.Repeat("-", 60))
|
||
}
|
||
|
||
fmt.Println("\nUsage:")
|
||
fmt.Println(" Use the 'ID' or 'Key' values with -project and -env flags")
|
||
fmt.Println(" Example: go run setup_permit.go -project <proj_id> -env <env_id> -config permit_policies.yaml")
|
||
}
|
||
|
||
// fetchProjects retrieves the list of projects from Permit.io API
|
||
func fetchProjects(client *http.Client, baseURL, apiKey string) ([]map[string]interface{}, error) {
|
||
url := fmt.Sprintf("%s/v2/projects", baseURL)
|
||
req, err := http.NewRequest("GET", url, nil)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("error creating request: %w", err)
|
||
}
|
||
|
||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("error making request: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, _ := io.ReadAll(resp.Body)
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
fmt.Fprintf(os.Stderr, "API error (status %d): %s\n", resp.StatusCode, string(body))
|
||
fmt.Println("\nYour API key might be environment-scoped.")
|
||
fmt.Println("Check the Permit.io dashboard for your project and environment IDs.")
|
||
return nil, fmt.Errorf("API returned status %d", resp.StatusCode)
|
||
}
|
||
|
||
return parseProjectsResponse(body)
|
||
}
|
||
|
||
// parseProjectsResponse parses the projects API response
|
||
func parseProjectsResponse(body []byte) ([]map[string]interface{}, error) {
|
||
var projects []map[string]interface{}
|
||
if err := json.Unmarshal(body, &projects); err != nil {
|
||
// Try as paginated response
|
||
var pagedResp map[string]interface{}
|
||
if err := json.Unmarshal(body, &pagedResp); err == nil {
|
||
if data, ok := pagedResp["data"].([]interface{}); ok {
|
||
for _, item := range data {
|
||
if proj, ok := item.(map[string]interface{}); ok {
|
||
projects = append(projects, proj)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return projects, nil
|
||
}
|
||
|
||
// printProject prints project information
|
||
func printProject(num int, proj map[string]interface{}) {
|
||
fmt.Printf("Project #%d:\n", num)
|
||
fmt.Printf(" Name: %v\n", proj["name"])
|
||
fmt.Printf(" Key: %v\n", proj["key"])
|
||
fmt.Printf(" ID: %v\n", proj["id"])
|
||
}
|
||
|
||
// printProjectEnvironments fetches and prints environments for a project
|
||
func printProjectEnvironments(client *http.Client, baseURL, apiKey string, proj map[string]interface{}) {
|
||
projID, ok := proj["id"].(string)
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
envs, err := fetchEnvironments(client, baseURL, apiKey, projID)
|
||
if err != nil || len(envs) == 0 {
|
||
return
|
||
}
|
||
|
||
fmt.Println(" Environments:")
|
||
for _, env := range envs {
|
||
fmt.Printf(" - Name: %v\n", env["name"])
|
||
fmt.Printf(" Key: %v\n", env["key"])
|
||
fmt.Printf(" ID: %v\n", env["id"])
|
||
fmt.Println()
|
||
}
|
||
}
|
||
|
||
// fetchEnvironments retrieves environments for a project
|
||
func fetchEnvironments(client *http.Client, baseURL, apiKey, projectID string) ([]map[string]interface{}, error) {
|
||
url := fmt.Sprintf("%s/v2/projects/%s/envs", baseURL, projectID)
|
||
req, err := http.NewRequest("GET", url, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
return nil, fmt.Errorf("status %d", resp.StatusCode)
|
||
}
|
||
|
||
body, _ := io.ReadAll(resp.Body)
|
||
var envs []map[string]interface{}
|
||
if err := json.Unmarshal(body, &envs); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return envs, nil
|
||
}
|