Skip to content

Commit 4a58aa2

Browse files
committed
clean up typing
1 parent d6a64b3 commit 4a58aa2

File tree

13 files changed

+776
-191
lines changed

13 files changed

+776
-191
lines changed

cmd/ctrlc/root/apply/apply.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package apply
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"path/filepath"
8+
9+
"os"
10+
11+
"github.com/charmbracelet/log"
12+
"github.com/ctrlplanedev/cli/internal/api"
13+
"github.com/google/uuid"
14+
"github.com/spf13/cobra"
15+
"github.com/spf13/viper"
16+
17+
"gopkg.in/yaml.v3"
18+
)
19+
20+
// Config represents the structure of the YAML file
21+
type Config struct {
22+
Systems map[string]System `yaml:"systems"`
23+
}
24+
25+
type System struct {
26+
Name string `yaml:"name"`
27+
Description string `yaml:"description"`
28+
Deployments map[string]Deployment `yaml:"deployments"`
29+
}
30+
31+
type Deployment struct {
32+
Name string `yaml:"name"`
33+
Description string `yaml:"description"`
34+
SystemName string `yaml:"systemName"`
35+
Metadata map[string]string `yaml:"metadata"`
36+
}
37+
38+
// NewApplyCommand creates a new apply command
39+
func NewApplyCmd() *cobra.Command {
40+
var filePath string
41+
42+
cmd := &cobra.Command{
43+
Use: "apply",
44+
Short: "Apply a YAML configuration file to create systems and deployments",
45+
Long: `Apply a YAML configuration file to create systems and deployments in Ctrlplane`,
46+
RunE: func(cmd *cobra.Command, args []string) error {
47+
return runApply(filePath)
48+
},
49+
}
50+
51+
cmd.Flags().StringVarP(&filePath, "file", "f", "", "Path to the YAML configuration file (required)")
52+
cmd.MarkFlagRequired("file")
53+
54+
return cmd
55+
}
56+
57+
func runApply(filePath string) error {
58+
ctx := context.Background()
59+
logger := log.FromContext(ctx)
60+
61+
// Read and parse the YAML file
62+
config, err := readConfigFile(filePath)
63+
if err != nil {
64+
return fmt.Errorf("failed to read config file: %w", err)
65+
}
66+
67+
// Create API client
68+
apiURL := viper.GetString("url")
69+
apiKey := viper.GetString("api-key")
70+
workspace := viper.GetString("workspace")
71+
72+
workspaceID, err := uuid.Parse(workspace)
73+
if err != nil {
74+
return fmt.Errorf("invalid workspace ID: %w", err)
75+
}
76+
77+
client, err := api.NewAPIKeyClientWithResponses(apiURL, apiKey)
78+
if err != nil {
79+
return fmt.Errorf("failed to create API client: %w", err)
80+
}
81+
82+
for slug, system := range config.Systems {
83+
logger.Info("Upserting system", "name", system.Name)
84+
85+
systemID, err := upsertSystem(ctx, client, workspaceID, slug, system)
86+
if err != nil {
87+
logger.Error("Failed to upsert system", "name", system.Name, "error", err)
88+
continue
89+
}
90+
logger.Info("System created successfully", "name", system.Name, "id", systemID)
91+
92+
systemIDUUID, err := uuid.Parse(systemID)
93+
if err != nil {
94+
return fmt.Errorf("invalid system ID: %w", err)
95+
}
96+
97+
for deploymentSlug, deployment := range system.Deployments {
98+
logger.Info("Creating deployment", "name", deployment.Name)
99+
_, err := upsertDeployment(ctx, client, systemIDUUID, deploymentSlug, deployment)
100+
if err != nil {
101+
logger.Error("Failed to create deployment", "name", deployment.Name, "error", err)
102+
}
103+
}
104+
}
105+
106+
return nil
107+
}
108+
109+
func readConfigFile(filePath string) (*Config, error) {
110+
// Resolve absolute path
111+
absPath, err := filepath.Abs(filePath)
112+
if err != nil {
113+
return nil, fmt.Errorf("failed to resolve file path: %w", err)
114+
}
115+
116+
// Read file
117+
data, err := os.ReadFile(absPath)
118+
if err != nil {
119+
return nil, fmt.Errorf("failed to read file: %w", err)
120+
}
121+
122+
// Parse YAML
123+
var config Config
124+
if err := yaml.Unmarshal(data, &config); err != nil {
125+
return nil, fmt.Errorf("failed to parse YAML: %w", err)
126+
}
127+
128+
return &config, nil
129+
}
130+
131+
func upsertSystem(
132+
ctx context.Context,
133+
client *api.ClientWithResponses,
134+
workspaceID uuid.UUID,
135+
slug string,
136+
system System,
137+
) (string, error) {
138+
resp, err := client.CreateSystemWithResponse(ctx, api.CreateSystemJSONRequestBody{
139+
Slug: slug,
140+
WorkspaceId: workspaceID,
141+
Name: system.Name,
142+
Description: &system.Description,
143+
})
144+
145+
if err != nil {
146+
return "", fmt.Errorf("API request failed: %w", err)
147+
}
148+
149+
if resp.StatusCode() >= 400 {
150+
return "", fmt.Errorf("API returned error status: %d", resp.StatusCode())
151+
}
152+
153+
if resp.JSON200 != nil {
154+
return resp.JSON200.Id.String(), nil
155+
}
156+
157+
if resp.JSON201 != nil {
158+
return resp.JSON201.Id.String(), nil
159+
}
160+
161+
return "", fmt.Errorf("unexpected response format")
162+
}
163+
164+
func upsertDeployment(
165+
ctx context.Context,
166+
client *api.ClientWithResponses,
167+
systemID uuid.UUID,
168+
deploymentSlug string,
169+
deployment Deployment,
170+
) (string, error) {
171+
resp, err := client.CreateDeploymentWithResponse(ctx, api.CreateDeploymentJSONRequestBody{
172+
SystemId: systemID,
173+
Slug: deploymentSlug,
174+
Name: deployment.Name,
175+
Description: &deployment.Description,
176+
})
177+
178+
if err != nil {
179+
return "", fmt.Errorf("API request failed: %w", err)
180+
}
181+
182+
if resp.StatusCode() >= 400 {
183+
return "", fmt.Errorf("API returned error status: %d", resp.StatusCode())
184+
}
185+
186+
if resp.JSON200 != nil {
187+
return resp.JSON200.Id.String(), nil
188+
}
189+
190+
if resp.JSON201 != nil {
191+
return resp.JSON201.Id.String(), nil
192+
}
193+
194+
return "", fmt.Errorf("unexpected response format")
195+
}

