Skip to content

Conversation

@adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Jan 15, 2025

Summary by CodeRabbit

  • New Features

    • Added synchronization functionality for Terraform workspaces.
    • Introduced new CLI command to sync Terraform organization resources.
    • Enhanced CLI with Terraform workspace management capabilities.
    • New Docker image for the Ctrlplane CLI with installation instructions.
  • Dependencies

    • Updated and added multiple Go dependencies for improved functionality.
    • Added Terraform-related libraries for API interactions.
  • Improvements

    • Expanded command structure to support more comprehensive resource management.
    • Implemented retry mechanisms for workspace retrieval.
    • Added support for workspace metadata and configuration extraction.
    • Enhanced GitHub Actions workflow for Docker image builds and pushes.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2025

Warning

Rate limit exceeded

@adityachoudhari26 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 16 minutes and 12 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 1e7c8b2 and 4450cb2.

📒 Files selected for processing (1)
  • .github/workflows/release.yaml (1 hunks)

Walkthrough

This pull request introduces a new synchronization feature for the CLI, focusing on Terraform workspace management. The changes involve adding a new sync command to the root command structure, creating a Terraform-specific synchronization mechanism. The implementation includes functionality to retrieve, process, and synchronize Terraform workspaces from a specified organization into the Ctrlplane system, with robust error handling and retry mechanisms. Additionally, updates to the dependency management and the introduction of a Dockerfile enhance the overall functionality and deployment options.

Changes

File Change Summary
cmd/ctrlc/root/root.go Added import for sync package and new sync command
cmd/ctrlc/root/sync/sync.go Renamed NewRootCmd to NewSyncCmd, added Terraform sync command
cmd/ctrlc/root/sync/terraform/terraform.go Created new NewSyncTerraformCmd() for Terraform workspace synchronization
cmd/ctrlc/root/sync/terraform/terraform-org-workspaces.go Added comprehensive workspace management functions
go.mod Updated dependencies, added Terraform and retry-related packages
.github/workflows/release.yaml Added new Docker job to GitHub Actions workflow
docker/Dockerfile Introduced Dockerfile for building CLI Docker image
docker/README.md Added documentation for the Docker image and usage instructions

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Sync Command
    participant TFClient as Terraform Client
    participant CPClient as Ctrlplane API
    
    CLI->>TFClient: Retrieve Workspaces
    TFClient-->>CLI: Return Workspace List
    CLI->>CPClient: Upsert Resource Provider
    CPClient-->>CLI: Provider ID
    CLI->>CPClient: Set Resources
    CPClient-->>CLI: Synchronization Result
Loading

Poem

🐰 Sync, sync, hop along the line,
Terraform workspaces now align!
From org to Ctrlplane, we dance and weave,
Resources synced with rabbit's reprieve!
Code hops, data flows, magic divine! 🚀


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
cmd/ctrlc/root/sync/sync.go (1)

Line range hint 13-17: Update example documentation to include TFE sync command.

The example documentation shows aws-eks and google-gke but doesn't include the newly added tfe subcommand. Please update it to include a TFE example:

 Example: heredoc.Doc(`
     $ ctrlc sync aws-eks
     $ ctrlc sync google-gke
+    $ ctrlc sync tfe --organization my-org
 `),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e10ea9 and 22614a1.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • cmd/ctrlc/root/root.go (2 hunks)
  • cmd/ctrlc/root/sync/sync.go (2 hunks)
  • cmd/ctrlc/root/sync/tfe/tfe-org-workspaces.go (1 hunks)
  • cmd/ctrlc/root/sync/tfe/tfe.go (1 hunks)
  • go.mod (2 hunks)
🔇 Additional comments (4)
cmd/ctrlc/root/root.go (1)

10-10: LGTM!

The sync command is properly integrated into the root command structure.

Also applies to: 48-48

go.mod (3)

27-33: LGTM! HashiCorp dependencies are appropriate for TFE integration.

The addition of HashiCorp's official go-tfe client and its supporting libraries is the correct approach for implementing TFE synchronization functionality.


Line range hint 26-57: Verify security advisories for new dependencies.

Let's check for any known security vulnerabilities in the newly added dependencies.

✅ Verification successful

No security vulnerabilities found in the new dependencies

All checked dependencies are currently free from known security vulnerabilities in GitHub's Security Advisory database.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for security advisories in the new dependencies

# Check GitHub Security Advisories for the new dependencies
for pkg in "google/go-querystring" "hashicorp/go-cleanhttp" "hashicorp/go-retryablehttp" "hashicorp/go-slug" "hashicorp/go-tfe" "hashicorp/go-version" "hashicorp/jsonapi"; do
  echo "Checking $pkg..."
  gh api graphql -f query='
  {
    securityVulnerabilities(first: 5, ecosystem: GO, package: "'$pkg'") {
      nodes {
        advisory {
          summary
          severity
          publishedAt
        }
        vulnerableVersionRange
        firstPatchedVersion {
          identifier
        }
      }
    }
  }'
done

Length of output: 4407


26-26: Verify the compatibility of utility dependencies.

The new utility dependencies look appropriate for the task:

  • go-querystring: Useful for API requests
  • golang.org/x/sync: Essential for synchronization operations
  • golang.org/x/time: Helpful for rate limiting API calls

However, let's verify the compatibility of these versions with the project's Go version (1.22.1).

Also applies to: 54-54, 55-55, 57-57

✅ Verification successful

Dependencies are compatible with Go 1.22.1

The utility dependencies are verified to be compatible:

  • go-querystring v1.1.0 is a stable release from Google
  • golang.org/x/sync and golang.org/x/time are official Go packages designed for cross-version compatibility
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the dependencies are compatible with Go 1.22.1