cmd/ctrlc/root/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/charmbracelet/log"
77
"github.com/ctrlplanedev/cli/cmd/ctrlc/root/agent"
88
"github.com/ctrlplanedev/cli/cmd/ctrlc/root/api"
9+
"github.com/ctrlplanedev/cli/cmd/ctrlc/root/apply"
910
"github.com/ctrlplanedev/cli/cmd/ctrlc/root/config"
1011
"github.com/ctrlplanedev/cli/cmd/ctrlc/root/run"
1112
"github.com/ctrlplanedev/cli/cmd/ctrlc/root/sync"
@@ -50,6 +51,7 @@ func NewRootCmd() *cobra.Command {
5051
cmd.AddCommand(sync.NewSyncCmd())
5152
cmd.AddCommand(run.NewRunCmd())
5253
cmd.AddCommand(version.NewVersionCmd())
54+
cmd.AddCommand(apply.NewApplyCmd())
5355

5456
return cmd
5557
}

cmd/ctrlc/root/sync/aws/common/regions.go

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -81,33 +81,32 @@ func GetAccountID(ctx context.Context, cfg aws.Config) (string, error) {
8181
func InitAWSConfig(ctx context.Context, region string) (aws.Config, error) {
8282
// Try to load AWS config with explicit credentials
8383
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region))
84-
if err != nil {
85-
log.Warn("LoadDefaultConfig failed, falling back to shared profile", "error", err)
86-
cfg, err = config.LoadDefaultConfig(ctx,
87-
config.WithRegion(region),
88-
config.WithSharedConfigProfile("default"),
89-
)
90-
if err != nil {
91-
return aws.Config{}, fmt.Errorf("failed to load AWS config: %w", err)
92-
}
93-
}
94-
95-
if roleArn := os.Getenv("AWS_ROLE_ARN"); roleArn != "" {
96-
stsClient := sts.NewFromConfig(cfg)
97-
sessName := os.Getenv("AWS_ROLE_SESSION_NAME")
98-
if sessName == "" {
99-
sessName = "aws-sdk-go-session"
100-
}
101-
102-
cfg.Credentials = aws.NewCredentialsCache(
103-
stscreds.NewAssumeRoleProvider(stsClient, roleArn, func(o *stscreds.AssumeRoleOptions) {
104-
o.RoleSessionName = sessName
105-
// o.Duration can be tweaked here if you need longer-lived tokens
106-
}),
107-
)
108-
log.Info("Configured STS AssumeRole", "role_arn", roleArn, "session", sessName)
109-
}
84+
if err != nil {
85+
log.Warn("LoadDefaultConfig failed, falling back to shared profile", "error", err)
86+
cfg, err = config.LoadDefaultConfig(ctx,
87+
config.WithRegion(region),
88+
config.WithSharedConfigProfile("default"),
89+
)
90+
if err != nil {
91+
return aws.Config{}, fmt.Errorf("failed to load AWS config: %w", err)
92+
}
93+
}
11094

95+
if roleArn := os.Getenv("AWS_ROLE_ARN"); roleArn != "" {
96+
stsClient := sts.NewFromConfig(cfg)
97+
sessName := os.Getenv("AWS_ROLE_SESSION_NAME")
98+
if sessName == "" {
99+
sessName = "aws-sdk-go-session"
100+
}
101+
102+
cfg.Credentials = aws.NewCredentialsCache(
103+
stscreds.NewAssumeRoleProvider(stsClient, roleArn, func(o *stscreds.AssumeRoleOptions) {
104+
o.RoleSessionName = sessName
105+
// o.Duration can be tweaked here if you need longer-lived tokens
106+
}),
107+
)
108+
log.Info("Configured STS AssumeRole", "role_arn", roleArn, "session", sessName)
109+
}
111110

112111
// Verify credentials are valid before proceeding
113112
credentials, err := cfg.Credentials.Retrieve(ctx)

cmd/ctrlc/root/sync/aws/eks/eks.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ var relationshipRules = []api.CreateResourceRelationshipRule{
315315
TargetKind: "ctrlplane.dev/network/v1",
316316
TargetVersion: "AWSNetwork",
317317

318-
MetadataKeysMatch: []string{"aws/region", "network/name"},
318+
MetadataKeysMatch: &[]string{"aws/region", "network/name"},
319319
},
320320
}
321321

cmd/ctrlc/root/sync/aws/rds/rds.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ var relationshipRules = []api.CreateResourceRelationshipRule{
467467
TargetKind: "ctrlplane.dev/network/v1",
468468
TargetVersion: "AWSNetwork",
469469

470-
MetadataKeysMatch: []string{"aws/region", "network/name"},
470+
MetadataKeysMatch: &[]string{"aws/region", "network/name"},
471471
},
472472
}
473473

cmd/ctrlc/root/sync/azure/aks/aks.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,12 @@ func getTenantIDFromEnv() string {
128128
if tenantID := os.Getenv("AZURE_TENANT_ID"); tenantID != "" {
129129
return tenantID
130130
}
131-
131+
132132
// Check viper config
133133
if tenantID := viper.GetString("azure.tenant-id"); tenantID != "" {
134134
return tenantID
135135
}
136-
136+
137137
return ""
138138
}
139139

@@ -221,7 +221,7 @@ func processCluster(_ context.Context, cluster *armcontainerservice.ManagedClust
221221

222222
certificateAuthorityData := ""
223223
// The Azure SDK may not expose KubeConfig directly, we'll handle this gracefully
224-
224+
225225
endpoint := ""
226226
if cluster.Properties.PrivateFQDN != nil {
227227
endpoint = *cluster.Properties.PrivateFQDN
@@ -292,14 +292,14 @@ func initClusterMetadata(cluster *armcontainerservice.ManagedCluster, subscripti
292292
"kubernetes/location": *cluster.Location,
293293
"kubernetes/endpoint": getEndpoint(cluster),
294294

295-
"azure/subscription": subscriptionID,
296-
"azure/tenant": tenantID,
297-
"azure/resource-group": resourceGroup,
298-
"azure/resource-type": "Microsoft.ContainerService/managedClusters",
299-
"azure/location": *cluster.Location,
300-
"azure/status": *cluster.Properties.ProvisioningState,
301-
"azure/id": *cluster.ID,
302-
"azure/console-url": fmt.Sprintf("https://portal.azure.com/#@/resource%s", *cluster.ID),
295+
"azure/subscription": subscriptionID,
296+
"azure/tenant": tenantID,
297+
"azure/resource-group": resourceGroup,
298+
"azure/resource-type": "Microsoft.ContainerService/managedClusters",
299+
"azure/location": *cluster.Location,
300+
"azure/status": *cluster.Properties.ProvisioningState,
301+
"azure/id": *cluster.ID,
302+
"azure/console-url": fmt.Sprintf("https://portal.azure.com/#@/resource%s", *cluster.ID),
303303
}
304304

305305
// Process creation time if available
@@ -310,20 +310,20 @@ func initClusterMetadata(cluster *armcontainerservice.ManagedCluster, subscripti
310310
// Add node pool information
311311
if cluster.Properties.AgentPoolProfiles != nil {
312312
metadata["kubernetes/node-pool-count"] = strconv.Itoa(len(cluster.Properties.AgentPoolProfiles))
313-
313+
314314
totalNodeCount := 0
315315
for i, pool := range cluster.Properties.AgentPoolProfiles {
316316
metadata[fmt.Sprintf("kubernetes/node-pool/%d/name", i)] = *pool.Name
317317
metadata[fmt.Sprintf("kubernetes/node-pool/%d/vm-size", i)] = *pool.VMSize
318318
metadata[fmt.Sprintf("kubernetes/node-pool/%d/os-type", i)] = string(*pool.OSType)
319319
metadata[fmt.Sprintf("kubernetes/node-pool/%d/mode", i)] = string(*pool.Mode)
320-
320+
321321
if pool.Count != nil {
322322
nodeCount := int(*pool.Count)
323323
metadata[fmt.Sprintf("kubernetes/node-pool/%d/count", i)] = strconv.Itoa(nodeCount)
324324
totalNodeCount += nodeCount
325325
}
326-
326+
327327
if pool.EnableAutoScaling != nil && *pool.EnableAutoScaling {
328328
metadata[fmt.Sprintf("kubernetes/node-pool/%d/autoscaling", i)] = "enabled"
329329
if pool.MinCount != nil {
@@ -335,12 +335,12 @@ func initClusterMetadata(cluster *armcontainerservice.ManagedCluster, subscripti
335335
} else {
336336
metadata[fmt.Sprintf("kubernetes/node-pool/%d/autoscaling", i)] = "disabled"
337337
}
338-
338+
339339
if pool.OSDiskSizeGB != nil {
340340
metadata[fmt.Sprintf("kubernetes/node-pool/%d/os-disk-size-gb", i)] = strconv.Itoa(int(*pool.OSDiskSizeGB))
341341
}
342342
}
343-
343+
344344
metadata["kubernetes/total-node-count"] = strconv.Itoa(totalNodeCount)
345345
}
346346

@@ -398,7 +398,7 @@ func getEndpoint(cluster *armcontainerservice.ManagedCluster) string {
398398
}
399399

400400
func extractResourceGroupFromID(id string) string {
401-
// The format of the resource ID is:
401+
// The format of the resource ID is:
402402
// /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ContainerService/managedClusters/{clusterName}
403403
parts := strings.Split(id, "/")
404404
for i, part := range parts {
@@ -420,7 +420,7 @@ var relationshipRules = []api.CreateResourceRelationshipRule{
420420
TargetKind: "AzureNetwork",
421421
TargetVersion: "ctrlplane.dev/network/v1",
422422

423-
MetadataKeysMatch: []string{"azure/subscription", "azure/resource-group"},
423+
MetadataKeysMatch: &[]string{"azure/subscription", "azure/resource-group"},
424424
},
425425
}
426426

@@ -455,4 +455,4 @@ func upsertToCtrlplane(ctx context.Context, resources []api.AgentResource, subsc
455455

456456
log.Info("Response from upserting resources", "status", upsertResp.Status)
457457
return nil
458-
}
458+
}

0 commit comments

Comments
 (0)