# Check go.mod files in these dependencies for their Go version requirements
for pkg in "github.com/google/go-querystring" "golang.org/x/sync" "golang.org/x/time"; do
  echo "Checking $pkg..."
  gh api repos/${pkg/github.com\//}/contents/go.mod --raw 2>/dev/null | grep -E "^go [0-9]+\.[0-9]+(\.[0-9]+)?$" || echo "go.mod not found for $pkg"
done

Length of output: 1089


Script:

#!/bin/bash
# Get detailed information about the dependencies
echo "Checking go-querystring..."
go list -m -json github.com/google/go-querystring@v1.1.0 2>/dev/null || echo "Failed to fetch go-querystring info"

echo -e "\nChecking golang.org/x/sync..."
go list -m -json golang.org/x/sync 2>/dev/null || echo "Failed to fetch sync info"

echo -e "\nChecking golang.org/x/time..."
go list -m -json golang.org/x/time 2>/dev/null || echo "Failed to fetch time info"

Length of output: 548

Comment on lines 54 to 62
func getWorkspaceVcsRepo(workspace *tfe.Workspace) map[string]string {
vcsRepo := make(map[string]string)
if workspace.VCSRepo != nil {
vcsRepo["terraform-cloud/vcs-repo/identifier"] = workspace.VCSRepo.Identifier
vcsRepo["terraform-cloud/vcs-repo/branch"] = workspace.VCSRepo.Branch
vcsRepo["terraform-cloud/vcs-repo/repository-http-url"] = workspace.VCSRepo.RepositoryHTTPURL
}
return vcsRepo
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should create a new struct call WorkspaceResource or something that "extends the tfe.Workspace and add these functions to that

if workspace == nil {
return WorkspaceResource{}, fmt.Errorf("workspace is nil")
}
version := workspace.TerraformVersion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the version of the Kind, which is also static

Comment on lines 97 to 110
variables := getWorkspaceVariables(workspace)
for key, value := range variables {
metadata[key] = value
}

tags := getWorkspaceTags(workspace)
for key, value := range tags {
metadata[key] = value
}

vcsRepo := getWorkspaceVcsRepo(workspace)
for key, value := range vcsRepo {
metadata[key] = value
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simplify

moreValues := []map[string]string{
	getWorkspaceVariables(workspace),
	getWorkspaceTags(workspace),
	getWorkspaceVcsRepo(workspace),
}

for k, v := range moreValues {
   metdata[k] = v
 }

Comment on lines 132 to 133
workspaces := items.Items
return workspaces, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
workspaces := items.Items
return workspaces, nil
return items.Items, nil


func getWorkspacesInOrg(client *tfe.Client, organization string) ([]*tfe.Workspace, error) {

// TODO: use cmd context
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none is going to ever fix this, just delete the comment or do it the right way. not hard to pass in ctx

Comment on lines 51 to 56
tfeClient, err := tfe.NewClient(tfe.DefaultConfig())
if err != nil {
return fmt.Errorf("failed to create TFE client: %w", err)
}

return nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tfeClient, err := tfe.NewClient(tfe.DefaultConfig())
if err != nil {
return fmt.Errorf("failed to create TFE client: %w", err)
}
return nil
if _, err := tfe.NewClient(tfe.DefaultConfig()); err != nil {
return fmt.Errorf("failed to create TFE client: %w", err)
}
return nil

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
cmd/ctrlc/root/sync/terraform/terraform.go (3)

16-16: Rename workspaceId to workspaceID for Go naming convention consistency.

According to Go naming conventions, acronyms and initialisms should be capitalized. Renaming workspaceId to workspaceID aligns with these conventions and improves code readability.

Apply this diff and update all occurrences:

-var workspaceId string
+var workspaceID string

62-62: Rename providerId to providerID for Go naming convention consistency.

As per Go conventions, Id should be uppercased as ID. This change enhances code clarity and adheres to standard practices.

Apply this diff and update all occurrences:

-providerId := resp.JSON200.Id
+providerID := resp.JSON200.Id

69-76: Define a named type for the resource struct to improve readability and reusability.

The inline definition of the resource struct can be replaced with a named type for better clarity and potential reuse elsewhere in the codebase.

Define the struct as a named type:

type Resource struct {
    Config     map[string]interface{} `json:"config"`
    Identifier string                 `json:"identifier"`
    Kind       string                 `json:"kind"`
    Metadata   map[string]string      `json:"metadata"`
    Name       string                 `json:"name"`
    Version    string                 `json:"version"`
}

Then update the code to use the new type:

-resources := []struct {
-    Config     map[string]interface{} `json:"config"`
-    Identifier string                 `json:"identifier"`
-    Kind       string                 `json:"kind"`
-    Metadata   map[string]string      `json:"metadata"`
-    Name       string                 `json:"name"`
-    Version    string                 `json:"version"`
-}{}
+resources := []Resource{}

And within the loop:

-resource := struct {
-    Config     map[string]interface{} `json:"config"`
-    Identifier string                 `json:"identifier"`
-    Kind       string                 `json:"kind"`
-    Metadata   map[string]string      `json:"metadata"`
-    Name       string                 `json:"name"`
-    Version    string                 `json:"version"`
-}{
+resource := Resource{
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22614a1 and 771f299.

📒 Files selected for processing (4)
  • cmd/ctrlc/root/sync/sync.go (1 hunks)
  • cmd/ctrlc/root/sync/terraform/terraform-org-workspaces.go (1 hunks)
  • cmd/ctrlc/root/sync/terraform/terraform.go (1 hunks)
  • go.mod (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • cmd/ctrlc/root/sync/sync.go
  • go.mod

Comment on lines 33 to 41
fmt.Println("Syncing Terraform resources into Ctrlplane")
apiURL := viper.GetString("url")
apiKey := viper.GetString("api-key")
ctx := cmd.Context()
if _, err := uuid.Parse(workspaceId); err != nil {
return fmt.Errorf("invalid workspace ID: %w", err)
}

ctrlplaneClient, err := api.NewAPIKeyClientWithResponses(apiURL, apiKey)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for the 'organization' flag to prevent empty values.

Currently, there is no validation to ensure that the --organization flag is provided. If it's empty, this can cause errors when constructing the provider name or fetching workspaces.

Apply this diff to add the validation:

 ctx := cmd.Context()
+if organization == "" {
+    return fmt.Errorf("organization name must be provided")
+}
 if _, err := uuid.Parse(workspaceId); err != nil {
     return fmt.Errorf("invalid workspace ID: %w", err)
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 35 to 45
apiKey := viper.GetString("api-key")
ctx := cmd.Context()
if _, err := uuid.Parse(workspaceId); err != nil {
return fmt.Errorf("invalid workspace ID: %w", err)
}

ctrlplaneClient, err := api.NewAPIKeyClientWithResponses(apiURL, apiKey)
if err != nil {
return fmt.Errorf("failed to create API client: %w", err)
}

Copy link
Contributor

@coderabbitai coderabbitai bot Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation for 'apiURL' and 'apiKey' to ensure they are set.

There's no explicit check to ensure that apiURL and apiKey are not empty before creating the API client. If these values are not set, api.NewAPIKeyClientWithResponses might fail unexpectedly.

Consider adding validation:

 apiURL := viper.GetString("url")
 apiKey := viper.GetString("api-key")
+if apiURL == "" || apiKey == "" {
+    return fmt.Errorf("API URL and API Key must be configured")
+}
 ctx := cmd.Context()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
apiKey := viper.GetString("api-key")
ctx := cmd.Context()
if _, err := uuid.Parse(workspaceId); err != nil {
return fmt.Errorf("invalid workspace ID: %w", err)
}
ctrlplaneClient, err := api.NewAPIKeyClientWithResponses(apiURL, apiKey)
if err != nil {
return fmt.Errorf("failed to create API client: %w", err)
}
apiKey := viper.GetString("api-key")
if apiURL == "" || apiKey == "" {
return fmt.Errorf("API URL and API Key must be configured")
}
ctx := cmd.Context()
if _, err := uuid.Parse(workspaceId); err != nil {
return fmt.Errorf("invalid workspace ID: %w", err)
}
ctrlplaneClient, err := api.NewAPIKeyClientWithResponses(apiURL, apiKey)
if err != nil {
return fmt.Errorf("failed to create API client: %w", err)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client will error with invalid creds

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adityachoudhari26 You're right. The client will handle invalid credentials by returning an error, which is already being caught and handled appropriately. I'll withdraw my suggestion.


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/cli#4
File: cmd/ctrlc/root/sync/terraform/terraform.go:35-45
Timestamp: 2025-01-15T17:46:32.055Z
Learning: In the Ctrlplane CLI, the API client (`api.NewAPIKeyClientWithResponses`) handles invalid credentials internally by returning an error, making explicit validation of apiURL and apiKey unnecessary.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!

Comment on lines 89 to 91
"ctrlplane/external-id": workspace.ID,
"terraform-cloud/organization": workspace.Organization.Name,
"terraform-cloud/workspace-name": workspace.Name,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Check for nil workspace.Organization before accessing its Name field.

Accessing workspace.Organization.Name without verifying that workspace.Organization is not nil can lead to a nil pointer dereference and runtime panic.

Add a nil check before accessing workspace.Organization.Name:

 metadata := map[string]string{
     "ctrlplane/external-id": workspace.ID,
-    "terraform-cloud/organization": workspace.Organization.Name,
     "terraform-cloud/workspace-name": workspace.Name,
     "terraform-cloud/workspace-auto-apply": strconv.FormatBool(workspace.AutoApply),
     "terraform/version": workspace.TerraformVersion,
 }
+if workspace.Organization != nil {
+    metadata["terraform-cloud/organization"] = workspace.Organization.Name
+}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 77 to 121
func convertWorkspaceToResource(workspace *tfe.Workspace, baseURL url.URL) (WorkspaceResource, error) {
if workspace == nil {
return WorkspaceResource{}, fmt.Errorf("workspace is nil")
}
version := Version
kind := Kind
name := workspace.Name
identifier := workspace.ID
config := map[string]interface{}{
"workspaceId": workspace.ID,
}
metadata := map[string]string{
"ctrlplane/external-id": workspace.ID,
"terraform-cloud/organization": workspace.Organization.Name,
"terraform-cloud/workspace-name": workspace.Name,
"terraform-cloud/workspace-auto-apply": strconv.FormatBool(workspace.AutoApply),
"terraform/version": workspace.TerraformVersion,
}

linksMetadata := getLinksMetadata(workspace, baseURL)
if linksMetadata != nil {
metadata["ctrlplane/links"] = *linksMetadata
}

moreValues := []map[string]string{
getWorkspaceVariables(workspace),
getWorkspaceTags(workspace),
getWorkspaceVcsRepo(workspace),
}

for _, moreValue := range moreValues {
for key, value := range moreValue {
metadata[key] = value
}
}

return WorkspaceResource{
Version: version,
Kind: kind,
Name: name,
Identifier: identifier,
Config: config,
Metadata: metadata,
}, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle potential nil values and improve error messages in convertWorkspaceToResource.

Ensure that all accessed fields are checked for nil to prevent runtime panics. Additionally, provide more descriptive error messages when failures occur.

Consider enhancing the function as follows:

  • Check for nil workspace.Organization before accessing its fields.
  • Provide more context in error messages.
 func convertWorkspaceToResource(workspace *tfe.Workspace, baseURL url.URL) (WorkspaceResource, error) {
     if workspace == nil {
         return WorkspaceResource{}, fmt.Errorf("workspace is nil")
     }
+    if workspace.Organization == nil {
+        return WorkspaceResource{}, fmt.Errorf("workspace organization is nil for workspace ID: %s", workspace.ID)
+    }
     // ... rest of the function
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func convertWorkspaceToResource(workspace *tfe.Workspace, baseURL url.URL) (WorkspaceResource, error) {
if workspace == nil {
return WorkspaceResource{}, fmt.Errorf("workspace is nil")
}
version := Version
kind := Kind
name := workspace.Name
identifier := workspace.ID
config := map[string]interface{}{
"workspaceId": workspace.ID,
}
metadata := map[string]string{
"ctrlplane/external-id": workspace.ID,
"terraform-cloud/organization": workspace.Organization.Name,
"terraform-cloud/workspace-name": workspace.Name,
"terraform-cloud/workspace-auto-apply": strconv.FormatBool(workspace.AutoApply),
"terraform/version": workspace.TerraformVersion,
}
linksMetadata := getLinksMetadata(workspace, baseURL)
if linksMetadata != nil {
metadata["ctrlplane/links"] = *linksMetadata
}
moreValues := []map[string]string{
getWorkspaceVariables(workspace),
getWorkspaceTags(workspace),
getWorkspaceVcsRepo(workspace),
}
for _, moreValue := range moreValues {
for key, value := range moreValue {
metadata[key] = value
}
}
return WorkspaceResource{
Version: version,
Kind: kind,
Name: name,
Identifier: identifier,
Config: config,
Metadata: metadata,
}, nil
}
func convertWorkspaceToResource(workspace *tfe.Workspace, baseURL url.URL) (WorkspaceResource, error) {
if workspace == nil {
return WorkspaceResource{}, fmt.Errorf("workspace is nil")
}
if workspace.Organization == nil {
return WorkspaceResource{}, fmt.Errorf("workspace organization is nil for workspace ID: %s", workspace.ID)
}
version := Version
kind := Kind
name := workspace.Name
identifier := workspace.ID
config := map[string]interface{}{
"workspaceId": workspace.ID,
}
metadata := map[string]string{
"ctrlplane/external-id": workspace.ID,
"terraform-cloud/organization": workspace.Organization.Name,
"terraform-cloud/workspace-name": workspace.Name,
"terraform-cloud/workspace-auto-apply": strconv.FormatBool(workspace.AutoApply),
"terraform/version": workspace.TerraformVersion,
}
linksMetadata := getLinksMetadata(workspace, baseURL)
if linksMetadata != nil {
metadata["ctrlplane/links"] = *linksMetadata
}
moreValues := []map[string]string{
getWorkspaceVariables(workspace),
getWorkspaceTags(workspace),
getWorkspaceVcsRepo(workspace),
}
for _, moreValue := range moreValues {
for key, value := range moreValue {
metadata[key] = value
}
}
return WorkspaceResource{
Version: version,
Kind: kind,
Name: name,
Identifier: identifier,
Config: config,
Metadata: metadata,
}, nil
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
cmd/ctrlc/root/sync/terraform/terraform-org-workspaces.go (2)

39-41: Use consistent error handling and consider returning errors

In the getLinksMetadata function, when JSON marshaling fails, the error is logged but not returned. This might hide issues that could be important for upstream error handling or for informing the user about what went wrong.

Consider modifying the function to return the error for better error propagation:

 func getLinksMetadata(workspace *tfe.Workspace, baseURL url.URL) (*string, error) {
     if workspace.Organization == nil {
         return nil, nil
     }
     links := map[string]string{
         "Terraform Workspace": fmt.Sprintf("%s/app/%s/workspaces/%s", baseURL.String(), workspace.Organization.Name, workspace.Name),
     }
     linksJSON, err := json.Marshal(links)
     if err != nil {
-        log.Error("Failed to marshal links", "error", err)
-        return nil
+        return nil, fmt.Errorf("failed to marshal links: %w", err)
     }
     linksString := string(linksJSON)
-    return &linksString
+    return &linksString, nil
 }

Update the calling function convertWorkspaceToResource to handle this error:

 linksMetadata, err := getLinksMetadata(workspace, baseURL)
-if linksMetadata != nil {
+if err != nil {
+    return WorkspaceResource{}, fmt.Errorf("failed to get links metadata: %w", err)
+}
+if linksMetadata != nil {
     metadata["ctrlplane/links"] = *linksMetadata
 }

50-53: Ensure variable keys are properly namespaced and handle potential key collisions

In the getWorkspaceVariables function, all variable keys are prefixed with terraform-cloud/variables/, but there could be a risk of key collisions if different variables share the same name across different workspaces.

Consider including the workspace name or ID in the key to uniquely identify variables:

 func getWorkspaceVariables(workspace *tfe.Workspace) map[string]string {
     variables := make(map[string]string)
     for _, variable := range workspace.Variables {
         if variable != nil && variable.Category == tfe.CategoryTerraform && !variable.Sensitive {
-            key := fmt.Sprintf("terraform-cloud/variables/%s", variable.Key)
+            key := fmt.Sprintf("terraform-cloud/variables/%s/%s", workspace.Name, variable.Key)
             variables[key] = variable.Value
         }
     }
     return variables
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 771f299 and 0bd0639.

📒 Files selected for processing (2)
  • cmd/ctrlc/root/sync/terraform/terraform-org-workspaces.go (1 hunks)
  • cmd/ctrlc/root/sync/terraform/terraform.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/ctrlc/root/sync/terraform/terraform.go
🔇 Additional comments (2)
cmd/ctrlc/root/sync/terraform/terraform-org-workspaces.go (2)

95-97: Great job checking for workspace.Organization before accessing its Name field

You have appropriately added a nil check for workspace.Organization before accessing workspace.Organization.Name, which prevents potential nil pointer dereferences.


174-180: [Duplicate Comment] Handle potential nil values and improve error handling in convertWorkspaceToResource

This concern was previously mentioned in past reviews and remains important to ensure robustness.

Comment on lines 70 to 73
for _, tag := range workspace.Tags {
key := fmt.Sprintf("terraform-cloud/tag/%s", tag.Name)
tags[key] = "true"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add nil check for tag to prevent potential nil pointer dereference

In the getWorkspaceTags function, if any of the tag elements in workspace.Tags are nil, accessing tag.Name will cause a nil pointer dereference and lead to a runtime panic.

Apply this diff to add a nil check for tag:

 func getWorkspaceTags(workspace *tfe.Workspace) map[string]string {
     tags := make(map[string]string)
     for _, tag := range workspace.Tags {
+        if tag == nil {
+            continue
+        }
         key := fmt.Sprintf("terraform-cloud/tag/%s", tag.Name)
         tags[key] = "true"
     }
     return tags
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for _, tag := range workspace.Tags {
key := fmt.Sprintf("terraform-cloud/tag/%s", tag.Name)
tags[key] = "true"
}
for _, tag := range workspace.Tags {
if tag == nil {
continue
}
key := fmt.Sprintf("terraform-cloud/tag/%s", tag.Name)
tags[key] = "true"
}

Comment on lines 174 to 180
for _, workspace := range workspaces {
workspaceResource, err := convertWorkspaceToResource(workspace, client.BaseURL())
if err != nil {
return nil, err
}
workspaceResources = append(workspaceResources, workspaceResource)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle errors during workspace conversion to prevent premature termination

In the getWorkspacesInOrg function, if convertWorkspaceToResource returns an error for a single workspace, the entire process stops and returns the error. This means that if one workspace fails to convert, none of the subsequent workspaces are processed, which could hinder the synchronization of valid workspaces.

Consider logging the error and continuing with the next workspace to ensure all valid workspaces are processed:

 for _, workspace := range workspaces {
     workspaceResource, err := convertWorkspaceToResource(workspace, client.BaseURL())
     if err != nil {
-        return nil, err
+        log.Error("Failed to convert workspace to resource", "workspace_id", workspace.ID, "error", err)
+        continue
     }
     workspaceResources = append(workspaceResources, workspaceResource)
 }

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (7)
docker/README.md (2)

15-17: Add version guidance for the Docker image.

Consider adding guidance about available versions or how users can find the latest version. This helps users make informed decisions about which version to use.

 ```sh
 docker pull ctrlplane/cli:v0.1.0

+For a list of available versions, visit our Docker Hub repository at https://hub.docker.com/r/ctrlplane/cli/tags


---

`21-23`: **Enhance run instructions with common command examples.**

The placeholder `[your-command]` might not provide enough guidance. Consider adding examples of common commands to help users get started.

```diff
 ```sh
 docker run ctrlplane/cli ctrlc [your-command]

+Common commands:
+sh +# List available commands +docker run ctrlplane/cli ctrlc --help + +# Get version information +docker run ctrlplane/cli ctrlc version +


</blockquote></details>
<details>
<summary>docker/Dockerfile (1)</summary><blockquote>

`1-3`: **Consider version pinning strategy and default CLI version.**

While using Alpine is good for minimal image size, consider:
1. Pin the Alpine version more specifically (e.g., `3.19.0`) for better reproducibility
2. Provide a default value for `CLI_VERSION` to improve usability

```diff
-FROM alpine:3.19
+FROM alpine:3.19.0
 
 ARG CLI_VERSION
+ARG CLI_VERSION="v0.1.0"  # Provide appropriate default version
.github/workflows/release.yaml (4)

34-42: LGTM! Job definition and permissions are well-structured.

The permissions are correctly scoped, and the repository owner check is consistent with other jobs.

Remove trailing spaces on lines 36 and 38 to fix yamllint warnings.

🧰 Tools
🪛 yamllint (1.35.1)

[error] 36-36: trailing spaces

(trailing-spaces)


[error] 38-38: trailing spaces

(trailing-spaces)


43-46: Consider adding support for arm64 architecture.

The matrix currently only builds for linux/amd64. Consider adding linux/arm64 to support ARM-based systems, which are increasingly common in cloud environments.

   matrix:
     platform: [linux/amd64]
+    # Consider adding: linux/arm64

57-71: Improve shell script robustness.

The Docker Hub authentication logic is well-structured, but the shell script could be more robust.

   - name: Check if Docker Hub secrets are available
     run: |
-      if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then
-        echo "DOCKERHUB_LOGIN=false" >> $GITHUB_ENV
+      if [ -z "${DOCKERHUB_USERNAME}" ] || [ -z "${DOCKERHUB_TOKEN}" ]; then
+        echo "DOCKERHUB_LOGIN=false" >> "${GITHUB_ENV}"
       else
-        echo "DOCKERHUB_LOGIN=true" >> $GITHUB_ENV
+        echo "DOCKERHUB_LOGIN=true" >> "${GITHUB_ENV}"
       fi
     env:
+      DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
+      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
🧰 Tools
🪛 actionlint (1.7.4)

58-58: shellcheck reported issue in this script: SC2086:info:2:35: Double quote to prevent globbing and word splitting

(shellcheck)


58-58: shellcheck reported issue in this script: SC2086:info:4:34: Double quote to prevent globbing and word splitting

(shellcheck)


34-35: Consider adding job dependency.

The docker job should ideally wait for the linux job to complete since it's part of the same release process.

   docker:
     runs-on: ubuntu-latest
+    needs: linux
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0bd0639 and bdf8e63.

📒 Files selected for processing (3)
  • .github/workflows/release.yaml (1 hunks)
  • docker/Dockerfile (1 hunks)
  • docker/README.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
docker/README.md

[uncategorized] ~27-~27: Loose punctuation mark.
Context: ...ronment variables - CTRLPLANE_API_KEY: Your Ctrlplane API key. - `CTRLPLANE_UR...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~28-~28: Loose punctuation mark.
Context: ...our Ctrlplane API key. - CTRLPLANE_URL: The URL of your Ctrlplane instance (e.g...

(UNLIKELY_OPENING_PUNCTUATION)


[style] ~31-~31: Consider a shorter alternative to avoid wordiness.
Context: ...p.ctrlplane.dev`). ### Terraform sync In order to sync Terraform resources into Ctrlplane...

(IN_ORDER_TO_PREMIUM)


[uncategorized] ~34-~34: Loose punctuation mark.
Context: ...ng environment variables: - TFE_TOKEN: Your Terraform Cloud API token. - `TFE_...

(UNLIKELY_OPENING_PUNCTUATION)

🪛 actionlint (1.7.4)
.github/workflows/release.yaml

58-58: shellcheck reported issue in this script: SC2086:info:2:35: Double quote to prevent globbing and word splitting

(shellcheck)


58-58: shellcheck reported issue in this script: SC2086:info:4:34: Double quote to prevent globbing and word splitting

(shellcheck)


74-74: the runner of "docker/metadata-action@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 yamllint (1.35.1)
.github/workflows/release.yaml

[error] 36-36: trailing spaces

(trailing-spaces)


[error] 38-38: trailing spaces

(trailing-spaces)

🔇 Additional comments (1)
.github/workflows/release.yaml (1)

47-56: LGTM! Setup steps follow best practices.

The setup includes all necessary components (checkout, QEMU, Buildx) with recent action versions.

Comment on lines +25 to +29
### Required environment variables

- `CTRLPLANE_API_KEY`: Your Ctrlplane API key.
- `CTRLPLANE_URL`: The URL of your Ctrlplane instance (e.g. `https://app.ctrlplane.dev`).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add examples of passing environment variables to Docker.

Show users how to properly pass the required environment variables to the Docker container.

 ### Required environment variables
 
 - `CTRLPLANE_API_KEY`: Your Ctrlplane API key.
 - `CTRLPLANE_URL`: The URL of your Ctrlplane instance (e.g. `https://app.ctrlplane.dev`).
+
+Example using environment variables:
+```sh
+docker run \
+  -e CTRLPLANE_API_KEY=your-api-key \
+  -e CTRLPLANE_URL=https://app.ctrlplane.dev \
+  ctrlplane/cli ctrlc [command]
+```
+
+Example using a .env file:
+```sh
+docker run --env-file .env ctrlplane/cli ctrlc [command]
+```
🧰 Tools
🪛 LanguageTool

[uncategorized] ~27-~27: Loose punctuation mark.
Context: ...ronment variables - CTRLPLANE_API_KEY: Your Ctrlplane API key. - `CTRLPLANE_UR...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~28-~28: Loose punctuation mark.
Context: ...our Ctrlplane API key. - CTRLPLANE_URL: The URL of your Ctrlplane instance (e.g...

(UNLIKELY_OPENING_PUNCTUATION)

Comment on lines 30 to 39
### Terraform sync

In order to sync Terraform resources into Ctrlplane, you need to set the following environment variables:

- `TFE_TOKEN`: Your Terraform Cloud API token.
- `TFE_ADDRESS` (optional): The URL of your Terraform Cloud instance (e.g. `https://app.terraform.io`). If not set, the default address (`https://app.terraform.io`) is used.

```sh
docker run ctrlplane/cli ctrlc sync terraform --organization my-org --workspace-id 2a7c5560-75c9-4dbe-be74-04ee33bf8188
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance Terraform sync documentation with parameter explanations.

The current example lacks context about the required parameters and how to obtain them.

 ### Terraform sync
 
 In order to sync Terraform resources into Ctrlplane, you need to set the following environment variables:
 
 - `TFE_TOKEN`: Your Terraform Cloud API token.
 - `TFE_ADDRESS` (optional): The URL of your Terraform Cloud instance (e.g. `https://app.terraform.io`). If not set, the default address (`https://app.terraform.io`) is used.
 
+Required parameters:
+- `--organization`: Your Terraform Cloud organization name
+- `--workspace-id`: The ID of the workspace to sync (found in workspace settings or via Terraform Cloud API)
+
 ```sh
-docker run ctrlplane/cli ctrlc sync terraform --organization my-org --workspace-id 2a7c5560-75c9-4dbe-be74-04ee33bf8188
+# Example with all environment variables
+docker run \
+  -e CTRLPLANE_API_KEY=your-api-key \
+  -e CTRLPLANE_URL=https://app.ctrlplane.dev \
+  -e TFE_TOKEN=your-terraform-token \
+  ctrlplane/cli ctrlc sync terraform \
+    --organization my-org \
+    --workspace-id 2a7c5560-75c9-4dbe-be74-04ee33bf8188

+For more information about finding your organization name and workspace ID, visit:
+- Organization: https://app.terraform.io/app/organizations
+- Workspace ID: Navigate to workspace settings in Terraform Cloud UI


<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[style] ~31-~31: Consider a shorter alternative to avoid wordiness.
Context: ...p.ctrlplane.dev`).  ### Terraform sync  In order to sync Terraform resources into Ctrlplane...

(IN_ORDER_TO_PREMIUM)

---

[uncategorized] ~34-~34: Loose punctuation mark.
Context: ...ng environment variables:  - `TFE_TOKEN`: Your Terraform Cloud API token. - `TFE_...

(UNLIKELY_OPENING_PUNCTUATION)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines 5 to 10
RUN apk add --no-cache curl tar && \
curl -L --fail "https://github.com/ctrlplanedev/cli/releases/download/${CLI_VERSION}/ctrlc_Linux_x86_64.tar.gz" -o ctrlc.tar.gz && \
tar xzf ctrlc.tar.gz && \
mv ctrlc /usr/local/bin/ && \
chmod +x /usr/local/bin/ctrlc && \
rm -rf ctrlc.tar.gz
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add checksum verification and improve error handling.

The current installation process lacks security verification and proper error handling:

  1. Add checksum verification for the downloaded artifact
  2. Add version validation
  3. Consider splitting the RUN command for better layer caching
-RUN apk add --no-cache curl tar && \
-  curl -L --fail "https://github.com/ctrlplanedev/cli/releases/download/${CLI_VERSION}/ctrlc_Linux_x86_64.tar.gz" -o ctrlc.tar.gz && \
-  tar xzf ctrlc.tar.gz && \
-  mv ctrlc /usr/local/bin/ && \
-  chmod +x /usr/local/bin/ctrlc && \
-  rm -rf ctrlc.tar.gz
+# Install dependencies
+RUN apk add --no-cache curl tar
+
+# Validate CLI_VERSION
+RUN [ -n "$CLI_VERSION" ] || (echo "CLI_VERSION must be set" && false)
+
+# Download and verify CLI
+RUN curl -L --fail "https://github.com/ctrlplanedev/cli/releases/download/${CLI_VERSION}/ctrlc_Linux_x86_64.tar.gz" -o ctrlc.tar.gz && \
+    curl -L --fail "https://github.com/ctrlplanedev/cli/releases/download/${CLI_VERSION}/checksums.txt" -o checksums.txt && \
+    sha256sum --check --ignore-missing checksums.txt && \
+    tar xzf ctrlc.tar.gz && \
+    mv ctrlc /usr/local/bin/ && \
+    chmod +x /usr/local/bin/ctrlc && \
+    rm -rf ctrlc.tar.gz checksums.txt
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN apk add --no-cache curl tar && \
curl -L --fail "https://github.com/ctrlplanedev/cli/releases/download/${CLI_VERSION}/ctrlc_Linux_x86_64.tar.gz" -o ctrlc.tar.gz && \
tar xzf ctrlc.tar.gz && \
mv ctrlc /usr/local/bin/ && \
chmod +x /usr/local/bin/ctrlc && \
rm -rf ctrlc.tar.gz
# Install dependencies
RUN apk add --no-cache curl tar
# Validate CLI_VERSION
RUN [ -n "$CLI_VERSION" ] || (echo "CLI_VERSION must be set" && false)
# Download and verify CLI
RUN curl -L --fail "https://github.com/ctrlplanedev/cli/releases/download/${CLI_VERSION}/ctrlc_Linux_x86_64.tar.gz" -o ctrlc.tar.gz && \
curl -L --fail "https://github.com/ctrlplanedev/cli/releases/download/${CLI_VERSION}/checksums.txt" -o checksums.txt && \
sha256sum --check --ignore-missing checksums.txt && \
tar xzf ctrlc.tar.gz && \
mv ctrlc /usr/local/bin/ && \
chmod +x /usr/local/bin/ctrlc && \
rm -rf ctrlc.tar.gz checksums.txt

Comment on lines 12 to 14
WORKDIR /app

CMD ["ctrlc", "--help"] No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Remove unused WORKDIR and improve default command

The /app working directory is not utilized anywhere in the container:

  • No files are copied there
  • No operations use this path
  • Can be safely removed

Consider replacing the default help command with a more meaningful message about how to use the container.

🔗 Analysis chain

Clarify the purpose of the working directory.

The /app working directory is set but not utilized. Consider:

  1. Document why this directory is needed
  2. Consider if a default help command is the most useful default

Let's check if this working directory is used elsewhere in the codebase:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for references to /app directory
rg "/app" --type-not dockerfile

Length of output: 66


Script:

#!/bin/bash
# Search for references to /app directory, excluding the Dockerfile
rg "/app" --glob '!docker/Dockerfile'

# Check Dockerfile for COPY/ADD commands
cat docker/Dockerfile

Length of output: 1040

Comment on lines 81 to 84
- name: Build and Push
uses: docker/build-push-action@v6
if: github.ref == 'refs/heads/main' && env.DOCKERHUB_LOGIN == 'true'
with:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect branch condition for release workflow.

This is a release workflow triggered by tags (v*), but the build step checks for the main branch. This condition will prevent the Docker image from being built during releases.

   - name: Build and Push
     uses: docker/build-push-action@v6
-    if: github.ref == 'refs/heads/main' && env.DOCKERHUB_LOGIN == 'true'
+    if: startsWith(github.ref, 'refs/tags/v') && env.DOCKERHUB_LOGIN == 'true'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Build and Push
uses: docker/build-push-action@v6
if: github.ref == 'refs/heads/main' && env.DOCKERHUB_LOGIN == 'true'
with:
- name: Build and Push
uses: docker/build-push-action@v6
if: startsWith(github.ref, 'refs/tags/v') && env.DOCKERHUB_LOGIN == 'true'
with:

env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

docker:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should only do this on main

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the push on main, not the build, we can do that on every branch

Comment on lines 5 to 10
RUN apk add --no-cache curl tar && \
curl -L --fail "https://github.com/ctrlplanedev/cli/releases/download/${CLI_VERSION}/ctrlc_Linux_x86_64.tar.gz" -o ctrlc.tar.gz && \
tar xzf ctrlc.tar.gz && \
mv ctrlc /usr/local/bin/ && \
chmod +x /usr/local/bin/ctrlc && \
rm -rf ctrlc.tar.gz
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider running as a stage to drop curl and tar from the final image
ex:

FROM alpine as Builder
RUN your thing
FROM alpine as Final
COPY --from=Builder /yourthing /usr/bin/yourthing

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
docker/Dockerfile (1)

3-9: ⚠️ Potential issue

Add checksum verification and improve error handling in the builder stage.

The current installation process lacks security verification and proper error handling:

Apply this diff to add checksum verification and improve error handling:

 ARG CLI_VERSION
+
+# Validate CLI_VERSION
+RUN [ -n "$CLI_VERSION" ] || (echo "CLI_VERSION must be set" && false)
 
 RUN apk add --no-cache curl tar && \
-  curl -L --fail "https://github.com/ctrlplanedev/cli/releases/download/${CLI_VERSION}/ctrlc_Linux_x86_64.tar.gz" -o ctrlc.tar.gz && \
-  tar xzf ctrlc.tar.gz && \
-  mv ctrlc /usr/local/bin/ && \
-  chmod +x /usr/local/bin/ctrlc
+    curl -L --fail \
+        "https://github.com/ctrlplanedev/cli/releases/download/${CLI_VERSION}/ctrlc_Linux_x86_64.tar.gz" -o ctrlc.tar.gz && \
+    curl -L --fail \
+        "https://github.com/ctrlplanedev/cli/releases/download/${CLI_VERSION}/checksums.txt" -o checksums.txt && \
+    sha256sum --check --ignore-missing checksums.txt && \
+    tar xzf ctrlc.tar.gz && \
+    mv ctrlc /usr/local/bin/ && \
+    chmod +x /usr/local/bin/ctrlc && \
+    rm -rf ctrlc.tar.gz checksums.txt
🧹 Nitpick comments (7)
cmd/ctrlc/root/sync/terraform/terraform-org-workspaces.go (4)

17-20: Add documentation for constants.

Consider adding documentation comments to explain the purpose and usage of these constants, especially since they are used in the resource conversion process.

+// Kind represents the resource type for Terraform workspaces
 const (
 	Kind    = "Workspace"
+	// Version indicates the API version for Terraform workspace resources
 	Version = "terraform/v1"
 )

22-29: Add struct documentation and field validation.

The struct would benefit from:

  1. Documentation explaining its purpose and usage
  2. Field documentation
  3. Optional: Validation tags for required fields
+// WorkspaceResource represents a Terraform workspace with its configuration and metadata
 type WorkspaceResource struct {
+	// Config contains workspace-specific configuration parameters
 	Config     map[string]interface{}
+	// Identifier is the unique identifier for the workspace
 	Identifier string
+	// Kind indicates the type of resource
 	Kind       string
+	// Metadata contains additional workspace information
 	Metadata   map[string]string
+	// Name is the workspace name
 	Name       string
+	// Version indicates the API version
 	Version    string
 }

79-126: Enhance error context in workspace conversion.

While the function is well-implemented, consider providing more context in the error message when workspace is nil.

 if workspace == nil {
-		return WorkspaceResource{}, fmt.Errorf("workspace is nil")
+		return WorkspaceResource{}, fmt.Errorf("cannot convert nil workspace to resource")
 }

128-185: Consider making retry parameters configurable.

The retry logic is well-implemented, but consider making the retry parameters (attempts, delay, max delay) configurable through function parameters or environment variables for better flexibility in different environments.

+type RetryConfig struct {
+	Attempts  uint
+	Delay     time.Duration
+	MaxDelay  time.Duration
+}
+
+func defaultRetryConfig() RetryConfig {
+	return RetryConfig{
+		Attempts: 5,
+		Delay:    time.Second,
+		MaxDelay: 5 * time.Second,
+	}
+}
+
-func listWorkspacesWithRetry(ctx context.Context, client *tfe.Client, organization string, pageNum, pageSize int) (*tfe.WorkspaceList, error) {
+func listWorkspacesWithRetry(ctx context.Context, client *tfe.Client, organization string, pageNum, pageSize int, retryConfig *RetryConfig) (*tfe.WorkspaceList, error) {
+	if retryConfig == nil {
+		cfg := defaultRetryConfig()
+		retryConfig = &cfg
+	}
 	var workspaces *tfe.WorkspaceList
 	err := retry.Do(
 		func() error {
 			var err error
 			workspaces, err = client.Workspaces.List(ctx, organization, &tfe.WorkspaceListOptions{
 				ListOptions: tfe.ListOptions{
 					PageNumber: pageNum,
 					PageSize:   pageSize,
 				},
 			})
 			return err
 		},
-		retry.Attempts(5),
-		retry.Delay(time.Second),
-		retry.MaxDelay(5*time.Second),
+		retry.Attempts(retryConfig.Attempts),
+		retry.Delay(retryConfig.Delay),
+		retry.MaxDelay(retryConfig.MaxDelay),
 	)
 	return workspaces, err
 }
docker/Dockerfile (1)

11-15: Consider adding a more meaningful default command.

The current default command shows the help message. Consider providing a more descriptive message about container usage.

Apply this diff to improve the default command:

-CMD ["ctrlc", "--help"]
+CMD ["sh", "-c", "echo 'Ctrlplane CLI container. Set CTRLPLANE_API_KEY and CTRLPLANE_URL environment variables before running commands.' && ctrlc --help"]
.github/workflows/release.yaml (2)

83-103: Consolidate build steps and improve conditions.

The build steps can be consolidated to reduce duplication and improve maintainability.

Apply this diff to consolidate the build steps:

-      - name: Build Only
-        uses: docker/build-push-action@v6
-        if: env.DOCKERHUB_LOGIN != 'true'
-        with:
-          push: false
-          file: docker/Dockerfile
-          platforms: ${{ matrix.platform }}
-          tags: ${{ steps.meta.outputs.tags }}
-          build-args: |
-            CLI_VERSION=${{ steps.meta.outputs.version }}
-
-      - name: Build and Push
+      - name: Build and Push Docker image
         uses: docker/build-push-action@v6
-        if: env.DOCKERHUB_LOGIN == 'true'
         with:
-          push: true
+          push: ${{ env.DOCKERHUB_LOGIN == 'true' }}
           file: docker/Dockerfile
           platforms: ${{ matrix.platform }}
           tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
           build-args: |
             CLI_VERSION=${{ steps.meta.outputs.version }}

38-38: Remove trailing spaces.

There are trailing spaces in the YAML file.

Apply this diff to remove trailing spaces:

     runs-on: ubuntu-latest
-    
+
     if: github.repository_owner == 'ctrlplanedev'
-    
+

Also applies to: 40-40

🧰 Tools
🪛 yamllint (1.35.1)

[error] 38-38: trailing spaces

(trailing-spaces)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdf8e63 and 3c26ab1.

📒 Files selected for processing (4)
  • .github/workflows/release.yaml (2 hunks)
  • cmd/ctrlc/root/sync/terraform/terraform-org-workspaces.go (1 hunks)
  • docker/Dockerfile (1 hunks)
  • docker/README.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docker/README.md
🧰 Additional context used
🪛 actionlint (1.7.4)
.github/workflows/release.yaml

60-60: shellcheck reported issue in this script: SC2086:info:2:35: Double quote to prevent globbing and word splitting

(shellcheck)


60-60: shellcheck reported issue in this script: SC2086:info:4:34: Double quote to prevent globbing and word splitting

(shellcheck)


76-76: the runner of "docker/metadata-action@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 yamllint (1.35.1)
.github/workflows/release.yaml

[error] 38-38: trailing spaces

(trailing-spaces)


[error] 40-40: trailing spaces

(trailing-spaces)

🔇 Additional comments (2)
cmd/ctrlc/root/sync/terraform/terraform-org-workspaces.go (2)

1-15: LGTM! Well-organized package structure and imports.

The package name and imports are appropriate for the functionality. The code uses relevant external packages for logging, retries, and Terraform Enterprise API integration.


31-77: LGTM! Well-implemented helper functions with proper nil checks.

The helper functions are well-structured and include appropriate nil checks to prevent runtime panics. They handle different aspects of workspace metadata effectively:

  • Links metadata with organization validation
  • Non-sensitive Terraform variables
  • VCS repository information
  • Workspace tags

Comment on lines 74 to 82
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ctrlplane/cli
tags: |
type=raw,value=latest
type=ref,event=tag
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update metadata-action version and improve tag strategy.

The current version of metadata-action is outdated.

Apply this diff to update the action and improve tag strategy:

       - name: Extract metadata (tags, labels) for Docker
         id: meta
-        uses: docker/metadata-action@v4
+        uses: docker/metadata-action@v5
         with:
           images: ctrlplane/cli
           tags: |
             type=raw,value=latest
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
             type=ref,event=tag
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ctrlplane/cli
tags: |
type=raw,value=latest
type=ref,event=tag
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ctrlplane/cli
tags: |
type=raw,value=latest
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=ref,event=tag
🧰 Tools
🪛 actionlint (1.7.4)

76-76: the runner of "docker/metadata-action@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

Comment on lines +59 to +65
- name: Check if Docker Hub secrets are available
run: |
if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then
echo "DOCKERHUB_LOGIN=false" >> $GITHUB_ENV
else
echo "DOCKERHUB_LOGIN=true" >> $GITHUB_ENV
fi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix shell script quoting in Docker Hub secrets check.

The shell script has potential word splitting issues.

Apply this diff to fix the shell script:

       - name: Check if Docker Hub secrets are available
         run: |
-          if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then
+          if [ -z "${DOCKERHUB_USERNAME}" ] || [ -z "${DOCKERHUB_TOKEN}" ]; then
             echo "DOCKERHUB_LOGIN=false" >> $GITHUB_ENV
           else
             echo "DOCKERHUB_LOGIN=true" >> $GITHUB_ENV
           fi
+        env:
+          DOCKERHUB_USERNAME: "${{ secrets.DOCKERHUB_USERNAME }}"
+          DOCKERHUB_TOKEN: "${{ secrets.DOCKERHUB_TOKEN }}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Check if Docker Hub secrets are available
run: |
if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then
echo "DOCKERHUB_LOGIN=false" >> $GITHUB_ENV
else
echo "DOCKERHUB_LOGIN=true" >> $GITHUB_ENV
fi
- name: Check if Docker Hub secrets are available
run: |
if [ -z "${DOCKERHUB_USERNAME}" ] || [ -z "${DOCKERHUB_TOKEN}" ]; then
echo "DOCKERHUB_LOGIN=false" >> $GITHUB_ENV
else
echo "DOCKERHUB_LOGIN=true" >> $GITHUB_ENV
fi
env:
DOCKERHUB_USERNAME: "${{ secrets.DOCKERHUB_USERNAME }}"
DOCKERHUB_TOKEN: "${{ secrets.DOCKERHUB_TOKEN }}"
🧰 Tools
🪛 actionlint (1.7.4)

60-60: shellcheck reported issue in this script: SC2086:info:2:35: Double quote to prevent globbing and word splitting

(shellcheck)


60-60: shellcheck reported issue in this script: SC2086:info:4:34: Double quote to prevent globbing and word splitting

(shellcheck)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
.github/workflows/release.yaml (2)

57-63: ⚠️ Potential issue

Fix shell script quoting in Docker Hub secrets check.

The shell script has potential word splitting issues.

       - name: Check if Docker Hub secrets are available
         run: |
-          if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then
+          if [ -z "${DOCKERHUB_USERNAME}" ] || [ -z "${DOCKERHUB_TOKEN}" ]; then
             echo "DOCKERHUB_LOGIN=false" >> $GITHUB_ENV
           else
             echo "DOCKERHUB_LOGIN=true" >> $GITHUB_ENV
           fi
+        env:
+          DOCKERHUB_USERNAME: "${{ secrets.DOCKERHUB_USERNAME }}"
+          DOCKERHUB_TOKEN: "${{ secrets.DOCKERHUB_TOKEN }}"
🧰 Tools
🪛 actionlint (1.7.4)

58-58: shellcheck reported issue in this script: SC2086:info:2:35: Double quote to prevent globbing and word splitting

(shellcheck)


58-58: shellcheck reported issue in this script: SC2086:info:4:34: Double quote to prevent globbing and word splitting

(shellcheck)


72-80: ⚠️ Potential issue

Update metadata-action version and improve tag strategy.

The current version of metadata-action is outdated.

       - name: Extract metadata (tags, labels) for Docker
         id: meta
-        uses: docker/metadata-action@v4
+        uses: docker/metadata-action@v5
         with:
           images: ctrlplane/cli
           tags: |
             type=raw,value=latest
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
             type=ref,event=tag
🧰 Tools
🪛 actionlint (1.7.4)

74-74: the runner of "docker/metadata-action@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🧹 Nitpick comments (2)
.github/workflows/release.yaml (2)

36-36: Remove trailing spaces.

Remove trailing spaces from lines 36 and 38 for cleaner code.

-    
+

Also applies to: 38-38

🧰 Tools
🪛 yamllint (1.35.1)

[error] 36-36: trailing spaces

(trailing-spaces)


43-46: Consider supporting additional platforms.

The platform matrix currently only supports linux/amd64. Consider adding support for other architectures like linux/arm64 for broader compatibility.

     strategy:
       matrix:
-        platform: [linux/amd64]
+        platform: [linux/amd64, linux/arm64]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c26ab1 and 1e7c8b2.

📒 Files selected for processing (1)
  • .github/workflows/release.yaml (1 hunks)
🧰 Additional context used
🪛 actionlint (1.7.4)
.github/workflows/release.yaml

58-58: shellcheck reported issue in this script: SC2086:info:2:35: Double quote to prevent globbing and word splitting

(shellcheck)


58-58: shellcheck reported issue in this script: SC2086:info:4:34: Double quote to prevent globbing and word splitting

(shellcheck)


74-74: the runner of "docker/metadata-action@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 yamllint (1.35.1)
.github/workflows/release.yaml

[error] 36-36: trailing spaces

(trailing-spaces)


[error] 38-38: trailing spaces

(trailing-spaces)

🔇 Additional comments (2)
.github/workflows/release.yaml (2)

48-56: LGTM! Docker setup steps are well configured.

The setup steps use appropriate action versions and follow best practices.


81-101: LGTM! Build steps are well structured.

The build steps are correctly configured to handle both scenarios:

  • Build only when Docker Hub credentials are not available
  • Build and push when credentials are available

@adityachoudhari26 adityachoudhari26 merged commit af11a0c into main Jan 15, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants