Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add Zero Touch Satellite Registration #54

Open
wants to merge 23 commits into
base: main
Choose a base branch
from

Conversation

bupd
Copy link
Collaborator

@bupd bupd commented Sep 22, 2024

Fix: #41
Also Fixes: #36

Sample config.gen.json Created by Satellite

{
  "auth": {                 // Credentials for accessing the registry.
    "name": "robot$sat10",
    "registry": "https://registry.bupd.xyz",
    "secret": "w3BBguuGhmQAMc2hPYJplQnTbcBA8GHt"
  },
  "bring_own_registry": false,
  "ground_control_url": "http://localhost:8080",
  "log_level": "error",
  "own_registry_adr": "127.0.0.1",
  "own_registry_port": "8585",
  "states": [                          // contains array of state artifact urls to be followed.
    "https://registry.bupd.xyz/satellite/group10/state:latest"
  ],
  "url_or_file": "https://demo.goharbor.io/v2/myproject/album-server",  // this should be removed
  "zotconfigpath": "./registry/config.json"
}

Current Database Diagram

currDB

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced group management with support for registry_url and projects.
    • Improved satellite registration functionality, including token management and group associations.
    • New endpoints for managing satellites and groups, including listing, adding, and deleting functionalities.
    • Introduced robust error handling across various operations.
    • Added new utility functions for managing artifacts and robot accounts.
    • New functionality for managing robot accounts, including creation, retrieval, and deletion.
    • Added functionality for managing satellite tokens.
    • New project management functions, including project creation and existence checks.
  • Bug Fixes

    • Improved error handling for satellite and robot account operations.
  • Documentation

    • Updated configuration handling to support new parameters and improved error messages.
  • Chores

    • Updated dependencies and configuration settings for improved performance and compatibility.
    • Streamlined client retrieval process in the Harbor package.

Copy link

coderabbitai bot commented Sep 22, 2024

Walkthrough

The pull request introduces significant modifications across multiple files, focusing on enhancing the data models, API routes, and SQL queries related to satellite and group management. Key changes include the addition of new structures for groups and robot accounts, the introduction of new API handlers for satellite registration and group synchronization, and the restructuring of SQL schemas to accommodate new fields. Additionally, several obsolete files related to image and label management have been removed, reflecting a shift in functionality. The configuration management has also been improved with new parameters and error handling enhancements.

Changes

File Path Change Summary
ground-control/internal/database/models.go Added Group, modified RobotAccount and SatelliteToken structures with new fields.
ground-control/internal/server/handlers.go Introduced new request structures and handlers for satellite registration, group synchronization, and error handling improvements.
ground-control/internal/server/routes.go Updated API routes for groups and satellites, added new routes, and removed obsolete ones.
ground-control/sql/schema/001_satellites.sql Removed token column from satellites table schema.
ground-control/internal/utils/helper.go Added utility functions for managing artifacts and robot accounts.
ground-control/.env Cleared values for several environment variables related to Harbor registry and database configuration.
ground-control/go.mod Added new dependencies and updated existing ones.
ground-control/internal/database/db.go Updated SQLC version from v1.26.0 to v1.27.0.
ground-control/internal/database/group_images.sql.go Deleted file managing image associations with groups.
ground-control/internal/database/groups.sql.go Modified CreateGroup, added deleteGroup, and updated retrieval functions to include new fields.
ground-control/internal/database/robot_accounts.sql.go Introduced SQL queries for managing robot accounts.
ground-control/sql/schema/002_groups.sql Added registry_url and projects columns to groups table schema.
ground-control/internal/models/models.go Added new data structures: StateArtifact, Artifact, ZtrResult, Account.
ground-control/reg/harbor/project.go Added functions for project management in the Harbor API.
ground-control/reg/harbor/replication.go Introduced ListReplication function for managing replication policies.
ground-control/reg/harbor/robot.go Modified functions to streamline client handling and added GetRobotAccount.
ground-control/sql/queries/satellite_token.sql Added SQL queries for managing satellite tokens.
ground-control/sql/schema/004_robot_accounts.sql Created robot_accounts table schema.
ground-control/sql/schema/005_satellite_token.sql Created satellite_token table schema.
main.go Integrated configuration initialization into the run function with enhanced error handling.

Sequence Diagram(s)

sequenceDiagram
    participant Satellite
    participant GroundControl
    participant Database

    Satellite->>GroundControl: Register Satellite
    GroundControl->>Database: Create Satellite Entry
    Database-->>GroundControl: Confirmation
    GroundControl-->>Satellite: Registration Success
Loading

Assessment against linked issues

Objective Addressed Explanation
Implement Zero Touch Registration (41)
Zero touch device registration (36)

Poem

🐰 In the fields of code, we hop and play,
New satellites join us, brightening the day.
Groups now gather, with projects in tow,
Registration's easy, just watch them grow!
With tokens and secrets, they dance in delight,
Ground Control's ready, all systems are bright! 🌟


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7dbfc0f and 3d030dd.

📒 Files selected for processing (1)
  • ground-control/.env (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ground-control/.env

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 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

@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: 13

Outside diff range and nitpick comments (9)
ground-control/sql/schema/012_robot_projects.sql (1)

1-10: Summary: New many-to-many relationship table added

This migration introduces a robot_projects table, establishing a many-to-many relationship between robots and projects. The implementation is correct and follows best practices for SQL schema design.

Key points:

  1. Proper foreign key constraints with CASCADE delete.
  2. Composite primary key ensures uniqueness.
  3. Both Up and Down migrations are present and correct.

Impact on the system:

  • Enables associating multiple robots with multiple projects and vice versa.
  • Provides flexibility in managing robot-project relationships.
  • Ensures referential integrity with cascading deletes.

As the system evolves, consider the following:

  1. Monitor query performance, especially for project-related queries, and add indexes if needed.
  2. If you need to track when robot-project associations are made, consider adding a timestamp column.
  3. Ensure that application logic is updated to utilize this new relationship table when managing robots and projects.
ground-control/sql/schema/011_projects.sql (2)

1-1: Remove unnecessary empty lines

Consider removing the empty lines at the beginning and end of the file to keep it concise.

Apply this diff to remove unnecessary empty lines:

-
-- +goose Up
+-- +goose Up

CREATE TABLE projects (
  id SERIAL PRIMARY KEY,
  projects VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT NOW() NOT NULL,
  updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);

-- +goose Down
DROP TABLE projects;
-

Also applies to: 13-13


4-9: Clarify the purpose of the projects table and consider additional columns

The current schema for the projects table is quite minimal. Could you provide more context on the purpose of this table and how it fits into the overall database schema?

Depending on the requirements, you might want to consider adding more columns to store project-specific information. Some suggestions:

  • description: TEXT
  • status: VARCHAR(50)
  • owner_id: INTEGER (foreign key to a users table, if applicable)
  • start_date: DATE
  • end_date: DATE

Also, consider how this table will relate to other tables in your schema. Will there be foreign keys referencing this table from other tables?

ground-control/sql/schema/010_robots.sql (2)

3-11: Consider adding a comment to explain the table's purpose

While the table creation syntax is correct, it would be helpful to add a comment explaining the purpose of the robots table and its relation to Zero Touch Satellite Registration. This will improve the maintainability of the schema.

Consider adding a comment like this before the CREATE TABLE statement:

-- This table stores information about robots used in Zero Touch Satellite Registration

13-14: Consider data preservation in the "Down" migration

The current "Down" migration simply drops the robots table. Depending on your requirements, you might want to consider preserving the data before dropping the table, especially if this table will be used in a production environment.

If data preservation is necessary, consider adding a step to backup the data before dropping the table:

-- +goose Down
CREATE TABLE robots_backup AS SELECT * FROM robots;
DROP TABLE robots;

This way, you can recover the data if needed after rolling back the migration.

ground-control/internal/database/models.go (2)

45-50: Consider renaming the 'Projects' field

The Project struct looks good overall, but the Projects field name is plural, which is unusual for a single project entity. Consider renaming it to Name or ProjectName for clarity.

 type Project struct {
 	ID        int32
-	Projects  string
+	Name      string
 	CreatedAt time.Time
 	UpdatedAt time.Time
 }

61-64: Consider adding timestamp fields to RobotProject

The RobotProject struct looks good as a join table for the many-to-many relationship between Robot and Project. However, for consistency with other structs in this file, consider adding CreatedAt and UpdatedAt fields.

 type RobotProject struct {
 	RobotID   int32
 	ProjectID int32
+	CreatedAt time.Time
+	UpdatedAt time.Time
 }
ground-control/internal/server/routes.go (1)

35-36: LGTM. Consider removing the commented-out route.

The new route for listing satellites follows RESTful conventions and is consistent with other handlers.

Remove the commented-out route /satellites/create as it appears to be redundant with the /satellites/register route:

-	// r.HandleFunc("/satellites/create", s.addSatelliteHandler).Methods("POST")
 	r.HandleFunc("/satellites/list", s.listSatelliteHandler).Methods("GET")
ground-control/internal/server/handlers.go (1)

225-250: Clarify the purpose and differences between registerSatelliteHandler and ztrHandler.

Both handlers appear to perform the same operation. If they are intended for different endpoints or have subtle differences not reflected in the current code, consider adding comments to clarify their purposes. Otherwise, consider combining them.

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between aaa9653 and d892b53.

Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
Files selected for processing (10)
  • dagger.json (1 hunks)
  • go.mod (6 hunks)
  • ground-control/internal/database/models.go (1 hunks)
  • ground-control/internal/server/handlers.go (2 hunks)
  • ground-control/internal/server/routes.go (1 hunks)
  • ground-control/sql/schema/001_satellites.sql (1 hunks)
  • ground-control/sql/schema/006_images.sql (1 hunks)
  • ground-control/sql/schema/010_robots.sql (1 hunks)
  • ground-control/sql/schema/011_projects.sql (1 hunks)
  • ground-control/sql/schema/012_robot_projects.sql (1 hunks)
Files skipped from review due to trivial changes (2)
  • ground-control/sql/schema/001_satellites.sql
  • ground-control/sql/schema/006_images.sql
Additional comments not posted (19)
dagger.json (1)

5-5: LGTM. Verify impact of Dagger engine update.

The update of the Dagger engine version from v0.13.1 to v0.13.3 looks good. This minor version update likely includes bug fixes and small improvements.

To ensure this update doesn't introduce any unexpected changes, please:

  1. Review the changelog for versions v0.13.2 and v0.13.3 to be aware of any changes that might affect your project.
  2. Test the project with this new engine version to confirm compatibility.
Verification successful

Dagger engine version update verified.

The update from v0.13.1 to v0.13.3 includes minor additions and bug fixes. No significant changes are expected to impact the project.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Fetch the changelog for Dagger versions v0.13.2 and v0.13.3

# Test: Retrieve changelogs for v0.13.2 and v0.13.3
echo "Changelog for v0.13.2:"
gh release view v0.13.2 -R dagger/dagger

echo "\nChangelog for v0.13.3:"
gh release view v0.13.3 -R dagger/dagger

Length of output: 2188

ground-control/sql/schema/012_robot_projects.sql (1)

9-10: LGTM! Down migration is correct.

The Down migration properly reverses the changes made in the Up migration by dropping the robot_projects table. This is sufficient, as the foreign key constraints with CASCADE delete ensure that no orphaned records will be left in other tables.

ground-control/sql/schema/011_projects.sql (1)

11-12: Down migration looks good

The Down migration correctly drops the projects table, which matches the table created in the Up migration.

ground-control/sql/schema/010_robots.sql (3)

1-15: LGTM: Well-structured migration file

The overall structure of the migration file is well-organized with clear "Up" and "Down" sections. The formatting and indentation are consistent, which enhances readability.


7-8: Reconsider ON DELETE SET NULL and verify satellites table existence

  1. The ON DELETE SET NULL behavior for satellite_id means that if a satellite is deleted, the robot will remain in the database but without a satellite association. Consider if this is the desired behavior or if ON DELETE CASCADE would be more appropriate.

  2. This schema assumes the existence of a satellites table. Please ensure that the satellites table is created before this migration runs.

Run the following script to verify the existence of the satellites table:


6-6: Review security implications of storing secret as plain text

The secret column is currently defined as VARCHAR(255). Consider whether this sensitive information should be encrypted or hashed before storage. Also, evaluate if the length of 255 characters is sufficient for your use case.

To ensure this is the only instance of potentially unsecured secret storage, run the following script:

ground-control/internal/database/models.go (2)

Line range hint 1-3: Caution: This is a generated file

Please note that this file is generated by sqlc and should not be edited manually. Any changes to the database schema should be made in the SQL files used by sqlc to generate this code.

To ensure this file is properly generated, run the following script:

#!/bin/bash
# Description: Verify the presence of sqlc.yaml and SQL files

# Test: Check for sqlc.yaml
if [ -f "sqlc.yaml" ]; then
    echo "sqlc.yaml found"
else
    echo "sqlc.yaml not found"
fi

# Test: Check for SQL files
sql_files=$(fd -e sql)
if [ -n "$sql_files" ]; then
    echo "SQL files found:"
    echo "$sql_files"
else
    echo "No SQL files found"
fi

52-59: Address potential issues with the Robot struct

The Robot struct looks good overall, but there are two potential issues to address:

  1. The Secret field might contain sensitive information. Consider implementing additional protection or encryption for this field.

  2. The sql package is not imported, which will cause a compilation error for the sql.NullInt32 type.

To resolve the compilation error, add the following import at the top of the file:

 import (
 	"time"
+	"database/sql"
 )

To ensure the Secret field is properly handled, run the following script:

ground-control/internal/server/routes.go (4)

37-38: LGTM. Ensure proper error handling for non-existent satellites.

The routes for getting and deleting a satellite by ID follow RESTful conventions and use appropriate HTTP methods. The handler names are clear and consistent.

Ensure that the handlers implement proper error handling, especially for cases where the satellite ID doesn't exist. Let's check if there's any standard error handling pattern in the codebase:

#!/bin/bash
# Description: Check for error handling patterns in satellite-related handlers

# Test: Search for error handling in satellite handlers
rg --type go 'func \(s \*Server\) (get|delete)SatelliteByID' -A 10 ground-control/internal/server/

39-41: LGTM. Consider implementing GET method and restructuring routes.

The routes for assigning and removing images from satellites follow RESTful conventions and use appropriate HTTP methods. The handler names are clear and consistent.

  1. Implement the commented-out GET method to retrieve images for a satellite.
  2. Consider restructuring the routes to better follow RESTful conventions:
-	// r.HandleFunc("/satellites/images", s.GetImagesForSatellite).Methods("GET")
-	r.HandleFunc("/satellites/image", s.assignImageToSatellite).Methods("POST")
-	r.HandleFunc("/satellites/image", s.removeImageFromSatellite).Methods("DELETE")
+	r.HandleFunc("/satellites/{satellite}/images", s.GetImagesForSatellite).Methods("GET")
+	r.HandleFunc("/satellites/{satellite}/images", s.assignImageToSatellite).Methods("POST")
+	r.HandleFunc("/satellites/{satellite}/images/{image}", s.removeImageFromSatellite).Methods("DELETE")

This structure more clearly represents the relationship between satellites and their images, and allows for more specific operations on individual images.

Let's check if there are any similar RESTful patterns already in use in the codebase:

#!/bin/bash
# Description: Check for nested resource patterns in routes

# Test: Search for nested resource patterns in route definitions
rg --type go 'r\.HandleFunc\("/.+/.+/.+"' ground-control/internal/server/routes.go

33-41: Overall good additions, with some suggestions for improvement

The new routes for satellite management are a good addition to the API. They generally follow RESTful conventions and are consistent with the existing codebase. Here are some overall recommendations:

  1. Ensure that all new routes have proper authentication and authorization checks.
  2. Consider implementing pagination for the /satellites/list endpoint to handle potentially large numbers of satellites.
  3. Implement proper error handling and validation for all new handlers.
  4. Add comprehensive unit tests for each new handler.
  5. Update API documentation to reflect these new endpoints and their usage.

To improve the overall architecture and maintainability of the codebase:

  1. Consider grouping related routes under a common prefix (e.g., /api/v1/satellites/) to allow for easier versioning and organization.
  2. Implement a middleware for common operations like authentication, logging, and error handling to keep the handlers focused on their primary logic.
  3. Consider using a dependency injection pattern for the Server struct to make it easier to test and mock dependencies.

Let's check if there's any existing API documentation that needs to be updated:

#!/bin/bash
# Description: Check for API documentation files

# Test: Search for API documentation files
fd -e md -e yaml -e json 'api|swagger|openapi' ground-control/

33-33: LGTM. Consider adding authentication middleware.

The new route for registering satellites follows RESTful conventions and naming consistency with other handlers. However, ensure that appropriate authentication and authorization checks are implemented in the handler or middleware to prevent unauthorized satellite registrations.

Let's verify if authentication middleware is applied elsewhere in the file:

go.mod (5)

Line range hint 1-5: LGTM: Module and Go version declarations are up-to-date.

The module name, Go version (1.22.3), and toolchain version (1.22.4) are all current and appropriate. This ensures that the project is using the latest features and security updates from Go.


408-414: LGTM: Core Go package updates look good, but some might have newer versions available.

The following core Go packages have been updated:

  • golang.org/x/crypto to v0.27.0
  • golang.org/x/net to v0.29.0
  • golang.org/x/sys to v0.25.0
  • golang.org/x/term to v0.24.0
  • golang.org/x/text to v0.18.0

These updates are crucial for security and performance. However, it's worth checking if even newer versions are available for some packages, particularly crypto and text, which seem to be slightly behind the others.

To verify if newer versions are available, please run the following script:

#!/bin/bash
# Description: Check for the latest versions of core Go packages

# Test: Compare current versions with the latest available versions
for pkg in "crypto" "net" "sys" "term" "text"; do
  echo "Checking golang.org/x/$pkg:"
  current_version=$(grep "golang.org/x/$pkg" go.mod | awk '{print $2}')
  latest_version=$(go list -m -versions golang.org/x/$pkg | awk '{print $NF}')
  echo "Current: $current_version, Latest: $latest_version"
  if [ "$current_version" != "$latest_version" ]; then
    echo "Newer version available"
  else
    echo "Up to date"
  fi
  echo
done

420-422: LGTM: Google package updates look good, but keep an eye on genproto packages.

The following Google packages have been updated:

  • google.golang.org/genproto/googleapis/api to v0.0.0-20240814211410-ddb44dafa142
  • google.golang.org/genproto/googleapis/rpc to v0.0.0-20240814211410-ddb44dafa142
  • google.golang.org/grpc to v1.65.0

The gRPC update to a stable version is good. The genproto packages are using specific commit hashes, which is common but may make version tracking more difficult. Consider monitoring these packages for stable releases in the future.

To verify the status of these packages, please run the following script:

#!/bin/bash
# Description: Check for stable releases of genproto packages and latest gRPC version

# Test: Check for stable releases of genproto packages
for pkg in "api" "rpc"; do
  echo "Checking google.golang.org/genproto/googleapis/$pkg:"
  go list -m -versions google.golang.org/genproto/googleapis/$pkg | grep -v "-"
done

# Test: Check latest gRPC version
echo "Checking latest gRPC version:"
go list -m -versions google.golang.org/grpc | awk '{print $NF}'

Line range hint 466-474: Note the use of replace directives for OpenTelemetry packages.

The file includes several replace directives for OpenTelemetry packages:

  • go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc
  • go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp
  • go.opentelemetry.io/otel/log
  • go.opentelemetry.io/otel/sdk/log

These directives ensure specific versions or commit hashes are used, which can be helpful for consistency. However, this approach may prevent automatic updates to these packages.

To keep these packages up-to-date, periodically run the following script to check for newer versions:

#!/bin/bash
# Description: Check for newer versions of replaced OpenTelemetry packages

# Test: Compare current versions with the latest available versions
for pkg in "otel/exporters/otlp/otlplog/otlploggrpc" "otel/exporters/otlp/otlplog/otlploghttp" "otel/log" "otel/sdk/log"; do
  echo "Checking go.opentelemetry.io/$pkg:"
  current_version=$(grep "go.opentelemetry.io/$pkg" go.mod | awk '{print $4}')
  latest_version=$(go list -m -versions go.opentelemetry.io/$pkg | awk '{print $NF}')
  echo "Current: $current_version, Latest: $latest_version"
  if [ "$current_version" != "$latest_version" ]; then
    echo "Newer version available"
  else
    echo "Up to date"
  fi
  echo
done

12-12: LGTM: Dependency updates look good, but ensure thorough testing.

The following key dependencies have been updated:

  • golang.org/x/sync to v0.8.0
  • golang.org/x/oauth2 to v0.22.0
  • github.com/adrg/xdg to v0.5.0
  • github.com/cespare/xxhash/v2 to v2.3.0
  • github.com/grpc-ecosystem/grpc-gateway/v2 to v2.22.0

These updates likely include important bug fixes, performance improvements, and security patches. However, it's crucial to thoroughly test the project with these new versions to ensure no breaking changes have been introduced.

To verify the impact of these changes, please run the following script:

Also applies to: 23-23, 67-67, 121-121, 223-223

ground-control/internal/server/handlers.go (2)

238-240: Handle the case where req.Name might be empty.

Similar to the previous comment, validate Name before using it.

Use the same script as above to ensure Name is validated.


211-213: Handle the case where req.Name might be empty.

If Name is a required field for creating a satellite, ensure you validate it before proceeding.

Run the following script to check if Name is validated elsewhere:

ground-control/sql/schema/012_robot_projects.sql Outdated Show resolved Hide resolved
ground-control/sql/schema/011_projects.sql Outdated Show resolved Hide resolved
ground-control/internal/server/routes.go Outdated Show resolved Hide resolved
ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
@bupd bupd changed the title WIP: Add Zero Touch Satellite Registration Add Zero Touch Satellite Registration Sep 29, 2024
@bupd bupd marked this pull request as ready for review September 29, 2024 20:02
Copy link

@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: 12

🧹 Outside diff range and nitpick comments (44)
ground-control/sql/queries/satellite_labels.sql (1)

1-4: Add a comment explaining the query's purpose

While the query is straightforward, it would be beneficial to add a brief comment explaining its purpose and the significance of the ON CONFLICT DO NOTHING clause. This can help other developers understand the intent behind the query.

Consider adding a comment like this:

 -- name: AddLabelToSatellite :exec
+-- Adds a label to a satellite. If the association already exists, the operation is silently ignored.
 INSERT INTO satellite_labels (satellite_id, label_id)
 VALUES ($1, $2)
 ON CONFLICT DO NOTHING;
ground-control/sql/queries/labels.sql (4)

6-8: LGTM: GetLabelByID query is correctly implemented.

The query effectively retrieves a label by its ID. Good use of parameterized query for security.

Consider specifying the exact columns needed instead of using * if not all columns are required by the application. This can improve query performance and reduce data transfer.


10-12: LGTM with suggestions: GetLabelByName query is implemented correctly.

The query effectively retrieves a label by its name, with good use of a parameterized query for security.

Consider the following improvements:

  1. Specify the exact columns needed instead of using * if not all columns are required.
  2. Ensure there's a uniqueness constraint on label_name in the table definition, or handle potential multiple results in the application layer.
  3. If multiple labels with the same name are possible and intended, consider changing the return type to :many instead of :one.

14-16: LGTM with a suggestion: DeleteLabel query is implemented correctly.

The query effectively deletes a label by its ID, with good use of a parameterized query for security.

Consider adding error handling in the application layer to check if any rows were affected by the DELETE operation. This can help distinguish between successful deletions and attempts to delete non-existent labels.


Line range hint 1-16: Overall, the label management queries are well-implemented and align with the PR objectives.

The file provides a solid foundation for label management in the context of Zero Touch Registration (ZTR) for satellites. All queries are secure and cover basic CRUD operations.

To further enhance ZTR capabilities, consider the following future improvements:

  1. Add a query to update existing labels.
  2. Implement a query to retrieve multiple labels (e.g., for a specific satellite or category).
  3. Add support for label hierarchies or relationships, if applicable to the ZTR process.
  4. Include a query to check for label existence without retrieving all data.

These enhancements could provide more flexibility and efficiency in managing labels for satellite registration.

ground-control/sql/schema/014_satellite_token.sql (2)

1-9: LGTM! Consider adding an index on satellite_id.

The table structure is well-designed and includes appropriate constraints:

  • The id as a SERIAL PRIMARY KEY is a good choice for an auto-incrementing identifier.
  • The satellite_id foreign key with CASCADE delete maintains referential integrity.
  • The token column's UNIQUE constraint ensures no duplicate tokens.
  • created_at and updated_at columns are useful for auditing.

Consider adding an index on the satellite_id column to improve query performance when searching tokens by satellite:

CREATE INDEX idx_satellite_token_satellite_id ON satellite_token(satellite_id);

1-12: Consider additional security measures for Zero Touch Registration.

The satellite_token table is a crucial component for implementing Zero Touch Registration (ZTR) for satellites. It provides a mechanism to associate unique tokens with specific satellites, which can be used for authentication during the automatic registration process.

To enhance security for ZTR, consider the following suggestions:

  1. Implement token expiration: Add an expires_at column to allow for token rotation and reduce the risk of token abuse.
  2. Use a cryptographically secure method for generating tokens.
  3. Implement rate limiting on token usage to prevent brute force attacks.
  4. Consider encrypting the token column for an extra layer of security.

Here's an example of how you might add an expiration column:

ALTER TABLE satellite_token
ADD COLUMN expires_at TIMESTAMP;

Also, ensure that your application logic includes proper token validation and handles token expiration gracefully.

ground-control/sql/queries/group_images.sql (1)

6-8: LGTM! Consider adding affected rows check.

The new RemoveImageFromGroup query is well-structured and secure, using parameterized inputs. It correctly removes the specified image from the group.

For improved error handling and feedback, consider modifying the query to return the number of affected rows. This can help detect cases where the specified group_id and image_id combination doesn't exist. Here's a suggested modification:

-- name: RemoveImageFromGroup :one
DELETE FROM group_images
WHERE group_id = $1 AND image_id = $2
RETURNING (xmax <> 0) AS deleted;

This modification will return a boolean indicating whether a row was actually deleted. You can then handle this in your application logic to provide appropriate feedback or error handling.

ground-control/sql/schema/013_robot_accounts.sql (2)

3-10: Table structure looks good, but consider enhancing security for robot_secret.

The table structure and constraints are well-defined:

  • Appropriate use of SERIAL PRIMARY KEY for the id column.
  • UNIQUE constraint on robot_name ensures no duplicate robot names.
  • Foreign key relationship with ON DELETE CASCADE maintains referential integrity.
  • Timestamp columns for created_at and updated_at with DEFAULT NOW() are good for tracking.

However, there are a couple of points to consider:

  1. For the robot_secret column, consider using a more secure data type or additional measures to protect sensitive information. For example, you could use pgcrypto for encryption or implement application-level encryption.
  2. Verify if VARCHAR(255) is sufficient for all columns, especially robot_name and robot_secret. Adjust if necessary based on your specific requirements.

Consider implementing one of these security enhancements for the robot_secret column:

  1. Use pgcrypto extension (if available):
CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TABLE robot_accounts (
  -- other columns remain the same
  robot_secret TEXT NOT NULL,
  -- use this in your application code:
  -- INSERT INTO robot_accounts (robot_secret) VALUES (crypt('secret', gen_salt('bf')));
);
  1. Or, if pgcrypto is not available, implement application-level encryption and store the encrypted value in the database.

1-13: Overall, the migration aligns with PR objectives, but consider clarifying ZTR implementation details.

This migration introduces a robot_accounts table, which is a step towards implementing Zero Touch Registration (ZTR) for satellites as outlined in the PR objectives. The table structure provides a foundation for managing robot accounts associated with satellites, which is crucial for automated registration processes.

However, to fully address the objectives of issues #41 and #36, consider the following:

  1. Clarify how this table will be used in the ZTR process. Will these robot accounts be automatically created during satellite registration? How will they interact with ground control?

  2. Document the overall ZTR flow, explaining how this table fits into the broader implementation of automatic satellite registration.

  3. Ensure that the security measures for robot_secret are sufficient for your use case, considering the sensitive nature of satellite communications.

  4. Plan for how this table will be populated and managed as part of the zero-touch process.

By addressing these points, you'll strengthen the connection between this database change and the overall ZTR implementation, ensuring that the PR fully meets its objectives.

ground-control/sql/queries/satellite_token.sql (3)

1-4: LGTM! Consider adding a UNIQUE constraint on the token column.

The AddToken command is well-structured and secure, using parameterized queries to prevent SQL injection. It correctly sets timestamps and efficiently returns the inserted token.

To enhance data integrity, consider adding a UNIQUE constraint on the token column in the satellite_token table. This would prevent duplicate tokens and align with the apparent 1:1 relationship between tokens and satellites. You can add this constraint with an ALTER TABLE statement:

ALTER TABLE satellite_token ADD CONSTRAINT unique_token UNIQUE (token);

11-13: *LGTM! Consider specifying required columns instead of using .

The GetToken command is secure, using a parameterized query. The :one annotation correctly indicates that it returns a single row.

For better performance and to make the query's intent clearer, consider explicitly listing the required columns instead of using *. This approach fetches only the necessary data and makes the query's purpose more evident. For example:

SELECT id, satellite_id, token, created_at, updated_at FROM satellite_token
WHERE id = $1;

Adjust the selected columns based on your specific needs.


18-20: LGTM! Consider returning the number of affected rows.

The DeleteToken command is secure, using a parameterized query. The deletion based on the token value is appropriate, assuming tokens are unique.

To provide more information about the operation's result, consider returning the number of affected rows. This can be useful for verifying whether the deletion was successful. You can modify the command as follows:

-- name: DeleteToken :one
DELETE FROM satellite_token
WHERE token = $1
RETURNING 1;

This will return 1 if a row was deleted, or no rows if no matching token was found. Update the annotation to :one to reflect this change.

ground-control/internal/database/satellite_labels.sql.go (2)

12-16: LGTM: Consistent naming for SQL constant

The rename from addSatelliteToLabel to addLabelToSatellite improves consistency with the function name change. The SQL query itself is correct for inserting a record into the satellite_labels table.

Consider adding a comment explaining the purpose of ON CONFLICT DO NOTHING. For example:

 const addLabelToSatellite = `-- name: AddLabelToSatellite :exec
 INSERT INTO satellite_labels (satellite_id, label_id)
 VALUES ($1, $2)
+-- Ignore if the satellite-label association already exists
 ON CONFLICT DO NOTHING
 `

23-26: LGTM: Function rename and implementation

The function rename from AddSatelliteToLabel to AddLabelToSatellite is consistent with the other changes. The implementation correctly uses the SQL constant and struct defined earlier.

Consider wrapping the returned error with additional context:

 func (q *Queries) AddLabelToSatellite(ctx context.Context, arg AddLabelToSatelliteParams) error {
 	_, err := q.db.ExecContext(ctx, addLabelToSatellite, arg.SatelliteID, arg.LabelID)
-	return err
+	if err != nil {
+		return fmt.Errorf("failed to add label to satellite: %w", err)
+	}
+	return nil
 }

This change would provide more context when debugging potential issues.

ground-control/sql/queries/satellites.sql (2)

2-3: LGTM! Consider adding a comment explaining the ZTR approach.

The removal of the token parameter and the use of NOW() for timestamps align well with the Zero Touch Registration (ZTR) objective. This simplifies the satellite creation process and ensures consistent timestamp recording.

Consider adding a comment above the query to explain the ZTR approach and why the token is no longer needed. This will help future maintainers understand the rationale behind this change.


17-19: Consider removing or explaining the commented-out GetSatelliteByToken query.

While commenting out this query aligns with the move towards Zero Touch Registration, leaving commented-out code in the file might lead to confusion in the future.

Consider one of the following options:

  1. Remove the commented-out query entirely if it's no longer needed.
  2. If there's a possibility of needing this query during a transition period, add a comment explaining its deprecation and when it can be safely removed. For example:
-- Deprecated: GetSatelliteByToken query
-- This query is no longer used in the Zero Touch Registration (ZTR) approach.
-- It is kept temporarily for reference and will be removed in version X.X.
-- -- name: GetSatelliteByToken :one
-- SELECT * FROM satellites
-- WHERE token = $1 LIMIT 1;
ground-control/sql/queries/robot_accounts.sql (6)

1-4: LGTM with a minor suggestion for data integrity.

The AddRobotAccount command is well-structured and correctly inserts a new robot account. It appropriately uses NOW() for timestamps and returns the full record.

Consider adding a UNIQUE constraint on the satellite_id column in the robot_accounts table to ensure each satellite has only one robot account. This can be done in a separate migration script:

ALTER TABLE robot_accounts ADD CONSTRAINT unique_satellite_id UNIQUE (satellite_id);

10-12: LGTM with a suggestion for handling potential multiple results.

The GetRobotAccBySatelliteID command efficiently retrieves the robot_name and robot_secret for a given satellite_id.

Consider changing the query type from :one to :many to handle potential scenarios where multiple robot accounts might be associated with a single satellite:

-- name: GetRobotAccBySatelliteID :many
SELECT robot_name, robot_secret FROM robot_accounts
WHERE satellite_id = $1;

This change would make the query more flexible if the relationship between satellites and robot accounts changes in the future.


14-15: LGTM with suggestions for scalability.

The ListRobotAccounts command effectively retrieves all robot accounts.

For improved scalability, consider the following enhancements:

  1. Implement pagination to limit the number of records returned in a single query:
-- name: ListRobotAccounts :many
SELECT * FROM robot_accounts
ORDER BY id
LIMIT $1 OFFSET $2;
  1. Add sorting options:
-- name: ListRobotAccounts :many
SELECT * FROM robot_accounts
ORDER BY $1 $2
LIMIT $3 OFFSET $4;

These changes would allow the application to retrieve data in manageable chunks and provide sorting flexibility.


17-19: LGTM with a suggestion for deletion confirmation.

The DeleteRobotAccount command correctly deletes a robot account by its id.

Consider returning the deleted row for confirmation:

-- name: DeleteRobotAccount :one
DELETE FROM robot_accounts
WHERE id = $1
RETURNING *;

This change would allow the application to confirm the deletion and potentially use the deleted data if needed.


21-26: LGTM with suggestions for improvement.

The UpdateRobotAccount command correctly updates the specified fields and the updated_at timestamp.

Consider the following improvements:

  1. Return the updated record for confirmation:
-- name: UpdateRobotAccount :one
UPDATE robot_accounts
SET robot_name = $2,
    robot_secret = $3,
    updated_at = NOW()
WHERE id = $1
RETURNING *;
  1. Add an option to update the satellite_id if needed:
-- name: UpdateRobotAccount :one
UPDATE robot_accounts
SET robot_name = $2,
    robot_secret = $3,
    satellite_id = COALESCE($4, satellite_id),
    updated_at = NOW()
WHERE id = $1
RETURNING *;

These changes would provide more flexibility and confirmation of the update operation.


1-26: Overall, well-structured CRUD operations with room for minor enhancements.

The SQL file provides a comprehensive set of operations for managing robot accounts. The queries are generally well-structured and functional. Consider the following overall suggestions:

  1. Implement the suggested improvements for individual queries to enhance scalability, flexibility, and data integrity.

  2. For consistency, consider adding a created_at field to the UpdateRobotAccount query:

-- name: UpdateRobotAccount :one
UPDATE robot_accounts
SET robot_name = $2,
    robot_secret = $3,
    updated_at = NOW(),
    created_at = COALESCE($4, created_at)
WHERE id = $1
RETURNING *;

This change would allow for potential correction of the created_at timestamp if needed, maintaining consistency across all fields.

  1. Consider adding appropriate indexes to the robot_accounts table to optimize query performance, especially for the satellite_id field which is used in the GetRobotAccBySatelliteID query.
ground-control/sql/queries/images.sql (2)

5-6: Consider adding pagination to ListImages query.

The new ListImages query is a straightforward way to retrieve all images. However, if the images table grows large, this query might return an extensive dataset, potentially causing performance issues.

Consider adding pagination to this query to improve performance and reduce memory usage. For example:

-- name: ListImages :many
SELECT * FROM images
ORDER BY id
LIMIT $1 OFFSET $2;

This allows for retrieving images in smaller batches, where $1 is the limit and $2 is the offset.


17-34: Approve GetReposOfSatellite query with suggestions for improvement.

The new GetReposOfSatellite query effectively retrieves repository names associated with a satellite, considering both satellite groups and labels. This comprehensive approach is commendable.

Consider the following improvements:

  1. Add an ORDER BY clause to ensure consistent ordering of results:

    ORDER BY repository
  2. If performance becomes an issue with large datasets, consider using a UNION ALL instead of UNION, as UNION ALL doesn't remove duplicates and is generally faster:

    UNION ALL

    However, only do this if duplicate repositories are acceptable in the result set.

  3. To potentially improve query performance, consider using a Common Table Expression (CTE) to avoid repeating the satellite_id condition:

    WITH satellite_data AS (
      SELECT $1 AS satellite_id
    )
    SELECT i.repository
    FROM satellite_data sd
    JOIN satellite_groups sg ON sd.satellite_id = sg.satellite_id
    JOIN group_images gi ON sg.group_id = gi.group_id
    JOIN images i ON gi.image_id = i.id
    
    UNION
    
    SELECT i.repository
    FROM satellite_data sd
    JOIN satellite_labels sl ON sd.satellite_id = sl.satellite_id
    JOIN label_images li ON sl.label_id = li.label_id
    JOIN images i ON li.image_id = i.id
    ORDER BY repository;

These suggestions aim to improve the query's performance and result consistency.

ground-control/internal/database/labels.sql.go (2)

37-45: LGTM with a minor suggestion.

The DeleteLabel function is well-implemented. It correctly uses a parameterized query to prevent SQL injection and properly handles the context and error.

Consider adding a comment to explain the function's behavior, especially regarding what happens if the label doesn't exist. For example:

// DeleteLabel removes a label from the database by its ID.
// If the label doesn't exist, it returns nil (no error).
func (q *Queries) DeleteLabel(ctx context.Context, id int32) error {
    // ... (existing code)
}

47-62: LGTM with a minor suggestion.

The GetLabelByID function is well-implemented. It uses a parameterized query to prevent SQL injection, properly handles the context, and correctly scans the result into a Label struct.

Consider adding a comment to explain the function's behavior, especially regarding what happens if the label doesn't exist. For example:

// GetLabelByID retrieves a label from the database by its ID.
// If the label doesn't exist, it returns an error (likely sql.ErrNoRows).
func (q *Queries) GetLabelByID(ctx context.Context, id int32) (Label, error) {
    // ... (existing code)
}
ground-control/internal/database/group_images.sql.go (1)

86-94: LGTM: New Go function and struct for removing an image from a group.

The new RemoveImageFromGroupParams struct and RemoveImageFromGroup method are well-implemented and consistent with the existing code style. The method correctly uses the database context and handles error reporting.

Consider adding a brief comment above the RemoveImageFromGroup method to describe its purpose and any important details. For example:

+// RemoveImageFromGroup removes the specified image from the given group.
+// It returns an error if the operation fails.
func (q *Queries) RemoveImageFromGroup(ctx context.Context, arg RemoveImageFromGroupParams) error {
    _, err := q.db.ExecContext(ctx, removeImageFromGroup, arg.GroupID, arg.ImageID)
    return err
}
ground-control/internal/database/satellites.sql.go (4)

13-15: *LGTM! Consider using RETURNING .

The changes to the createSatellite query look good. Removing the token field and using NOW() for timestamps are appropriate modifications.

Consider using RETURNING * instead of explicitly listing the columns. This can make the query more maintainable as it will automatically adapt to any future column additions:

INSERT INTO satellites (name, created_at, updated_at)
VALUES ($1, NOW(), NOW())
RETURNING *

40-42: LGTM! Consider adding an index on the id column.

The changes to the getSatellite query look good. Retrieving a satellite by ID is more efficient and less prone to errors than using names.

Consider adding an index on the id column if it doesn't already exist. This will improve the performance of this query, especially as the number of satellites grows:

CREATE INDEX IF NOT EXISTS idx_satellites_id ON satellites(id);

57-59: LGTM! Consider adding an index on the name column.

The addition of the getSatelliteByName query is good. It provides flexibility in how satellites can be retrieved.

Consider adding a unique index on the name column if it doesn't already exist. This will improve the performance of this query and ensure that satellite names are unique:

CREATE UNIQUE INDEX IF NOT EXISTS idx_satellites_name ON satellites(name);

Line range hint 1-120: Overall, the changes align well with the Zero Touch Registration objectives.

The modifications to this file, including the removal of token-based operations and the addition of name-based retrieval methods, appear to support a more streamlined satellite registration process. These changes align with the PR objectives of implementing Zero Touch Registration (ZTR) for satellites.

However, to ensure full alignment with the ZTR objectives:

  1. Verify that these database changes are accompanied by corresponding changes in the application logic to support automatic satellite registration.
  2. Consider adding documentation or comments explaining how these database operations support the ZTR process.
  3. Ensure that proper error handling and logging are implemented in the application code that uses these database operations to facilitate troubleshooting of the ZTR process.
ground-control/internal/server/handlers.go (9)

141-161: Improved error handling and image URL parsing.

The changes in this function enhance error handling and introduce proper parsing of the image URL. These improvements are beneficial for better error reporting and data validation.

Consider making the error message for invalid artifact URL more specific by including the actual URL:

 		err = &AppError{
-			Message: "Error: Invalid Artifact URL",
+			Message: fmt.Sprintf("Error: Invalid Artifact URL: %s", req.Image),
 			Code:    http.StatusBadRequest,
 		}

210-364: Well-implemented registerSatelliteHandler with room for refactoring.

The function correctly handles satellite registration with proper transaction management and error handling. The use of a transaction ensures data consistency across multiple database operations.

While the implementation is correct, the function is quite long and complex. Consider refactoring it into smaller, more focused functions to improve readability and maintainability. For example:

  1. Create a separate function for group association:
func (s *Server) associateGroups(ctx context.Context, q *Queries, satelliteID int32, groups []string) error {
    // Implementation here
}
  1. Create a separate function for label association:
func (s *Server) associateLabels(ctx context.Context, q *Queries, satelliteID int32, labels []string) error {
    // Implementation here
}
  1. Create a separate function for robot account creation:
func (s *Server) createRobotAccount(ctx context.Context, q *Queries, satelliteID int32, satelliteName string) error {
    // Implementation here
}

Then, call these functions from the main registerSatelliteHandler. This approach would make the main function more concise and easier to understand.


366-427: Enhance clarity and documentation in ztrHandler.

The function correctly implements Zero Touch Registration (ZTR) for satellites with proper transaction management and error handling.

To improve code clarity and maintainability:

  1. Add a comment explaining the ZTR process at the beginning of the function.
  2. Use more descriptive variable names. For example, change q to txQueries.
  3. Add comments explaining the purpose of each major step in the function.

Example:

// ztrHandler handles Zero Touch Registration (ZTR) for satellites.
// It validates the provided token, retrieves the associated satellite,
// and returns the robot account information for the satellite.
func (s *Server) ztrHandler(w http.ResponseWriter, r *http.Request) {
    // ... (existing code)

    // Start a new transaction
    tx, err := s.db.BeginTx(r.Context(), nil)
    if err != nil {
        log.Println("Failed to start transaction:", err)
        HandleAppError(w, err)
        return
    }

    txQueries := s.dbQueries.WithTx(tx)

    // ... (existing code)

    // Validate the token and get the associated satellite ID
    satelliteID, err := txQueries.GetSatelliteIDByToken(r.Context(), token)
    if err != nil {
        log.Println("Invalid Satellite Token:", err)
        // ... (existing error handling)
    }

    // Delete the used token
    err = txQueries.DeleteToken(r.Context(), token)
    if err != nil {
        log.Println("Failed to delete token:", err)
        // ... (existing error handling)
    }

    // Retrieve the robot account for the satellite
    robot, err := txQueries.GetRobotAccBySatelliteID(r.Context(), satelliteID)
    if err != nil {
        log.Println("Robot Account Not Found:", err)
        // ... (existing error handling)
    }

    // ... (rest of the existing code)
}

These changes will make the function easier to understand and maintain.


429-437: Consider adding pagination to listSatelliteHandler.

The function correctly retrieves and returns the list of satellites with proper error handling.

To improve scalability and performance, consider implementing pagination for this endpoint. This is especially important if the number of satellites could grow large over time. Here's an example of how you might implement this:

func (s *Server) listSatelliteHandler(w http.ResponseWriter, r *http.Request) {
    limit := 50 // Default limit
    offset := 0 // Default offset

    // Parse limit and offset from query parameters
    if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
        if parsedLimit, err := strconv.Atoi(limitStr); err == nil {
            limit = parsedLimit
        }
    }
    if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" {
        if parsedOffset, err := strconv.Atoi(offsetStr); err == nil {
            offset = parsedOffset
        }
    }

    result, err := s.dbQueries.ListSatellitesWithPagination(r.Context(), database.ListSatellitesWithPaginationParams{
        Limit:  int32(limit),
        Offset: int32(offset),
    })
    if err != nil {
        HandleAppError(w, err)
        return
    }

    WriteJSONResponse(w, http.StatusOK, result)
}

This change would require adding a new ListSatellitesWithPagination method to your database queries, but it would allow clients to request smaller chunks of data, improving performance for large datasets.


Line range hint 439-456: Implement getSatelliteByID and deleteSatelliteByID correctly.

Both getSatelliteByID and deleteSatelliteByID functions currently have the same implementation as listSatelliteHandler, which doesn't match their intended purposes.

These functions need to be implemented correctly:

  1. For getSatelliteByID:
func (s *Server) getSatelliteByID(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    idStr := vars["id"]
    id, err := strconv.ParseInt(idStr, 10, 32)
    if err != nil {
        HandleAppError(w, &AppError{Message: "Invalid satellite ID", Code: http.StatusBadRequest})
        return
    }

    satellite, err := s.dbQueries.GetSatelliteByID(r.Context(), int32(id))
    if err != nil {
        if err == sql.ErrNoRows {
            HandleAppError(w, &AppError{Message: "Satellite not found", Code: http.StatusNotFound})
        } else {
            HandleAppError(w, err)
        }
        return
    }

    WriteJSONResponse(w, http.StatusOK, satellite)
}
  1. For deleteSatelliteByID:
func (s *Server) deleteSatelliteByID(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    idStr := vars["id"]
    id, err := strconv.ParseInt(idStr, 10, 32)
    if err != nil {
        HandleAppError(w, &AppError{Message: "Invalid satellite ID", Code: http.StatusBadRequest})
        return
    }

    err = s.dbQueries.DeleteSatellite(r.Context(), int32(id))
    if err != nil {
        if err == sql.ErrNoRows {
            HandleAppError(w, &AppError{Message: "Satellite not found", Code: http.StatusNotFound})
        } else {
            HandleAppError(w, err)
        }
        return
    }

    WriteJSONResponse(w, http.StatusOK, map[string]string{"message": "Satellite deleted successfully"})
}

These implementations assume the existence of GetSatelliteByID and DeleteSatellite methods in your database queries. If these methods don't exist, you'll need to add them to your database layer as well.


Line range hint 481-501: Approve AddLabelToSatellite with minor suggestion.

The renaming of the function to AddLabelToSatellite makes it exported, which is fine if this is the intended behavior. The implementation correctly handles adding a label to a satellite.

Consider improving the error handling slightly:

 func (s *Server) AddLabelToSatellite(w http.ResponseWriter, r *http.Request) {
     var req AddLabelToSatelliteParams
     if err := DecodeRequestBody(r, &req); err != nil {
         HandleAppError(w, err)
         return
     }

     params := database.AddLabelToSatelliteParams{
         SatelliteID: int32(req.SatelliteID),
         LabelID:     int32(req.LabelID),
     }

     err := s.dbQueries.AddLabelToSatellite(r.Context(), params)
     if err != nil {
-        log.Printf("Error: Failed to add satellite to label: %v", err)
-        HandleAppError(w, err)
+        log.Printf("Error: Failed to add label to satellite: %v", err)
+        HandleAppError(w, &AppError{
+            Message: "Failed to add label to satellite",
+            Code:    http.StatusInternalServerError,
+        })
         return
     }

     WriteJSONResponse(w, http.StatusOK, map[string]string{})
 }

This change provides a more specific error message and ensures that an AppError is always passed to HandleAppError.


547-567: Improve error handling in deleteImageFromGroup.

The function correctly handles removing an image from a group, but the error handling can be improved.

Consider enhancing the error handling:

 func (s *Server) deleteImageFromGroup(w http.ResponseWriter, r *http.Request) {
     var req AssignImageToGroupParams
     if err := DecodeRequestBody(r, &req); err != nil {
         HandleAppError(w, err)
         return
     }

     params := database.RemoveImageFromGroupParams{
         GroupID: int32(req.GroupID),
         ImageID: int32(req.ImageID),
     }

     err := s.dbQueries.RemoveImageFromGroup(r.Context(), params)
     if err != nil {
-        log.Printf("Error: Failed to delete image from group: %v", err)
-        HandleAppError(w, err)
+        if err == sql.ErrNoRows {
+            log.Printf("Image (ID: %d) not found in group (ID: %d)", req.ImageID, req.GroupID)
+            HandleAppError(w, &AppError{
+                Message: "Image not found in group",
+                Code:    http.StatusNotFound,
+            })
+        } else {
+            log.Printf("Error: Failed to delete image from group: %v", err)
+            HandleAppError(w, &AppError{
+                Message: "Failed to delete image from group",
+                Code:    http.StatusInternalServerError,
+            })
+        }
         return
     }

     WriteJSONResponse(w, http.StatusOK, map[string]string{})
 }

This change provides more specific error messages and handles the case where the image is not found in the group.


569-592: Enhance error handling in listGroupImages.

The function correctly retrieves images for a specific group, but the error handling can be improved.

Consider enhancing the error handling and adding a check for empty results:

 func (s *Server) listGroupImages(w http.ResponseWriter, r *http.Request) {
     vars := mux.Vars(r)
     groupID := vars["groupID"]

     id, err := strconv.ParseInt(groupID, 10, 32)
     if err != nil {
-        log.Printf("Error: Invalid groupID: %v", err)
-        err := &AppError{
-            Message: "Error: Invalid GroupID",
-            Code:    http.StatusBadRequest,
-        }
-        HandleAppError(w, err)
+        log.Printf("Error: Invalid groupID: %s - %v", groupID, err)
+        HandleAppError(w, &AppError{
+            Message: fmt.Sprintf("Invalid group ID: %s", groupID),
+            Code:    http.StatusBadRequest,
+        })
         return
     }

     result, err := s.dbQueries.GetImagesForGroup(r.Context(), int32(id))
     if err != nil {
-        log.Printf("Error: Failed to get image for group: %v", err)
-        HandleAppError(w, err)
+        if err == sql.ErrNoRows {
+            log.Printf("No images found for group (ID: %d)", id)
+            WriteJSONResponse(w, http.StatusOK, []database.Image{})
+        } else {
+            log.Printf("Error: Failed to get images for group (ID: %d): %v", id, err)
+            HandleAppError(w, &AppError{
+                Message: "Failed to retrieve images for group",
+                Code:    http.StatusInternalServerError,
+            })
+        }
         return
     }

+    if len(result) == 0 {
+        log.Printf("No images found for group (ID: %d)", id)
+    }

     WriteJSONResponse(w, http.StatusOK, result)
 }

These changes provide more specific error messages, handle the case where no images are found for the group, and add a log message when the result is empty.


621-623: Enhance environment variable checks in regListHandler.

The use of environment variables for Harbor credentials is a good security practice. The check for the URL's presence is appropriate.

Consider adding checks for the username and password as well:

 func (s *Server) regListHandler(w http.ResponseWriter, r *http.Request) {
     username := os.Getenv("HARBOR_USERNAME")
     password := os.Getenv("HARBOR_PASSWORD")
     url := os.Getenv("HARBOR_URL")

-    if url == "" {
+    if username == "" || password == "" || url == "" {
         err := &AppError{
-            Message: "Missing URL in Request",
+            Message: "Missing Harbor credentials or URL in environment variables",
             Code:    http.StatusBadRequest,
         }
         HandleAppError(w, err)
         return
     }

     result, err := reg.FetchRepos(username, password, url)
     if err != nil {
         HandleAppError(w, err)
         return
     }

     WriteJSONResponse(w, http.StatusOK, result)
 }

This change ensures that all necessary credentials are present before attempting to fetch repositories.

Also applies to: 645-651

ground-control/internal/utils/helper.go (1)

91-95: Ensure uniqueness of project names when extracting from repositories

While extracting project names from the repositories, there is a possibility of duplicate project names being added to the projects slice. If duplicates can cause issues in later processing, consider ensuring that the list of projects is unique.

You can use a map to track unique project names:

projectSet := make(map[string]struct{})
for _, repo := range repos {
    proj := strings.Split(repo, "/")
    projectSet[proj[0]] = struct{}{}
}

var projects []string
for project := range projectSet {
    projects = append(projects, project)
}
ground-control/internal/database/images.sql.go (2)

114-116: Remove redundant call to rows.Close() in GetReposOfSatellite

You have already deferred rows.Close() at line 105. Explicitly calling rows.Close() again at lines 114-116 is unnecessary and can be removed to streamline the code.

Apply this diff to remove the redundant call:

-	if err := rows.Close(); err != nil {
-		return nil, err
-	}

Line range hint 149-151: Remove redundant call to rows.Close() in ListImages

In the ListImages function, rows.Close() is already deferred at line 132. The explicit call to rows.Close() at lines 149-151 is unnecessary and can be removed to clean up the code.

Apply this diff to remove the redundant call:

-	if err := rows.Close(); err != nil {
-		return nil, err
-	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d892b53 and b2f758b.

📒 Files selected for processing (28)
  • ground-control/internal/database/db.go (1 hunks)
  • ground-control/internal/database/group_images.sql.go (2 hunks)
  • ground-control/internal/database/groups.sql.go (3 hunks)
  • ground-control/internal/database/images.sql.go (3 hunks)
  • ground-control/internal/database/label_images.sql.go (1 hunks)
  • ground-control/internal/database/labels.sql.go (2 hunks)
  • ground-control/internal/database/models.go (3 hunks)
  • ground-control/internal/database/robot_accounts.sql.go (1 hunks)
  • ground-control/internal/database/satellite_groups.sql.go (1 hunks)
  • ground-control/internal/database/satellite_labels.sql.go (2 hunks)
  • ground-control/internal/database/satellite_token.sql.go (1 hunks)
  • ground-control/internal/database/satellites.sql.go (3 hunks)
  • ground-control/internal/server/handlers.go (10 hunks)
  • ground-control/internal/server/routes.go (1 hunks)
  • ground-control/internal/utils/helper.go (1 hunks)
  • ground-control/reg/adapter.go (1 hunks)
  • ground-control/sql/queries/group_images.sql (1 hunks)
  • ground-control/sql/queries/groups.sql (2 hunks)
  • ground-control/sql/queries/images.sql (1 hunks)
  • ground-control/sql/queries/labels.sql (1 hunks)
  • ground-control/sql/queries/robot_accounts.sql (1 hunks)
  • ground-control/sql/queries/satellite_labels.sql (1 hunks)
  • ground-control/sql/queries/satellite_token.sql (1 hunks)
  • ground-control/sql/queries/satellites.sql (1 hunks)
  • ground-control/sql/schema/001_satellites.sql (1 hunks)
  • ground-control/sql/schema/011_projects.sql (1 hunks)
  • ground-control/sql/schema/013_robot_accounts.sql (1 hunks)
  • ground-control/sql/schema/014_satellite_token.sql (1 hunks)
✅ Files skipped from review due to trivial changes (5)
  • ground-control/internal/database/db.go
  • ground-control/internal/database/label_images.sql.go
  • ground-control/internal/database/robot_accounts.sql.go
  • ground-control/internal/database/satellite_groups.sql.go
  • ground-control/internal/database/satellite_token.sql.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • ground-control/internal/server/routes.go
  • ground-control/sql/schema/001_satellites.sql
  • ground-control/sql/schema/011_projects.sql
🔇 Additional comments (41)
ground-control/sql/queries/satellite_labels.sql (2)

1-4: Summary: Changes align with ZTR implementation

The modifications in this file, particularly the improved operation naming, contribute to the overall goal of implementing Zero Touch Registration (ZTR) for satellites. The existing conflict resolution strategy (ON CONFLICT DO NOTHING) is well-suited for an automated system, as it prevents duplicate entries without raising errors.

While this change alone doesn't fully implement ZTR, it's a step in the right direction. As you continue to work on this feature, consider how this query will be used in the broader context of automatic satellite registration and labeling.


1-1: Approve operation name change

The change from AddSatelliteToLabel to AddLabelToSatellite is a good improvement. It more accurately describes the operation being performed, which is adding a label to a satellite rather than the other way around.

To ensure this change doesn't break any existing code, please run the following script to check for any references to the old operation name:

✅ Verification successful

Adjusting Reference Search Script

The previous script had an error with the python file type. Here's the corrected version to ensure all references to AddSatelliteToLabel are properly searched:


Operation Name Change Verified

No remaining references to AddSatelliteToLabel were found in the codebase. The operation name change to AddLabelToSatellite is safe and does not impact existing functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for any remaining references to the old operation name

# Test: Search for the old operation name. Expect: No results.
rg --type sql --type go --type python "AddSatelliteToLabel"

Length of output: 90


Script:

#!/bin/bash
# Description: Search for any remaining references to the old operation name

# Test: Search for the old operation name. Expect: No results.
rg --type sql --type go --type py "AddSatelliteToLabel"

Length of output: 55

ground-control/sql/queries/labels.sql (1)

Line range hint 1-4: LGTM: CreateLabel query is well-implemented.

The CreateLabel query is correctly implemented with the following good practices:

  1. Use of parameterized queries to prevent SQL injection.
  2. Inclusion of created_at and updated_at fields for proper record keeping.
  3. Returns all columns, allowing the application to access any auto-generated fields.
ground-control/sql/schema/014_satellite_token.sql (1)

11-12: LGTM! The "Down" migration is correct.

The "Down" migration properly reverses the "Up" migration by dropping the satellite_token table. This ensures that the migration can be rolled back if needed.

ground-control/sql/queries/group_images.sql (1)

Line range hint 1-15: Overall, the changes enhance group image management capabilities.

The addition of the RemoveImageFromGroup query complements the existing operations, providing a complete set of functionalities for managing group images. This aligns well with the PR objectives of implementing Zero Touch Registration for satellites, potentially allowing for dynamic updates of satellite imagery groups during the registration process.

ground-control/sql/queries/groups.sql (2)

3-3: Approve: Improved timestamp handling in CreateGroup query

The change from $3 to NOW() for the updated_at field is a good improvement. It ensures that the timestamp is always set to the current time when a group is created, reducing the chance of inconsistencies and simplifying the API by removing one parameter.


17-19: Approve: Addition of DeleteGroup query

The new DeleteGroup query provides necessary functionality to remove groups from the database. This is a standard operation in data management and the implementation is straightforward.

Consider potential improvements to the delete strategy

While the current implementation is functional, consider the following suggestions:

  1. Add a check for the existence of the group before deletion to provide more informative feedback.
  2. Consider implementing a soft delete mechanism if retaining historical data is important for your use case.
  3. Ensure that this delete operation aligns with the intended functionality for Zero Touch Satellite Registration as mentioned in the PR objectives.

Here's a potential improvement that includes an existence check:

-- name: DeleteGroup :execrows
WITH deleted AS (
  DELETE FROM groups
  WHERE id = $1
  RETURNING id
)
SELECT COUNT(*) FROM deleted;

This version returns the number of rows deleted, which will be 0 if the group didn't exist.

To ensure we're not overlooking any potential issues with foreign key relationships, let's run a check:

This will help us identify if there are any tables with foreign key constraints referencing the groups table, which might need to be considered in the delete strategy.

ground-control/sql/schema/013_robot_accounts.sql (2)

1-3: LGTM: Up migration structure is correct.

The Up migration is properly defined using the +goose Up comment. The migration file name follows the sequential naming convention, which is good for maintaining order in migrations.


12-13: LGTM: Down migration is correct, but be cautious about data loss.

The Down migration is properly defined using the +goose Down comment and correctly drops the robot_accounts table. This is an appropriate way to revert the Up migration.

However, it's important to note that executing this Down migration in a production environment will result in permanent data loss. Ensure that proper backups are in place and that the team is aware of this risk. Consider running the following verification script to check for existing data before applying the migration in production:

This script will help verify the existence of the table and presence of data before applying the migration.

ground-control/sql/queries/satellite_token.sql (2)

6-9: LGTM! Efficient and secure token lookup.

The GetSatelliteIDByToken command is well-implemented. It uses a parameterized query for security and selects only the necessary column for efficiency. The :one annotation correctly indicates that it returns a single row.


1-20: Overall, good implementation of satellite token management for Zero Touch Registration.

This file successfully implements the necessary SQL queries for managing satellite tokens, which is crucial for the Zero Touch Registration (ZTR) system mentioned in the PR objectives. The queries cover creation, retrieval, listing, and deletion of tokens, providing a solid foundation for the ZTR implementation.

A few suggestions for improvement have been made:

  1. Adding a UNIQUE constraint on the token column.
  2. Implementing pagination for the ListToken query.
  3. Explicitly listing required columns instead of using * in select statements.
  4. Returning affected rows count for the DeleteToken operation.

These changes will enhance performance, security, and usability of the satellite token management system, further supporting the goals outlined in issues #41 and #36.

ground-control/internal/database/satellite_labels.sql.go (3)

3-3: LGTM: sqlc version update

The update to sqlc v1.27.0 is a good practice to ensure you're using the latest features and bug fixes.


18-21: LGTM: Struct rename for consistency

The rename of the struct from AddSatelliteToLabelParams to AddLabelToSatelliteParams maintains consistency with the function name change. The struct fields correctly match the SQL query parameters.


Line range hint 1-26: Summary: Consistent renaming supports Zero Touch Registration implementation

The changes in this file, particularly the renaming of the AddLabelToSatellite function and related components, contribute to the implementation of Zero Touch Registration (ZTR) for satellites. These updates improve code consistency and readability, which is crucial for maintaining a robust automatic registration system.

While the core functionality remains unchanged, these naming improvements lay a foundation for a more intuitive API that could be used in the ZTR process. As you continue to implement the ZTR system, consider how this function will be integrated into the automatic registration flow and ensure that error handling and logging are comprehensive throughout the process.

ground-control/sql/queries/satellites.sql (2)

9-11: LGTM! The new GetSatellite query is a valuable addition.

This query provides a straightforward way to retrieve a satellite by its id, which is essential for satellite management in the ZTR system. The use of LIMIT 1 is appropriate as id should be unique.


Line range hint 1-28: Overall, the changes effectively support Zero Touch Registration (ZTR) for satellites.

The modifications in this file align well with the PR objectives:

  1. Removal of token-based registration in CreateSatellite.
  2. Introduction of GetSatellite query for id-based retrieval.
  3. Deprecation of GetSatelliteByToken.

These changes collectively contribute to implementing ZTR as outlined in issues #41 and #36, simplifying the satellite registration process and moving away from manual token-based systems.

ground-control/sql/queries/robot_accounts.sql (1)

6-8: LGTM! Simple and effective query.

The GetRobotAccount command efficiently retrieves a single robot account by its id. It's concise and assumes 'id' is the primary key, which is a common and appropriate practice.

ground-control/sql/queries/images.sql (2)

13-15: Approve rename of DeleteImageList to DeleteImage.

The rename from DeleteImageList to DeleteImage is appropriate as it more accurately describes the query's function of deleting a single image by its ID.


36-50: Clarify the replacement of GetImagesForSatellite with GetReposOfSatellite.

The previous GetImagesForSatellite query has been commented out and effectively replaced by the new GetReposOfSatellite query. This change represents a significant shift in functionality:

  1. The old query used a satellite token for identification, while the new one uses a satellite_id.
  2. The old query retrieved all image data, whereas the new query only fetches repository names.

Could you please clarify the reasons for this change? Is it intentional to only retrieve repository names instead of full image data? Also, how will this affect the systems that were previously using the GetImagesForSatellite query?

To verify the impact of this change, we can run the following script:

This script will help us identify any code that might be affected by this change.

✅ Verification successful

Verification Successful: The GetImagesForSatellite query has been effectively replaced by GetReposOfSatellite. No active references to the old query were found in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for usages of the old GetImagesForSatellite query in the codebase

# Search for GetImagesForSatellite usages
echo "Searching for GetImagesForSatellite usages:"
rg --type go "GetImagesForSatellite"

# Search for potential new usages of GetReposOfSatellite
echo "Searching for GetReposOfSatellite usages:"
rg --type go "GetReposOfSatellite"

Length of output: 999

ground-control/reg/adapter.go (3)

19-24: Consider renaming the struct to singular form for clarity.

The struct name Images is plural, which might be confusing as it represents a single image configuration. Consider renaming it to Image or ImageConfig for better clarity.

Clarify the relationship between Images and Image structs.

There's an existing Image struct in the file. Could you please clarify the relationship between the new Images struct and the existing Image struct? Are they meant to be used together or for different purposes?

Explain how this struct contributes to Zero Touch Registration.

Given that this PR is about implementing Zero Touch Registration for satellites, could you provide some context on how this new struct contributes to that goal? It would be helpful to understand its role in the registration process.


19-24: Provide information on the planned usage of the new Images struct.

The new Images struct is not used anywhere in the existing code provided. Could you please explain how this struct is intended to be used? Are there planned updates to existing functions or new functions that will utilize this struct?


Line range hint 31-78: Clarify the role of FetchRepos in satellite registration.

The FetchRepos function fetches repositories from a Docker registry. Could you please explain how this function relates to the Zero Touch Registration process for satellites? Understanding its role in the larger context would be helpful.

Consider making the repository limit configurable.

The URL in the FetchRepos function includes a hardcoded limit of 1000 repositories (n=1000). Consider making this limit configurable to allow for more flexibility in different scenarios.

You could modify the function signature to include a limit parameter:

func FetchRepos(username, password, url string, limit int) ([]string, error) {
    // ...
    url = fmt.Sprintf("%s/v2/_catalog?n=%d", url, limit)
    // ...
}
ground-control/internal/database/labels.sql.go (3)

Line range hint 1-79: Summary of changes and relation to PR objectives

The changes in this file introduce new database operations for managing labels, which appear to be consistent with the PR objectives of implementing Zero Touch Registration (ZTR) for satellites. The new functions (DeleteLabel, GetLabelByID, and GetLabelByName) provide the necessary database interactions to support label management, which could be a part of the satellite registration process.

However, it's important to note:

  1. This file is generated by sqlc and should not be manually edited.
  2. The direct connection between these label operations and ZTR is not immediately clear from this file alone.

To ensure these changes align with the ZTR implementation, please run the following script:

#!/bin/bash
# Description: Check for ZTR-related code in the codebase

# Test: Search for ZTR-related functions or comments
rg -i "zero.?touch|ztr|satellite.?registration" --type go

# Test: Check if labels are used in any ZTR-related code
rg -i "zero.?touch|ztr|satellite.?registration" -C 5 | rg "label"

Please review the results to confirm that these label operations are indeed being used in the context of Zero Touch Registration for satellites.


64-79: LGTM with suggestions and a question.

The GetLabelByName function is well-implemented. It uses a parameterized query to prevent SQL injection, properly handles the context, and correctly scans the result into a Label struct.

Consider adding a comment to explain the function's behavior, especially regarding what happens if the label doesn't exist. For example:

// GetLabelByName retrieves a label from the database by its name.
// If the label doesn't exist, it returns an error (likely sql.ErrNoRows).
func (q *Queries) GetLabelByName(ctx context.Context, labelName string) (Label, error) {
    // ... (existing code)
}

This function is very similar to GetLabelByID. Could we potentially refactor to reduce duplication? For example, we could have a single internal function that takes a query string and a parameter, and then have GetLabelByID and GetLabelByName as thin wrappers around this internal function.

To verify if this refactoring is feasible and beneficial, please run the following script:

#!/bin/bash
# Description: Check for similar functions in the codebase

# Test: Search for functions with similar structure
ast-grep --lang go --pattern $'func (q *Queries) Get$_($_) (Label, error) {
    row := q.db.QueryRowContext(ctx, $_, $_)
    var i Label
    err := row.Scan(
        &i.ID,
        &i.LabelName,
        &i.CreatedAt,
        &i.UpdatedAt,
    )
    return i, err
}'

# Test: Count the number of such functions
ast-grep --lang go --pattern $'func (q *Queries) Get$_($_) (Label, error) {
    row := q.db.QueryRowContext(ctx, $_, $_)
    var i Label
    err := row.Scan(
        &i.ID,
        &i.LabelName,
        &i.CreatedAt,
        &i.UpdatedAt,
    )
    return i, err
}' | wc -l

Please review the results to determine if refactoring would be beneficial.


3-3: Verify the impact of sqlc version update.

The sqlc version has been updated from v1.26.0 to v1.27.0. While this is likely beneficial, it's important to ensure that this update doesn't introduce any breaking changes or unexpected behavior.

Run the following script to check for any significant changes or known issues in the new sqlc version:

Please review the changelog and any reported issues to ensure that the update is safe and beneficial for your project.

✅ Verification successful

sqlc version update is safe.

No breaking changes or known issues found in sqlc v1.27.0.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for significant changes or known issues in the new sqlc version

# Test: Fetch the changelog for sqlc v1.27.0
curl -s https://api.github.com/repos/kyleconroy/sqlc/releases/tags/v1.27.0 | jq -r '.body'

# Test: Search for any reported issues related to v1.27.0
gh issue list -R kyleconroy/sqlc -S "v1.27.0 in:body" --json number,title,state

Length of output: 936


Script:

#!/bin/bash
# Description: Verify sqlc version update from v1.26.0 to v1.27.0

# Check if required tools are installed
command -v curl >/dev/null 2>&1 || { echo "curl is required but it's not installed. Exiting."; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq is required but it's not installed. Exiting."; exit 1; }
command -v gh >/dev/null 2>&1 || { echo "GitHub CLI (gh) is required but it's not installed. Exiting."; exit 1; }

# Fetch the changelog for sqlc v1.27.0
echo "Fetching changelog for sqlc v1.27.0..."
curl -s https://api.github.com/repos/kyleconroy/sqlc/releases/tags/v1.27.0 | jq -r '.body' > sqlc_v1.27.0_changelog.txt
if [ $? -ne 0 ]; then
  echo "Failed to fetch or parse the changelog."
  exit 1
fi
echo "Changelog fetched successfully."

# Search for any reported issues related to v1.27.0
echo "Searching for issues related to sqlc v1.27.0..."
gh issue list -R kyleconroy/sqlc --search "v1.27.0 in:body" --json number,title,state > sqlc_v1.27.0_issues.json
if [ $? -ne 0 ]; then
  echo "Failed to fetch issues from GitHub."
  exit 1
fi
echo "Issue search completed successfully."

Length of output: 612

ground-control/internal/database/group_images.sql.go (3)

81-84: LGTM: New SQL command for removing an image from a group.

The new SQL command removeImageFromGroup is well-structured and follows the existing naming conventions. It correctly uses parameterized queries to prevent SQL injection, which is a good security practice.


Line range hint 1-94: Summary: New functionality aligns with PR objectives.

The changes made to this file introduce a new capability to remove an image from a group, which could be a part of the Zero Touch Registration (ZTR) process for satellites. The implementation is consistent with the existing code style and follows good practices for SQL operations and error handling.


3-3: LGTM: sqlc version update.

The update to sqlc v1.27.0 is a good practice to keep the tool up-to-date. However, it's important to ensure that this update doesn't introduce any breaking changes.

To verify the compatibility of the new sqlc version with your project, please run the following commands:

If the git diff command shows no differences, it indicates that the new version is compatible with your current setup.

ground-control/internal/database/groups.sql.go (4)

15-15: Improvement in timestamp handling for group creation.

The modification to use NOW() for the updated_at field in the createGroup SQL command is a positive change. This approach ensures that the updated_at timestamp is always set to the current time when a group is created, improving data consistency and simplifying the API.


25-25: API simplification for CreateGroup function.

The removal of the UpdatedAt parameter from the CreateGroup function is consistent with the SQL command changes and simplifies the API. This is a positive change that reduces the potential for errors when creating groups.

Consider updating any relevant documentation or comments to reflect this change in the API.


36-44: New DeleteGroup functionality added.

The addition of the deleteGroup SQL command and the corresponding DeleteGroup function is a valuable feature that allows for the removal of groups from the database. The implementation is correct and consistent with the existing code structure.

Consider adding a check for the existence of the group before deletion to provide more informative error handling. This could be done by returning the number of affected rows and checking if it's zero:

 func (q *Queries) DeleteGroup(ctx context.Context, id int32) error {
-	_, err := q.db.ExecContext(ctx, deleteGroup, id)
-	return err
+	result, err := q.db.ExecContext(ctx, deleteGroup, id)
+	if err != nil {
+		return err
+	}
+	rowsAffected, err := result.RowsAffected()
+	if err != nil {
+		return err
+	}
+	if rowsAffected == 0 {
+		return fmt.Errorf("no group found with id %d", id)
+	}
+	return nil
 }

This change would provide more meaningful feedback when attempting to delete a non-existent group.


Line range hint 1-110: Overall assessment of changes in groups.sql.go

The changes made to this file are well-aligned with the PR objectives and represent improvements in the API for group management. Key points:

  1. The sqlc version update should be verified for any potential impacts.
  2. Timestamp handling for group creation has been improved.
  3. The API for creating groups has been simplified.
  4. A new feature for deleting groups has been added, with a suggestion for improved error handling.

These changes contribute positively to the implementation of Zero Touch Registration for satellites by enhancing the group management capabilities of the system.

ground-control/internal/database/satellites.sql.go (4)

18-19: LGTM! Function signature simplified.

The changes to the CreateSatellite function look good. The simplified signature with a single 'name' parameter aligns well with the modifications made to the SQL query.


45-46: LGTM! Function updated to use ID for retrieval.

The changes to the GetSatellite function look good. Using ID for satellite retrieval is more efficient and aligns well with typical CRUD operations.


62-63: LGTM! New function added for name-based retrieval.

The addition of the GetSatelliteByName function is good. It complements the new getSatelliteByName SQL query and provides a clear way to retrieve satellites by name.


3-3: Verify sqlc version update impact.

The sqlc version has been updated from v1.26.0 to v1.27.0. While this is likely a beneficial update, it's important to ensure that it doesn't introduce any breaking changes or unexpected behavior.

Run the following script to check for any significant changes or known issues in the new version:

ground-control/internal/server/handlers.go (1)

Line range hint 1-674: Overall assessment of changes in handlers.go

The changes in this file represent significant improvements and additions to the functionality of the server. New handlers for satellite registration, image management, and group handling have been implemented, and existing functions have been enhanced.

Key strengths:

  1. Improved error handling in many functions.
  2. Use of database transactions for complex operations.
  3. Addition of new functionality for satellite registration and management.

Areas for further improvement:

  1. Some long functions (e.g., registerSatelliteHandler) could benefit from refactoring into smaller, more focused functions.
  2. Consistency in error handling across all functions could be improved.
  3. Some functions (getSatelliteByID, deleteSatelliteByID) need proper implementation.
  4. Consider adding pagination to list operations for better scalability.

Overall, the changes are positive and enhance the capabilities of the server. Addressing the suggested improvements will further increase the code's maintainability and robustness.

ground-control/internal/utils/helper.go (1)

16-55: Review of ParseArtifactURL function

The ParseArtifactURL function is well-structured and effectively parses the artifact URL into its components. Apart from the issue noted below regarding the handling of both tag and digest, the function logic is sound.

ground-control/internal/database/images.sql.go (3)

3-3: Update sqlc to version v1.27.0

The sqlc code generator has been updated to v1.27.0. Ensure that this version is compatible with your project and that any new features or changes are accounted for.


127-128: Verify that replacing GetImagesForSatellite with ListImages is intentional

The function GetImagesForSatellite has been replaced with ListImages, which now retrieves all images without filtering by satellite. Please confirm that this change is intentional and verify that all call sites and dependent functionality have been updated accordingly to prevent unintended behavior.

Run the following script to identify all usages of GetImagesForSatellite:

#!/bin/bash
# Description: Find all references to the old `GetImagesForSatellite` function.

# Test: Search for usages of `GetImagesForSatellite` in the codebase.
rg --type go --word-regexp 'GetImagesForSatellite'

55-56: Ensure all references to DeleteImageList are updated to DeleteImage

The function DeleteImageList has been renamed to DeleteImage. Please verify that all references to the old function name have been updated throughout the codebase to prevent any undefined references or runtime errors.

Run the following script to find any occurrences of DeleteImageList:

ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
ground-control/internal/database/models.go Outdated Show resolved Hide resolved
ground-control/internal/database/models.go Outdated Show resolved Hide resolved
ground-control/internal/utils/helper.go Outdated Show resolved Hide resolved
ground-control/internal/utils/helper.go Outdated Show resolved Hide resolved
ground-control/internal/utils/helper.go Outdated Show resolved Hide resolved
Copy link

@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

🧹 Outside diff range and nitpick comments (10)
ground-control/internal/server/handlers.go (10)

142-162: Improved error handling and logging in addImageHandler.

The changes to this function enhance error handling and logging, which is a good practice. The use of ParseArtifactURL and custom AppError type allows for more specific error responses.

For consistency with other error handling in this file, consider wrapping the error returned by ParseArtifactURL with fmt.Errorf:

 	image, err := utils.ParseArtifactURL(req.Image)
 	if err != nil {
 		log.Println(err)
+		err = fmt.Errorf("error: invalid artifact URL: %v", err)
 		err = &AppError{
 			Message: "Error: Invalid Artifact URL",
 			Code:    http.StatusBadRequest,
 		}
 		HandleAppError(w, err)
 		return
 	}

177-187: Implement listImageHandler with proper error handling.

The new listImageHandler function correctly retrieves and returns the list of images. The error handling follows the pattern used in other parts of the file.

Based on a previous learning, consider modifying the error handling to create an AppError:

 func (s *Server) listImageHandler(w http.ResponseWriter, r *http.Request) {
 	result, err := s.dbQueries.ListImages(r.Context())
 	if err != nil {
-		err = fmt.Errorf("error: list images failed: %v", err)
 		log.Println(err)
+		err = &AppError{
+			Message: fmt.Sprintf("Failed to list images: %v", err),
+			Code:    http.StatusInternalServerError,
+		}
 		HandleAppError(w, err)
 		return
 	}

 	WriteJSONResponse(w, http.StatusOK, result)
 }

This change aligns with the AppError usage in other parts of the file and provides more structured error information.


189-216: Well-implemented removeImageHandler with proper error handling.

The removeImageHandler function is well-implemented with appropriate error handling for invalid image IDs and database operations. The specific handling of sql.ErrNoRows is a good practice.

For consistency with other error handling in this file, consider using AppError for the invalid imageID case:

 	id, err := strconv.ParseInt(imageID, 10, 32)
 	if err != nil {
-		err = fmt.Errorf("error: invalid imageID: %v", err)
+		err = &AppError{
+			Message: fmt.Sprintf("Invalid imageID: %v", err),
+			Code:    http.StatusBadRequest,
+		}
 		log.Println(err)
 		HandleAppError(w, err)
 		return
 	}

This change would make the error handling more consistent throughout the file.


374-435: Well-implemented ztrHandler with proper transaction management.

The ztrHandler function is well-implemented with appropriate transaction management and error handling. It correctly handles the Zero Touch Registration (ZTR) process for satellites.

For consistency with other error handling in this file, consider using AppError for the "Robot Account Not Found" case:

 	if err != nil {
 		log.Println("Robot Account Not Found")
 		log.Println(err)
-		err := &AppError{
+		err = &AppError{
 			Message: "Error: Robot Account Not Found",
 			Code:    http.StatusInternalServerError,
 		}
 		HandleAppError(w, err)
 		tx.Rollback()
 		return
 	}

This change would make the error handling more consistent throughout the file.


Line range hint 457-465: Incorrect implementation of deleteSatelliteByID.

The deleteSatelliteByID function is incorrectly implemented. It's currently using the ListSatellites query instead of deleting a specific satellite by ID.

Implement the correct logic for deleting a satellite by ID:

func (s *Server) deleteSatelliteByID(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	idStr := vars["id"]
	
	id, err := strconv.ParseInt(idStr, 10, 32)
	if err != nil {
		HandleAppError(w, &AppError{
			Message: "Invalid satellite ID",
			Code:    http.StatusBadRequest,
		})
		return
	}

	err = s.dbQueries.DeleteSatellite(r.Context(), int32(id))
	if err != nil {
		if err == sql.ErrNoRows {
			HandleAppError(w, &AppError{
				Message: "Satellite not found",
				Code:    http.StatusNotFound,
			})
		} else {
			HandleAppError(w, err)
		}
		return
	}

	WriteJSONResponse(w, http.StatusOK, map[string]string{"message": "Satellite deleted successfully"})
}

This implementation assumes the existence of a DeleteSatellite method in your database queries. If it doesn't exist, you'll need to add it to your database layer.


577-600: Well-implemented listGroupImages function with proper error handling.

The listGroupImages function is correctly implemented with appropriate error handling for invalid group IDs and database operations. It follows the established patterns in the file for parameter parsing, error handling, and response writing.

For consistency with other error handling in this file, consider using HandleAppError for the invalid groupID case:

 	id, err := strconv.ParseInt(groupID, 10, 32)
 	if err != nil {
-		log.Printf("Error: Invalid groupID: %v", err)
-		err := &AppError{
+		log.Println(err)
+		err = &AppError{
 			Message: "Error: Invalid GroupID",
 			Code:    http.StatusBadRequest,
 		}
 		HandleAppError(w, err)
 		return
 	}

This change would make the error handling more consistent throughout the file.


602-616: Remove commented-out GetImagesForSatellite function.

The GetImagesForSatellite function is currently commented out. Keeping commented-out code in the codebase can lead to confusion and clutter.

If this function is no longer needed, consider removing it entirely. If it's intended for future use, consider moving it to a separate file or adding a TODO comment explaining why it's kept and when it might be used.

-// func (s *Server) GetImagesForSatellite(w http.ResponseWriter, r *http.Request) {
-// 	token, err := GetAuthToken(r)
-// 	if err != nil {
-// 		HandleAppError(w, err)
-// 		return
-// 	}
-// 	result, err := s.dbQueries.GetImagesForSatellite(r.Context(), token)
-// 	if err != nil {
-// 		log.Printf("Error: Failed to get image for satellite: %v", err)
-// 		HandleAppError(w, err)
-// 		return
-// 	}
-//
-// 	WriteJSONResponse(w, http.StatusOK, result)
-// }

629-631: Improved security by using environment variables for Harbor credentials.

The use of environment variables for Harbor credentials and URL is a good security practice. It prevents hardcoding sensitive information in the source code.

Consider adding validation for the username and password to ensure they are not empty:

 	username := os.Getenv("HARBOR_USERNAME")
 	password := os.Getenv("HARBOR_PASSWORD")
 	url := os.Getenv("HARBOR_URL")

+	if username == "" || password == "" {
+		err := &AppError{
+			Message: "Missing Harbor credentials",
+			Code:    http.StatusInternalServerError,
+		}
+		HandleAppError(w, err)
+		return
+	}

 	if url == "" {
 		err := &AppError{
 			Message: "Missing URL in Request",
 			Code:    http.StatusBadRequest,
 		}
 		HandleAppError(w, err)
 		return
 	}

This addition will ensure that the function fails gracefully if the Harbor credentials are not properly set in the environment.


655-661: Improved getGroupHandler with proper group ID parsing.

The changes to getGroupHandler correctly parse the group ID from the URL parameters, which is an improvement. However, the error handling can be made more consistent with the rest of the file.

Consider updating the error handling to use AppError and HandleAppError for consistency:

 	groupID, err := strconv.ParseInt(group, 10, 32)
 	if err != nil {
-		http.Error(w, err.Error(), http.StatusBadRequest)
+		log.Println(err)
+		HandleAppError(w, &AppError{
+			Message: "Invalid group ID",
+			Code:    http.StatusBadRequest,
+		})
 		return
 	}

 	result, err := s.dbQueries.GetGroupByID(r.Context(), int32(groupID))
 	if err != nil {
-		http.Error(w, err.Error(), http.StatusNotFound)
+		log.Println(err)
+		if err == sql.ErrNoRows {
+			HandleAppError(w, &AppError{
+				Message: "Group not found",
+				Code:    http.StatusNotFound,
+			})
+		} else {
+			HandleAppError(w, err)
+		}
 		return
 	}

These changes will make the error handling in getGroupHandler more consistent with the rest of the file.


Line range hint 1-707: Overall assessment of changes in handlers.go

The changes in this file significantly enhance the functionality of the server, particularly in areas of satellite registration, image management, and group handling. Here's a summary of the key points:

  1. Improved security practices, such as using environment variables for sensitive information.
  2. Consistent use of transactions for complex operations, ensuring data integrity.
  3. Generally good error handling practices, with some room for improvement in consistency.
  4. New functions that expand the server's capabilities in managing satellites, images, and groups.

While the individual functions are generally well-implemented, there are opportunities to improve the overall architecture:

  1. Consider breaking down large functions (e.g., registerSatelliteHandler) into smaller, more focused functions to improve maintainability.
  2. Implement a consistent error handling strategy across all functions, preferably using the AppError type and HandleAppError function.
  3. Consider creating a separate package for database operations to better separate concerns and improve modularity.
  4. Implement comprehensive logging throughout the handlers for better observability and debugging.

These architectural improvements will make the codebase more maintainable, easier to test, and more scalable as the project grows.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b2f758b and 99b1af5.

📒 Files selected for processing (2)
  • ground-control/internal/server/handlers.go (10 hunks)
  • ground-control/internal/utils/helper.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ground-control/internal/utils/helper.go
🧰 Additional context used
📓 Learnings (1)
ground-control/internal/server/handlers.go (1)
Learnt from: bupd
PR: container-registry/harbor-satellite#54
File: ground-control/internal/server/handlers.go:176-186
Timestamp: 2024-09-30T06:15:48.512Z
Learning: In `listImageHandler`, the error should be wrapped with `fmt.Errorf` before being passed to `HandleAppError`.
🔇 Additional comments (4)
ground-control/internal/server/handlers.go (4)

437-445: Correctly implemented listSatelliteHandler.

The listSatelliteHandler function is correctly implemented, retrieving and returning the list of satellites with proper error handling.


555-575: Well-implemented deleteImageFromGroup function.

The deleteImageFromGroup function is correctly implemented with proper error handling and database interaction. It follows the established patterns in the file for request decoding, error handling, and response writing.


671-684: Well-implemented GenerateRandomToken function.

The GenerateRandomToken function is correctly implemented, using crypto/rand for secure random number generation. It properly handles the conversion of bytes to a hex-encoded string, ensuring the generated token has the specified character length.

This implementation is secure and follows best practices for generating random tokens.


33-36: 🛠️ Refactor suggestion

Consider using value types instead of pointer types for slices.

The Groups and Labels fields in the RegisterSatelliteParams struct are defined as pointers to slices (*[]string). In Go, slices are already reference types, so using pointers to slices is usually unnecessary and can complicate the code.

Consider simplifying the struct definition:

 type RegisterSatelliteParams struct {
 	Name   string    `json:"satellite_name"`
-	Groups *[]string `json:"groups,omitempty"`
-	Labels *[]string `json:"labels,omitempty"`
+	Groups []string `json:"groups,omitempty"`
+	Labels []string `json:"labels,omitempty"`
 }

This change will simplify the code and reduce the need for nil checks when accessing these fields.

Likely invalid or redundant comment.

ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
ground-control/internal/server/handlers.go Outdated Show resolved Hide resolved
@bupd bupd marked this pull request as draft October 8, 2024 13:46
Copy link

@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: 39

🧹 Outside diff range and nitpick comments (26)
ground-control/sql/schema/012_robot_projects.sql (1)

3-7: Consider enhancing the table for Zero Touch Registration.

The table structure is well-designed with appropriate foreign key constraints and composite primary key. However, for a Zero Touch Registration system, consider these enhancements:

  1. Add audit timestamps to track automated registrations:
CREATE TABLE robot_projects (
  robot_id INT REFERENCES robots(id) ON DELETE CASCADE,
  project_id INT REFERENCES projects(id) ON DELETE CASCADE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  registered_by VARCHAR(255), -- to track if registration was automatic or manual
  PRIMARY KEY (robot_id, project_id)
);
  1. Add an index to optimize project-based queries:
CREATE INDEX idx_robot_projects_project_id ON robot_projects(project_id);
ground-control/sql/schema/002_groups.sql (1)

Line range hint 1-13: Document schema design decisions.

Please add comments to document:

  1. The purpose of the registry_url (format expectations, usage)
  2. The structure of the projects array (format, limitations)
  3. The relationship between groups, projects, and the Zero Touch Registration flow
-- +goose Up

+ -- groups table stores satellite group configurations for Zero Touch Registration
+ -- registry_url: The URL of the registry service (must be valid HTTP/HTTPS URL)
+ -- projects: Array of project identifiers associated with this group
CREATE TABLE groups (
ground-control/.env (1)

6-7: Document the purpose of ZOT Registry

Since this PR implements Zero Touch Registration, it would be helpful to document how the ZOT Registry relates to this feature.

Add a comment explaining:

  1. The role of ZOT Registry in the Zero Touch Registration process
  2. Whether this is a required component for ZTR
ground-control/sql/queries/satellite_groups.sql (1)

Line range hint 1-4: LGTM! The insert operation handles conflicts appropriately.

The ON CONFLICT DO NOTHING clause ensures idempotency, which is crucial for zero-touch registration scenarios where multiple registration attempts might occur.

Consider adding foreign key constraints to ensure referential integrity:

ALTER TABLE satellite_groups 
  ADD CONSTRAINT fk_satellite 
    FOREIGN KEY (satellite_id) 
    REFERENCES satellites(id),
  ADD CONSTRAINT fk_group
    FOREIGN KEY (group_id) 
    REFERENCES groups(id);
ground-control/sql/queries/satellite_token.sql (3)

6-9: Add index on token column for performance.

Since this query filters by token, which is likely to be frequently used for lookups, consider adding an index on the token column in your schema file.

CREATE INDEX idx_satellite_token_token ON satellite_token(token);

18-20: Consider implementing soft delete.

For Zero Touch Registration (ZTR) systems, maintaining an audit trail is crucial. Consider implementing soft delete by adding a deleted_at timestamp column instead of physically removing the records.

-DELETE FROM satellite_token
-WHERE token = $1;
+UPDATE satellite_token
+SET deleted_at = NOW()
+WHERE token = $1 AND deleted_at IS NULL;

This would require schema modification to add the deleted_at column and updating other queries to include WHERE deleted_at IS NULL.


1-20: Add constraints for token column.

Since this is part of an authentication system, consider adding these constraints to your schema:

  1. Make token column unique to prevent duplicates
  2. Add a minimum length requirement
  3. Consider adding a check constraint for token format if you're using a specific pattern
ALTER TABLE satellite_token
  ADD CONSTRAINT unique_token UNIQUE (token),
  ADD CONSTRAINT check_token_length CHECK (length(token) >= 32);
ground-control/sql/queries/satellites.sql (1)

Line range hint 1-28: Consider documenting the new satellite authentication flow.

The changes show a shift from token-based to a new authentication mechanism for Zero Touch Registration. Consider:

  1. Documenting the new authentication flow in the README
  2. Adding migration guides for existing satellite registrations
  3. Updating any API documentation to reflect these changes

Would you like me to help create documentation templates for these changes?

ground-control/internal/models/models.go (3)

3-7: Consider improving field naming clarity in StateArtifact struct.

The struct fields could be more descriptive to better reflect their roles in satellite registration:

  • Group - Is this a satellite group? Consider a more specific name like SatelliteGroup
  • Registry - The purpose isn't immediately clear. Consider RegistryURL or ArtifactRegistry if it's a container registry

18-21: Clarify the purpose of States field in ZtrResult.

The States field name is ambiguous. If these are registration states or status messages, consider renaming to be more specific (e.g., RegistrationStates or StatusMessages).


23-26: Enhance security measures for Account struct.

The Account struct handles sensitive data but lacks validation constraints. Consider:

  1. Adding field validation tags (e.g., using validator package)
  2. Adding documentation about secret handling
  3. Implementing methods to safely handle the secret (e.g., masking in logs)

Example implementation with validation:

type Account struct {
    // Name represents the account identifier for satellite registration
    Name   string `json:"name" validate:"required,min=3,max=64"`
    // Secret is the authentication token. Should be handled securely and never logged.
    Secret string `json:"secret" validate:"required,min=32"`
}

// String implements Stringer interface to prevent accidental secret logging
func (a Account) String() string {
    return fmt.Sprintf("Account{Name: %s, Secret: [REDACTED]}", a.Name)
}
ground-control/reg/harbor/project.go (1)

20-22: Enhance error context

The error message could provide more context about what was being attempted.

-        return nil, fmt.Errorf("error: listing projects: %v", err)
+        return nil, fmt.Errorf("error: failed to list Harbor projects: %w", err)
ground-control/reg/harbor/replication.go (2)

14-28: Add parameter validation and consider pagination limits.

The function accepts ListParams without validation. Consider adding checks for:

  • Negative page numbers
  • Reasonable page size limits
  • Valid sort parameters

Here's a suggested improvement:

 func ListReplication(ctx context.Context, opts ListParams, client *v2client.HarborAPI) ([]*models.ReplicationPolicy, error) {
+       if opts.Page < 1 {
+               opts.Page = 1
+       }
+       if opts.PageSize < 1 || opts.PageSize > 100 {
+               opts.PageSize = 100
+       }
+
        response, err := client.Replication.ListReplicationPolicies(

24-26: Enhance error context for debugging.

The error wrapping could provide more context about the operation and parameters.

-               return nil, fmt.Errorf("error: listing replication policies: %v", err)
+               return nil, fmt.Errorf("failed to list replication policies (page: %d, size: %d, query: %s): %w", 
+                       opts.Page, opts.PageSize, opts.Q, err)
ground-control/sql/queries/robot_accounts.sql (1)

22-24: Consider implementing soft delete.

For audit purposes and data recovery, consider implementing soft delete instead of hard delete. This is especially important for robot accounts that might have historical data or audit requirements.

Consider adding a deleted_at timestamp:

- DELETE FROM robot_accounts
- WHERE id = $1;
+ UPDATE robot_accounts
+ SET deleted_at = NOW()
+ WHERE id = $1 AND deleted_at IS NULL;
ground-control/internal/server/routes.go (1)

24-25: Implement the group satellite listing functionality.

The TODO comment indicates a missing feature for listing satellites in a group, but the commented route already provides the solution.

Consider implementing this endpoint as it's a crucial functionality for group management. Would you like me to:

  1. Generate the handler implementation for this endpoint?
  2. Open a GitHub issue to track this task?
ground-control/reg/adapter.go (1)

19-24: LGTM! Structure follows container image naming conventions.

The Images struct correctly represents container image metadata with appropriate serialization tags and optional fields.

Consider adding documentation to explain the purpose and usage of this struct:

+// Images represents the components of a container image location,
+// including the registry host, repository path, and optional tag or digest.
+// This is used for managing container images during satellite registration.
 type Images struct {
ground-control/internal/database/satellite_token.sql.go (1)

40-51: Implement rate limiting for token lookups.

Frequent token lookups could be used for brute force attacks to discover valid tokens. Consider implementing rate limiting at the database or application level.

Consider:

  1. Adding rate limiting middleware
  2. Implementing token lookup caching
  3. Adding logging for security audits (but ensure tokens are not logged in plaintext)
ground-control/internal/database/satellite_groups.sql.go (1)

Line range hint 1-97: Architecture aligns well with Zero Touch Registration requirements.

The implementation provides a solid foundation for satellite group management, which is crucial for Zero Touch Registration (ZTR). The queries support:

  1. Dynamic group assignment during automatic registration
  2. Flexible group management with add/remove capabilities
  3. Efficient group membership lookups in both directions (satellite→groups and group→satellites)

These features align well with the ZTR objectives from issues #41 and #36.

ground-control/internal/database/satellites.sql.go (2)

57-63: LGTM! Consider adding an index on the name column.

The implementation looks correct. Since this function performs lookups by name, consider adding an index on the name column if not already present, especially if this query is frequently used.


80-82: Remove commented query from source SQL file.

These comments appear in the generated code, suggesting they exist in the source SQL file. Since the token-based lookup has been removed, these comments should be cleaned up from the source SQL file to maintain clarity.

ground-control/reg/harbor/robot.go (3)

88-99: Add input validation for robot ID.

Consider validating that the id parameter is positive before making the API call.

 func GetRobotAccount(ctx context.Context, id int64, client *v2client.HarborAPI) (*models.Robot, error) {
+	if id <= 0 {
+		return nil, fmt.Errorf("invalid robot ID: must be positive")
+	}
 	response, err := client.Robot.GetRobotByID(

Line range hint 128-144: Consider making access permissions configurable.

The hardcoded permissions array could be made configurable to support different robot account types with varying access levels.

+// RobotAccessLevel defines the set of permissions for a robot account
+type RobotAccessLevel struct {
+    Action   string
+    Resource string
+}
+
+// DefaultRobotAccess provides the default minimal permissions
+var DefaultRobotAccess = []RobotAccessLevel{
+    {Action: "read", Resource: "artifact"},
+    {Action: "read", Resource: "repository"},
+    {Action: "pull", Resource: "repository"},
+}
+
-func GenRobotPerms(projects []string) []*models.RobotPermission {
-    robotAccess := []*models.Access{
-        {Action: "read", Resource: "artifact"},
-        {Action: "read", Resource: "repository"},
-        {Action: "pull", Resource: "repository"},
-    }
+func GenRobotPerms(projects []string, access []RobotAccessLevel) []*models.RobotPermission {
+    robotAccess := make([]*models.Access, len(access))
+    for i, a := range access {
+        robotAccess[i] = &models.Access{Action: a.Action, Resource: a.Resource}
+    }

Line range hint 1-144: Consider documenting the ZTR workflow.

This file plays a crucial role in Zero Touch Registration by managing Harbor robot accounts. Consider adding documentation that explains:

  1. The lifecycle of robot accounts in the ZTR process
  2. The relationship between satellites and robot accounts
  3. The security model and access control strategy
ground-control/internal/database/robot_accounts.sql.go (2)

60-78: Use consistent naming for 'RobotAccount', avoid abbreviations

The function GetRobotAccBySatelliteID and the constant getRobotAccBySatelliteID use the abbreviation 'Acc'. For clarity and consistency with other functions like GetRobotAccount, consider using 'Account' instead of 'Acc'.

Apply this diff to rename the function and constant:

-const getRobotAccBySatelliteID = `-- name: GetRobotAccBySatelliteID :one
+const getRobotAccountBySatelliteID = `-- name: GetRobotAccountBySatelliteID :one
 SELECT id, robot_name, robot_secret, robot_id, satellite_id, created_at, updated_at FROM robot_accounts
 WHERE satellite_id = $1
 `

-func (q *Queries) GetRobotAccBySatelliteID(ctx context.Context, satelliteID int32) (RobotAccount, error) {
-	row := q.db.QueryRowContext(ctx, getRobotAccBySatelliteID, satelliteID)
+func (q *Queries) GetRobotAccountBySatelliteID(ctx context.Context, satelliteID int32) (RobotAccount, error) {
+	row := q.db.QueryRowContext(ctx, getRobotAccountBySatelliteID, satelliteID)
 	var i RobotAccount
 	err := row.Scan(
 		&i.ID,
 		&i.RobotName,
 		&i.RobotSecret,
 		&i.RobotID,
 		&i.SatelliteID,
 		&i.CreatedAt,
 		&i.UpdatedAt,
 	)
 	return i, err
 }

135-159: Consider allowing satellite_id to be updated in UpdateRobotAccount

The current updateRobotAccount query does not update the satellite_id. If there's a requirement to modify the satellite_id of an existing robot account, include it in the SET clause and adjust the parameters accordingly.

Apply this diff to include satellite_id in the update:

 UPDATE robot_accounts
 SET robot_name = $2,
     robot_secret = $3,
     robot_id = $4,
+    satellite_id = $5,
     updated_at = NOW()
 WHERE id = $1

Update the UpdateRobotAccountParams struct to include SatelliteID:

 type UpdateRobotAccountParams struct {
     ID          int32
     RobotName   string
     RobotSecret string
     RobotID     string
+    SatelliteID int32
 }

Adjust the function call to pass the new parameter:

 func (q *Queries) UpdateRobotAccount(ctx context.Context, arg UpdateRobotAccountParams) error {
     _, err := q.db.ExecContext(ctx, updateRobotAccount,
         arg.ID,
         arg.RobotName,
         arg.RobotSecret,
         arg.RobotID,
+        arg.SatelliteID,
     )
     return err
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 99b1af5 and a1d7827.

⛔ Files ignored due to path filters (1)
  • ground-control/go.sum is excluded by !**/*.sum
📒 Files selected for processing (44)
  • ground-control/.env (1 hunks)
  • ground-control/go.mod (2 hunks)
  • ground-control/internal/database/db.go (1 hunks)
  • ground-control/internal/database/group_images.sql.go (0 hunks)
  • ground-control/internal/database/groups.sql.go (4 hunks)
  • ground-control/internal/database/images.sql.go (0 hunks)
  • ground-control/internal/database/label_images.sql.go (0 hunks)
  • ground-control/internal/database/labels.sql.go (0 hunks)
  • ground-control/internal/database/models.go (2 hunks)
  • ground-control/internal/database/robot_accounts.sql.go (1 hunks)
  • ground-control/internal/database/satellite_groups.sql.go (2 hunks)
  • ground-control/internal/database/satellite_labels.sql.go (0 hunks)
  • ground-control/internal/database/satellite_token.sql.go (1 hunks)
  • ground-control/internal/database/satellites.sql.go (3 hunks)
  • ground-control/internal/models/models.go (1 hunks)
  • ground-control/internal/server/handlers.go (4 hunks)
  • ground-control/internal/server/routes.go (1 hunks)
  • ground-control/internal/utils/helper.go (1 hunks)
  • ground-control/reg/adapter.go (1 hunks)
  • ground-control/reg/harbor/project.go (1 hunks)
  • ground-control/reg/harbor/replication.go (1 hunks)
  • ground-control/reg/harbor/robot.go (5 hunks)
  • ground-control/sql/queries/group_images.sql (0 hunks)
  • ground-control/sql/queries/groups.sql (2 hunks)
  • ground-control/sql/queries/images.sql (0 hunks)
  • ground-control/sql/queries/label_images.sql (0 hunks)
  • ground-control/sql/queries/labels.sql (0 hunks)
  • ground-control/sql/queries/robot_accounts.sql (1 hunks)
  • ground-control/sql/queries/satellite_groups.sql (1 hunks)
  • ground-control/sql/queries/satellite_labels.sql (0 hunks)
  • ground-control/sql/queries/satellite_token.sql (1 hunks)
  • ground-control/sql/queries/satellites.sql (1 hunks)
  • ground-control/sql/schema/001_satellites.sql (1 hunks)
  • ground-control/sql/schema/002_groups.sql (1 hunks)
  • ground-control/sql/schema/003_labels.sql (0 hunks)
  • ground-control/sql/schema/005_satellite_labels.sql (0 hunks)
  • ground-control/sql/schema/006_images.sql (0 hunks)
  • ground-control/sql/schema/007_group_images.sql (0 hunks)
  • ground-control/sql/schema/008_label_images.sql (0 hunks)
  • ground-control/sql/schema/010_robots.sql (1 hunks)
  • ground-control/sql/schema/011_projects.sql (1 hunks)
  • ground-control/sql/schema/012_robot_projects.sql (1 hunks)
  • ground-control/sql/schema/013_robot_accounts.sql (1 hunks)
  • ground-control/sql/schema/014_satellite_token.sql (1 hunks)
💤 Files with no reviewable changes (15)
  • ground-control/internal/database/group_images.sql.go
  • ground-control/internal/database/images.sql.go
  • ground-control/internal/database/label_images.sql.go
  • ground-control/internal/database/labels.sql.go
  • ground-control/internal/database/satellite_labels.sql.go
  • ground-control/sql/queries/group_images.sql
  • ground-control/sql/queries/images.sql
  • ground-control/sql/queries/label_images.sql
  • ground-control/sql/queries/labels.sql
  • ground-control/sql/queries/satellite_labels.sql
  • ground-control/sql/schema/003_labels.sql
  • ground-control/sql/schema/005_satellite_labels.sql
  • ground-control/sql/schema/006_images.sql
  • ground-control/sql/schema/007_group_images.sql
  • ground-control/sql/schema/008_label_images.sql
✅ Files skipped from review due to trivial changes (1)
  • ground-control/internal/database/db.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • ground-control/internal/database/models.go
  • ground-control/sql/schema/001_satellites.sql
  • ground-control/sql/schema/010_robots.sql
  • ground-control/sql/schema/011_projects.sql
  • ground-control/sql/schema/014_satellite_token.sql
🔇 Additional comments (41)
ground-control/sql/schema/012_robot_projects.sql (1)

9-10: LGTM!

The down migration correctly drops the table.

ground-control/sql/schema/002_groups.sql (1)

Line range hint 1-13: Verify the impact on existing data and handlers.

The addition of non-nullable columns without defaults requires verification:

  1. Ensure existing data migration strategy is in place
  2. Verify handlers are updated to provide these new required fields
✅ Verification successful

Let me check the content of the found files to better understand the situation.


Migration and handlers are properly aligned with the schema changes

The verification shows that:

  1. This is the first migration creating the groups table (002_groups.sql is the only file with CREATE TABLE groups)
  2. All required handlers are properly implemented in the generated sqlc code (groups.sql.go) with correct field mappings
  3. The queries (groups.sql) handle all the new fields appropriately, including an upsert strategy for handling conflicts

No data migration is needed since this is the initial creation of the groups table, and all handlers are correctly generated to work with the new schema.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing groups table and verify handler updates

# Test 1: Check if groups table exists and has data
echo "Checking for existing groups table..."
find . -type f -name "*.sql" -exec grep -l "INSERT INTO groups" {} \;

# Test 2: Verify handlers are updated to handle new fields
echo "Checking handlers for new fields..."
rg -l "registry_url|projects.*groups" --type go

# Test 3: Check for migration scripts
echo "Checking for data migration scripts..."
find . -type f -name "*migrate*" -o -name "*upgrade*" -exec grep -l "groups" {} \;

Length of output: 550


Script:

#!/bin/bash
# Check the content of found files

echo "Content of groups.sql:"
cat ./ground-control/sql/queries/groups.sql

echo -e "\nContent of groups.sql.go:"
cat ./ground-control/internal/database/groups.sql.go

# Check if this is the first migration creating the groups table
echo -e "\nChecking for previous groups table migrations:"
find . -type f -name "*.sql" -exec grep -l "CREATE TABLE groups" {} \;

Length of output: 3768

ground-control/.env (1)

13-14: Review the synchronization interval for production readiness

A 10-second sync interval might be too aggressive for production use, potentially causing unnecessary load on both the Ground Control system and the registry.

Let's check if this interval is used in any critical operations:

Consider:

  1. Making this value environment-specific (higher in production)
  2. Adding documentation about its purpose and impact
  3. Implementing exponential backoff for failed operations
ground-control/sql/schema/013_robot_accounts.sql (1)

3-11: LGTM on the overall table structure.

The table design effectively supports Zero Touch Registration (ZTR) for satellites with:

  • Proper primary key and unique constraints
  • Foreign key relationship with cascading deletes
  • Audit timestamps for tracking records
ground-control/sql/queries/satellite_groups.sql (1)

14-16: LGTM! The delete operation is precise.

The compound WHERE clause ensures accurate removal of the specific satellite-group association.

Let's verify the table structure and constraints:

✅ Verification successful

The delete operation is safe and well-designed

The verification confirms that the RemoveSatelliteFromGroup query is properly designed:

  • The table has a composite primary key (satellite_id, group_id)
  • Both columns are foreign keys with CASCADE delete behavior
  • The WHERE clause in the delete operation matches the primary key structure
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify satellite_groups table structure and constraints

# Test: Check if the table exists and its structure
rg -A 10 "CREATE TABLE.*satellite_groups" ground-control/sql/schema/

# Test: Check for existing foreign key constraints
rg "FOREIGN KEY.*satellite_groups" ground-control/sql/schema/

Length of output: 781

ground-control/sql/queries/satellite_token.sql (1)

15-16: Previous review comment about pagination and column specification still applies.

ground-control/sql/queries/groups.sql (2)

22-24: Consider data consistency implications of group deletion.

The DELETE operation might leave orphaned satellite records. Consider implementing one of these solutions:

  1. Add ON DELETE CASCADE to the foreign key constraints in related tables
  2. Implement a transaction that handles cleanup of related records

Let's check for related tables and constraints:

✅ Verification successful

The previous output showed a partial result indicating there's at least one table satellite_groups with ON DELETE CASCADE, but let's get the complete picture of the database schema to make a proper assessment.


ON DELETE CASCADE is properly implemented - no data consistency issues

The schema shows that the satellite_groups junction table, which is the only table directly referencing the groups table, has the appropriate ON DELETE CASCADE constraint:

CREATE TABLE satellite_groups (
  satellite_id INT REFERENCES satellites(id) ON DELETE CASCADE,
  group_id INT REFERENCES groups(id) ON DELETE CASCADE,
  PRIMARY KEY (satellite_id, group_id)
);

This ensures that when a group is deleted, all related records in the satellite_groups table will be automatically removed, maintaining referential integrity.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for related tables that might be affected by group deletion
# Expected: Find foreign key constraints referencing groups table

# Search for table definitions referencing groups
fd -e sql -x rg -A 10 'REFERENCES\s+groups'

# Search for existing transaction handling
rg -A 5 'BEGIN|START TRANSACTION' 

Length of output: 319


Script:

#!/bin/bash
# Let's search more thoroughly for table definitions and foreign keys
# Look for any SQL files that might contain table definitions
fd -e sql -x cat {} \; | grep -A 10 -B 2 'CREATE TABLE'

# Also specifically look for satellite_groups table definition
fd -e sql -x rg -l 'satellite_groups'

Length of output: 2666


2-8: LGTM! Verify registry_url format validation.

The UPSERT logic for groups with registry URL and projects looks good. However, ensure that registry URL validation is implemented in the application layer.

Let's verify the validation implementation:

✅ Verification successful

Registry URL validation is implemented correctly

The codebase has proper URL validation through the IsValidURL function in internal/utils/utils.go which validates URLs using Go's net/url package. Additionally, the ParseArtifactURL function in ground-control/internal/utils/helper.go provides thorough URL parsing and validation for registry URLs.

Key validations in place:

  • URL scheme verification
  • Registry host presence check
  • Repository path validation
  • Tag/digest validation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for registry URL validation in the codebase
# Expected: Find validation logic for registry URL format

# Search for registry URL validation patterns
rg -A 5 'registry.*url.*valid|valid.*registry.*url'

# Search for URL validation utilities/helpers
ast-grep --pattern 'func $_(url string) bool {
  $$$
}'

Length of output: 319


Script:

#!/bin/bash
# Let's try a different approach to find URL validation logic

# Search for URL-related validation patterns in Go files
rg -t go "validate.*url|isValid.*url|checkURL|validateURL" -A 5

# Search for URL parsing or validation imports
rg -t go "net/url|validator"

# Look for struct field tags related to URL validation
rg -t go 'RegistryURL.*`.*validate:".*"`'

# Check for URL-related constants or patterns
rg "(?i)(registry.*url.*pattern|url.*pattern|url.*regex)"

# Look for group-related validation logic
fd -e go -x rg -l "group.*validate|validate.*group"

Length of output: 340


Script:

#!/bin/bash
# Let's check the content of files that import net/url to see how it's used

# Check utils.go content
echo "=== Content of internal/utils/utils.go ==="
cat internal/utils/utils.go

# Check helper.go content
echo "=== Content of ground-control/internal/utils/helper.go ==="
cat ground-control/internal/utils/helper.go

# Look for any group-related structs and their validation
rg -t go "type.*Group.*struct" -A 10

Length of output: 15597

ground-control/sql/queries/satellites.sql (3)

9-11: LGTM! Well-structured query for satellite retrieval.

The query follows best practices with:

  • Proper parameter usage for safe querying
  • LIMIT 1 for efficient single record retrieval

2-3: Consider adding name validation and uniqueness constraints.

While removing the token field aligns with the new ZTR approach, consider:

  1. Adding input validation for the satellite name
  2. Ensuring uniqueness constraints are in place to prevent duplicate satellite names

Let's verify if these constraints exist in the schema:


17-19: Verify token functionality migration before removing commented code.

The commented-out token query suggests a transition in the authentication mechanism. Let's verify that the token functionality has been properly migrated to the new system.

Let's check the new token implementation:

✅ Verification successful

Safe to remove the commented code - token functionality has been properly migrated

The token functionality has been successfully migrated to a dedicated satellite_token table with proper queries implemented in satellite_token.sql. The new implementation includes:

  • A dedicated table with proper foreign key constraints
  • Complete set of CRUD operations for tokens
  • GetSatelliteIDByToken query that replaces the old GetSatelliteByToken functionality

The commented code in satellites.sql can be safely removed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for the new satellite_token table and related queries
echo "Checking for satellite_token table:"
rg -A 10 "CREATE TABLE satellite_token" ground-control/sql/schema/

echo "Checking for token-related queries:"
rg -l "satellite_token" ground-control/sql/queries/

Length of output: 1135


Script:

#!/bin/bash
# Let's check the content of satellite_token.sql to verify the token-related queries
echo "Content of satellite_token.sql:"
cat ground-control/sql/queries/satellite_token.sql

# Also check if there are any remaining token-related queries in satellites.sql
echo -e "\nRemaining token references in satellites.sql:"
cat ground-control/sql/queries/satellites.sql

Length of output: 1306

ground-control/internal/models/models.go (1)

1-27: Verify integration with satellite registration workflow.

The models seem to be missing explicit satellite identification or registration status fields. Let's verify how these models integrate with the satellite registration process.

ground-control/reg/harbor/project.go (2)

1-10: LGTM: Clean package structure and imports

The imports are well-organized and use specific versioning for the Harbor client.


12-30: Verify integration with Zero Touch Registration flow

The function is part of the ZTR implementation but its relationship to the satellite registration process isn't immediately clear. We should verify how this integrates with the overall ZTR flow.

Let's check for references to this function in the codebase:

✅ Verification successful

Integration with Zero Touch Registration is verified and properly implemented

The ListProjects function is correctly integrated into the ZTR flow through the following chain:

  • The registerSatelliteHandler initiates satellite registration
  • It calls CreateRobotAccForSatellite which creates Harbor robot accounts for satellites
  • The robot account creation process uses RobotAccountTemplate to set up proper permissions
  • While ListProjects is available, it's intentionally not used in the registration flow as projects are derived from the provided repositories
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for references to ListProjects in the codebase
rg -l "ListProjects.*harbor" --type go

# Look for ZTR-related code that might use this function
rg -l "zero.*touch|ZTR" --type go

Length of output: 76


Script:

#!/bin/bash
# Let's try a broader search for ListProjects function usage
rg "ListProjects" --type go -A 3

# Search for harbor project related imports and usage
rg "harbor.*project" --type go

# Search for registration or robot related code that might use this
rg "RobotCreate|registration" --type go

Length of output: 1706


Script:

#!/bin/bash
# Let's examine the robot account creation flow and its relationship with ListProjects
rg -A 10 "CreateRobotAccForSatellite" --type go

# Look at the robot account template implementation
rg -A 10 "RobotAccountTemplate" --type go

# Check for satellite registration related code
rg -A 5 "satellite.*registration|register.*satellite" --type go -i

Length of output: 4917

ground-control/reg/harbor/replication.go (1)

14-28: Verify the role of replication policies in ZTR architecture.

This function appears to be part of the Zero Touch Registration implementation, potentially managing satellite image distribution.

Let's verify the usage of replication policies in the codebase:

ground-control/sql/queries/robot_accounts.sql (1)

1-32: Consider implementing comprehensive data security and audit measures.

For a Zero Touch Registration system handling robot accounts, consider implementing these security measures across all queries:

  1. Add appropriate indexes for robot_id and satellite_id
  2. Implement row-level security policies
  3. Add audit logging for sensitive operations
  4. Consider encryption for robot_secret

Let's verify the current security measures:

ground-control/internal/server/routes.go (2)

30-31: Verify the dual registration flow implementation.

The code implements two registration paths: regular registration and Zero Touch Registration (ZTR). Let's verify that both flows are properly implemented.

#!/bin/bash
# Description: Verify the implementation of both registration flows

# Test 1: Check the registration handler implementations
echo "Checking registration handler implementations:"
ast-grep --pattern 'func $_(s *Server) registerSatelliteHandler($$$) {
  $$$
}'

echo "\nChecking ZTR handler implementations:"
ast-grep --pattern 'func $_(s *Server) ztrHandler($$$) {
  $$$
}'

# Test 2: Look for registration-related database operations
echo "\nChecking database operations:"
rg -A 5 "INSERT INTO.*satellites"

The previous review comment about missing ZTR documentation is still valid. Please add documentation explaining:

  • The purpose and process of ZTR
  • How it differs from regular registration
  • Any specific requirements or prerequisites

32-34: LGTM! The satellite management endpoints follow REST conventions.

The endpoints for listing, getting, and deleting satellites are well-structured and follow REST best practices:

  • Use of plural resource names
  • Proper HTTP methods
  • Consistent URL patterns
ground-control/reg/adapter.go (1)

19-24: Verify integration with satellite registration flow.

The Images struct appears to be part of the Zero Touch Registration (ZTR) process for satellites. Let's verify how it's being used in the registration flow.

✅ Verification successful

The Images struct is properly integrated with the satellite workflow

The verification shows that the Images struct is well-integrated into the system through the ParseArtifactURL utility function, which properly validates:

  • Required fields (repository and registry)
  • Presence of either tag or digest
  • URL format and structure

The struct is used as part of the artifact URL parsing flow, which is a critical component for satellite image management. The implementation includes appropriate error handling and validation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check how Images struct is used in the registration process

# Look for registration handlers or services using this struct
echo "Searching for registration-related code using Images struct..."
rg -l "reg\.Images" | grep -i "registration\|register"

# Look for tests to ensure proper validation
echo "Searching for related tests..."
rg -l "reg\.Images" | grep "_test.go"

Length of output: 300


Script:

#!/bin/bash
# Let's try a broader search to understand the usage of Images struct

# Search for direct struct usage without the package prefix
echo "Searching for Images struct usage..."
rg -A 5 "type Images struct"

# Search for any files importing the reg package
echo "Searching for imports of reg package..."
rg -l "\".*ground-control/reg\""

# Search for files containing "Images" in ground-control directory
echo "Searching for files with Images references..."
rg -l "Images" ./ground-control/

# Look for any registration or satellite related files
echo "Searching for registration/satellite related files..."
fd -t f "registration|satellite|reg" ./ground-control/

Length of output: 1627


Script:

#!/bin/bash
# Let's examine the actual usage of Images struct in the found files

# Check helper.go for Images usage
echo "Checking helper.go implementation..."
rg -A 10 "Images" ./ground-control/internal/utils/helper.go

# Check routes.go for Images usage
echo "Checking routes.go implementation..."
rg -A 10 "Images" ./ground-control/internal/server/routes.go

# Check for any satellite registration handlers
echo "Checking for satellite registration handlers..."
rg -A 10 "func.*Register.*Satellite" ./ground-control/internal/server/

Length of output: 1798

ground-control/go.mod (4)

33-33: Verify logging configuration.

The addition of klauspost/compress and sirupsen/logrus suggests compression and logging capabilities. Ensure proper log levels and compression settings are configured for production use.

#!/bin/bash
# Check for logging configuration
rg -A 5 "logrus.*New|logrus.*Level"

Also applies to: 43-43


7-7: Review the container registry integration requirements.

The addition of github.com/google/go-containerregistry suggests container image operations will be part of the Zero Touch Registration flow. This is a solid choice for container operations, but ensure proper error handling is implemented when using this library.

#!/bin/bash
# Check for error handling patterns in container registry operations
rg -A 3 "go-containerregistry" | rg -B 2 "error|err"

Line range hint 3-3: Verify Go version compatibility with deployment environment.

The module specifies Go 1.22.4, which is a very recent version. Ensure your deployment environment supports this version.

✅ Verification successful

Let me gather more information about Go version requirements by searching through deployment and configuration files.


Go version 1.22.4 is compatible with CI/CD requirements

The Go version 1.22.4 specified in go.mod is compatible with the deployment environment requirements. CI/CD workflows in .github/workflows/ specify Go version >=1.22, which is satisfied by version 1.22.4.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if README or deployment docs specify Go version requirements
rg -i "go.*version|golang.*version" README.md

Length of output: 2622


Script:

#!/bin/bash
# Search for Go version requirements in deployment files and documentation
fd -e yaml -e yml -e md | xargs rg -i "go.*version|golang.*version"

# Check if there's a Dockerfile that specifies Go version
fd Dockerfile | xargs cat

# Check if there are any CI configuration files that specify Go version
fd -e yml -e yaml . .github/ | xargs rg -i "go-version|golang-version"

Length of output: 515


16-17: Review compatibility of Docker and OCI dependencies.

Several Docker and OCI-related packages are marked as incompatible or using release candidates:

  • github.com/docker/cli v27.1.1+incompatible
  • github.com/opencontainers/image-spec v1.1.0-rc3

Consider:

  1. Upgrading to compatible versions if available
  2. Verifying these versions work well together
  3. Testing container operations thoroughly

Also applies to: 38-39

✅ Verification successful

This will help us understand:

  1. All related Docker/OCI dependencies and their versions
  2. Where these packages are actually being used
  3. The scope of container-related functionality in the codebase

This will help us understand:

  1. How these dependencies are actually being used in critical paths
  2. If there are any existing issues with these dependencies
  3. The architectural decisions that led to these dependency choices

Based on the gathered information, I can now provide a final response:

Docker and OCI dependencies are appropriately configured for the project's needs

The current dependency setup is intentional and working as designed:

  • The project uses Crane (Google's container registry library) as the primary tool for image operations, as documented in ADR 0001
  • The Docker/OCI dependencies are indirect requirements from these core libraries
  • No errors or issues related to these dependencies are found in the codebase
  • The image operations (replication, listing, deletion) are working as expected

The +incompatible markers are normal for Go modules when using packages that haven't fully adopted semantic import versioning, which is common in the Docker ecosystem. The current versions are stable releases, not release candidates as initially suspected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for any reported issues with these dependencies
rg -l "docker/cli|image-spec" | xargs rg -B 2 "TODO|FIXME|BUG|HACK"

Length of output: 131


Script:

#!/bin/bash
# Check go.mod for all Docker and OCI-related dependencies
rg "github.com/(docker|opencontainers)" go.mod

# Check for actual usage of these packages in the codebase
rg -l "github.com/docker/cli|github.com/docker/distribution|github.com/opencontainers/image-spec"

# Look for container-related functionality that might be affected
rg -l "container|docker|oci|image"

Length of output: 2014


Script:

#!/bin/bash
# Check actual container/image operations in key files
rg -A 5 "(NewClient|Pull|Push|Image|Container)" internal/images/get-images.go internal/satellite/satellite.go ground-control/reg/adapter_test.go

# Check if there are any error logs or issues related to Docker/OCI operations
rg -B 2 "err.*docker|err.*container|err.*image" internal/images/get-images.go internal/satellite/satellite.go ground-control/reg/adapter_test.go

# Look at architecture decisions for context
cat docs/decisions/0002-zot-vs-docker-registries.md docs/decisions/0001-skopeo-vs-crane.md

Length of output: 7407

ground-control/internal/database/satellite_token.sql.go (3)

71-102: ⚠️ Potential issue

Add pagination to prevent memory issues.

The current implementation loads all tokens into memory, which could cause issues with large datasets.

Consider modifying the query to include pagination:

 const listToken = `-- name: ListToken :many
 SELECT id, satellite_id, token, created_at, updated_at FROM satellite_token
+LIMIT $1 OFFSET $2
`

-func (q *Queries) ListToken(ctx context.Context) ([]SatelliteToken, error) {
+func (q *Queries) ListToken(ctx context.Context, limit int32, offset int32) ([]SatelliteToken, error) {
-	rows, err := q.db.QueryContext(ctx, listToken)
+	rows, err := q.db.QueryContext(ctx, listToken, limit, offset)

Also, there's a redundant rows.Close() call at line 95 since it's already handled by the defer statement.

Let's check the potential impact:

#!/bin/bash
# Check how many tokens might be affected
rg -A 2 "ListToken.*\(" --type go

1-102: Verify secure token handling across the system.

As this code handles authentication tokens for satellite registration, it's crucial to ensure secure token handling throughout the system.

Let's check for potential security concerns:

#!/bin/bash
# Check for token exposure in logs
rg -B 2 -A 2 "log.*token|print.*token" --type go

# Check for token transmission in clear text
rg -B 2 -A 2 "http.*token|grpc.*token" --type go

# Look for token generation implementation
ast-grep --pattern 'func $$$Token$$$'

Consider implementing:

  1. Token encryption at rest
  2. Audit logging for token operations
  3. Token rotation mechanism
  4. Rate limiting for token operations

12-28: Consider adding token validation and uniqueness constraints.

The token insertion lacks validation for token strength and uniqueness. This could lead to security vulnerabilities or conflicts in satellite identification.

Let's verify if there are any uniqueness constraints in the schema:

Consider implementing:

  1. Token strength validation before insertion
  2. A unique constraint on the token column if not already present
  3. Token expiration mechanism for enhanced security
ground-control/internal/database/satellite_groups.sql.go (2)

56-69: LGTM!

The implementation is clean, secure, and follows best practices with proper parameter handling and error checking.


3-3: Verify sqlc version update impact.

The sqlc version has been updated from 1.26.0 to 1.27.0. Please ensure this update is intentional and documented in the dependency management files.

✅ Verification successful

sqlc version update is consistent across the codebase

The sqlc version has been updated to v1.27.0 consistently across all generated files in the database package. This indicates a proper and complete regeneration of the database code.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if sqlc version is consistently updated across the codebase
rg -l "sqlc v1\.2[67]\.0" 

Length of output: 375


Script:

#!/bin/bash
# Check the actual sqlc version in each file to ensure consistency
for file in $(rg -l 'sqlc v1\.2[67]\.0'); do
  echo "=== $file ==="
  rg "sqlc v1\.2[67]\.0" "$file"
done

Length of output: 1849

ground-control/internal/database/satellites.sql.go (3)

40-46: LGTM! Good improvement in database access pattern.

The change from name-based to ID-based lookup is a good practice as it:

  1. Uses the primary key for efficient lookups
  2. Avoids potential issues with name changes
  3. Follows proper database normalization principles

91-91: LGTM! Good practice using explicit column selection.

The change from SELECT * to explicit column selection is a good practice as it:

  1. Makes the code more maintainable
  2. Prevents issues with schema changes
  3. Improves query performance

13-19: Verify token handling mechanism for satellite authentication.

The removal of the token parameter from CreateSatellite suggests a change in the authentication mechanism. While this aligns with the Zero Touch Registration objectives, we need to ensure secure token handling is implemented elsewhere.

✅ Verification successful

Token handling has been properly refactored into a dedicated system

The removal of the token parameter from CreateSatellite is justified as token management has been properly moved to a dedicated satellite_token table with appropriate schema and queries:

  • A new table satellite_token maintains the relationship between satellites and their tokens
  • Foreign key constraint ensures referential integrity with the satellites table
  • Comprehensive token management queries are implemented:
    • AddToken for token creation
    • GetSatelliteIDByToken for token validation
    • GetToken and ListToken for token retrieval
    • DeleteToken for token revocation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Look for satellite token handling implementation
# Expected: Find evidence of token management in the new satellite_token table

# Check for satellite token related schema
fd -e sql -e go . | xargs rg -l "satellite.*token"

# Look for token-related functions
ast-grep --pattern 'func $_(ctx context.Context, token string)'

Length of output: 355


Script:

#!/bin/bash
# Let's examine the token-related schema and implementation

# Check the token table schema
cat ground-control/sql/schema/014_satellite_token.sql

# Check the token-related queries
cat ground-control/sql/queries/satellite_token.sql

# Look for token validation in handlers
rg -A 5 "func.*ValidateSatelliteToken|func.*VerifyToken" ground-control/internal/server/handlers.go

Length of output: 970

ground-control/reg/harbor/robot.go (2)

14-16: LGTM! Clean struct definition for list parameters.

The renamed ListParams struct provides a well-structured set of fields for pagination and filtering, making it more generic and reusable.


115-124: Review security implications of system-level access.

Several security considerations to address:

  1. The level: "system" grants broad access. Consider if project-level access would be sufficient.
  2. The infinite duration (-1) prevents automatic credential rotation. Consider setting a reasonable expiration.
  3. The description could include more metadata (e.g., creation timestamp, purpose).

Let's check if system-level access is truly needed:

✅ Verification successful

Based on the gathered information, I can now provide a final response:

System-level access is appropriate for this robot account's purpose

The implementation shows that the robot account is granted minimal permissions despite being at system level:

  • Only read/pull access to artifacts and repositories
  • No write/push capabilities
  • Permissions are scoped to specific projects passed as parameters
  • The infinite duration (-1) is justified as this is a managed service account

The description "managed by ground-control should not edit" is also appropriate as it clearly indicates the account's purpose and management authority.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any uses of the robot account that require system-level access
rg -A 5 "level.*system" --type go

Length of output: 35


Script:

#!/bin/bash
# Let's try a broader search to understand the robot account usage and permissions
# First, let's find where GenRobotPerms is defined and used
ast-grep --pattern 'func GenRobotPerms($$$) {
  $$$
}'

# Also search for any references to robot permissions or access levels
rg -B 2 -A 5 'Level.*:.*' --type go

# And look for any documentation or comments about robot account permissions
rg -l 'robot.*permission|permission.*robot' --type go

Length of output: 618


Script:

#!/bin/bash
# Let's check the handlers.go file to understand how robot permissions are used
cat ground-control/internal/server/handlers.go

# Let's also search for GenRobotPerms with ripgrep to see its implementation
rg -B 5 -A 10 "GenRobotPerms" --type go

# And check for any configuration or documentation about robot accounts
rg -l "robot.*config|config.*robot" --type go
fd "README|CONTRIBUTING" --type f --exec cat {}

Length of output: 26979


Script:

#!/bin/bash
# Let's check the GenRobotPerms implementation to understand the actual permissions being granted
rg -B 2 -A 10 "func GenRobotPerms" ground-control/reg/harbor/robot.go

# Also check for any robot-related tests that might provide insights
rg -l "_test.*robot|robot.*_test" --type go

# And look for any configuration files that might define robot permissions
fd "config|settings" --type yaml --type json --exec cat {}

Length of output: 786

ground-control/internal/utils/helper.go (2)

37-74: LGTM! Well-structured URL parsing with proper validation

The function has good error handling and validation checks for all required components.


124-149: LGTM! Well-structured robot account update logic

The function has good error handling and proper validation of inputs.

ground-control/internal/database/groups.sql.go (5)

3-3: Generated code updated with sqlc v1.27.0

The file has been regenerated using sqlc v1.27.0, addressing previous concerns about outdated generated code.


15-22: CreateGroup SQL query updated to handle new fields

The INSERT INTO groups statement now includes registry_url and projects, along with an ON CONFLICT clause to update existing entries. This ensures that group records are up-to-date without inserting duplicates.


26-28: CreateGroupParams and CreateGroup function correctly handle new fields

The CreateGroupParams struct and CreateGroup function have been updated to include RegistryUrl and Projects, matching the changes in the SQL query. The use of pq.Array for the Projects slice is appropriate for handling PostgreSQL array types.

Also applies to: 32-38


45-53: DeleteGroup function added for group deletion

The new DeleteGroup function allows for the deletion of groups by their ID, enhancing the CRUD capabilities for group management.


Line range hint 56-67: Query functions updated to include new fields

The GetGroupByID, GetGroupByName, and ListGroups functions now select and scan the registry_url and projects fields, ensuring that the additional group information is retrieved as expected.

Also applies to: 75-86, 94-110

ground-control/internal/database/robot_accounts.sql.go (1)

30-48: Proper implementation of AddRobotAccount function

The AddRobotAccount function correctly prepares the statement, executes the query with parameters, and scans the result into the RobotAccount struct. Error handling is appropriately implemented.

@@ -3,6 +3,8 @@
CREATE TABLE groups (
id SERIAL PRIMARY KEY,
group_name VARCHAR(255) UNIQUE NOT NULL,
registry_url VARCHAR(255) NOT NULL,
projects TEXT[] NOT NULL,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding array constraints and a GIN index.

For the projects array column:

  1. Consider adding constraints to ensure non-empty elements
  2. Add a GIN index if you plan to perform array operations like containment searches
  id SERIAL PRIMARY KEY,
  group_name VARCHAR(255) UNIQUE NOT NULL,
  registry_url VARCHAR(255) NOT NULL,
- projects TEXT[] NOT NULL,
+ projects TEXT[] NOT NULL CHECK (array_length(projects, 1) > 0 AND NOT EXISTS (SELECT 1 FROM unnest(projects) AS p WHERE p = '')),
  created_at TIMESTAMP DEFAULT NOW() NOT NULL,
  updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);

+ CREATE INDEX idx_groups_projects ON groups USING GIN (projects);
📝 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
projects TEXT[] NOT NULL,
projects TEXT[] NOT NULL CHECK (array_length(projects, 1) > 0 AND NOT EXISTS (SELECT 1 FROM unnest(projects) AS p WHERE p = '')),
CREATE INDEX idx_groups_projects ON groups USING GIN (projects);

@@ -3,6 +3,8 @@
CREATE TABLE groups (
id SERIAL PRIMARY KEY,
group_name VARCHAR(255) UNIQUE NOT NULL,
registry_url VARCHAR(255) NOT NULL,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding URL format validation and an index.

The registry_url column might benefit from:

  1. A CHECK constraint to validate URL format
  2. An index for faster lookups if queried frequently
  id SERIAL PRIMARY KEY,
  group_name VARCHAR(255) UNIQUE NOT NULL,
- registry_url VARCHAR(255) NOT NULL,
+ registry_url VARCHAR(255) NOT NULL CHECK (registry_url ~ '^https?://'),
  projects TEXT[] NOT NULL,
  created_at TIMESTAMP DEFAULT NOW() NOT NULL,
  updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);

+ CREATE INDEX idx_groups_registry_url ON groups(registry_url);
📝 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
registry_url VARCHAR(255) NOT NULL,
id SERIAL PRIMARY KEY,
group_name VARCHAR(255) UNIQUE NOT NULL,
registry_url VARCHAR(255) NOT NULL CHECK (registry_url ~ '^https?://'),
projects TEXT[] NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);
CREATE INDEX idx_groups_registry_url ON groups(registry_url);

Comment on lines +9 to 11
# Ground Control PORT
PORT=8080
APP_ENV=local
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Environment configuration inconsistency

The file mixes production URLs (registry.bupd.xyz) with local environment settings (APP_ENV=local), which could lead to accidental production data manipulation during development.

Consider:

  1. Creating separate .env files for different environments
  2. Using environment-specific URLs:
-APP_ENV=local
+APP_ENV=production  # or keep local and change HARBOR_URL to local

Committable suggestion was skipped due to low confidence.

Comment on lines 1 to 4
# Source Registry
HARBOR_USERNAME=admin
HARBOR_PASSWORD=Harbor12345
HARBOR_URL=https://demo.goharbor.io
HARBOR_URL=https://registry.bupd.xyz
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security: Avoid committing credentials to version control

The file contains hard-coded admin credentials for Harbor registry. This poses a significant security risk, especially since it's pointing to what appears to be a production URL.

Consider:

  1. Using environment-specific credential management
  2. Adding this file to .gitignore
  3. Providing a template file (e.g., .env.example) with placeholder values instead
-HARBOR_USERNAME=admin
-HARBOR_PASSWORD=Harbor12345
+HARBOR_USERNAME=<your-username>
+HARBOR_PASSWORD=<your-password>

Committable suggestion was skipped due to low confidence.

robot_name VARCHAR(255) UNIQUE NOT NULL,
robot_secret VARCHAR(255) NOT NULL,
robot_id VARCHAR(255) UNIQUE NOT NULL,
satellite_id INT NOT NULL REFERENCES satellites(id) ON DELETE CASCADE,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add an index for foreign key performance.

Consider adding an index on satellite_id to improve query performance when looking up robot accounts by satellite.

Add this after the table creation:

CREATE INDEX idx_robot_accounts_satellite_id ON robot_accounts(satellite_id);

Comment on lines +453 to +464
func GenerateRandomToken(charLength int) (string, error) {
// The number of bytes needed to generate a token with the required number of hex characters
byteLength := charLength / 2

// Create a byte slice of the required length
token := make([]byte, byteLength)
_, err := rand.Read(token)
if err != nil {
return "", err
}

// Return the token as a hex-encoded string
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve token generation security.

The GenerateRandomToken function should handle the case where charLength is odd, and it should validate the input to prevent potential issues.

Apply this diff to improve the implementation:

 func GenerateRandomToken(charLength int) (string, error) {
+    if charLength <= 0 {
+        return "", fmt.Errorf("token length must be positive")
+    }
+
     // The number of bytes needed to generate a token with the required number of hex characters
-    byteLength := charLength / 2
+    byteLength := (charLength + 1) / 2  // Handle odd lengths correctly
 
     // Create a byte slice of the required length
     token := make([]byte, byteLength)
     _, err := rand.Read(token)
     if err != nil {
         return "", err
     }
 
     // Return the token as a hex-encoded string
-    return hex.EncodeToString(token), nil
+    hexToken := hex.EncodeToString(token)
+    // Ensure exact length for odd charLength
+    if len(hexToken) > charLength {
+        hexToken = hexToken[:charLength]
+    }
+    return hexToken, nil
📝 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 GenerateRandomToken(charLength int) (string, error) {
// The number of bytes needed to generate a token with the required number of hex characters
byteLength := charLength / 2
// Create a byte slice of the required length
token := make([]byte, byteLength)
_, err := rand.Read(token)
if err != nil {
return "", err
}
// Return the token as a hex-encoded string
func GenerateRandomToken(charLength int) (string, error) {
if charLength <= 0 {
return "", fmt.Errorf("token length must be positive")
}
// The number of bytes needed to generate a token with the required number of hex characters
byteLength := (charLength + 1) / 2 // Handle odd lengths correctly
// Create a byte slice of the required length
token := make([]byte, byteLength)
_, err := rand.Read(token)
if err != nil {
return "", err
}
// Return the token as a hex-encoded string
hexToken := hex.EncodeToString(token)
// Ensure exact length for odd charLength
if len(hexToken) > charLength {
hexToken = hexToken[:charLength]
}
return hexToken, nil

Comment on lines 365 to 373
func (s *Server) getSatelliteByID(w http.ResponseWriter, r *http.Request) {
result, err := s.dbQueries.ListSatellites(r.Context())
if err != nil {
HandleAppError(w, err)
return
}

params := database.AssignImageToLabelParams{
LabelID: int32(req.LabelID),
ImageID: int32(req.ImageID),
}
WriteJSONResponse(w, http.StatusOK, result)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect implementation of getSatelliteByID.

The getSatelliteByID handler is incorrectly using ListSatellites instead of retrieving a specific satellite by ID.

Apply this diff to fix the implementation:

 func (s *Server) getSatelliteByID(w http.ResponseWriter, r *http.Request) {
-    result, err := s.dbQueries.ListSatellites(r.Context())
+    vars := mux.Vars(r)
+    idStr := vars["id"]
+    
+    id, err := strconv.ParseInt(idStr, 10, 32)
+    if err != nil {
+        HandleAppError(w, &AppError{
+            Message: "Invalid satellite ID",
+            Code:    http.StatusBadRequest,
+        })
+        return
+    }
+
+    result, err := s.dbQueries.GetSatelliteByID(r.Context(), int32(id))
     if err != nil {
+        if err == sql.ErrNoRows {
+            HandleAppError(w, &AppError{
+                Message: "Satellite not found",
+                Code:    http.StatusNotFound,
+            })
+            return
+        }
         HandleAppError(w, err)
         return
     }
📝 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 (s *Server) getSatelliteByID(w http.ResponseWriter, r *http.Request) {
result, err := s.dbQueries.ListSatellites(r.Context())
if err != nil {
HandleAppError(w, err)
return
}
params := database.AssignImageToLabelParams{
LabelID: int32(req.LabelID),
ImageID: int32(req.ImageID),
}
WriteJSONResponse(w, http.StatusOK, result)
}
func (s *Server) getSatelliteByID(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
idStr := vars["id"]
id, err := strconv.ParseInt(idStr, 10, 32)
if err != nil {
HandleAppError(w, &AppError{
Message: "Invalid satellite ID",
Code: http.StatusBadRequest,
})
return
}
result, err := s.dbQueries.GetSatelliteByID(r.Context(), int32(id))
if err != nil {
if err == sql.ErrNoRows {
HandleAppError(w, &AppError{
Message: "Satellite not found",
Code: http.StatusNotFound,
})
return
}
HandleAppError(w, err)
return
}
WriteJSONResponse(w, http.StatusOK, result)
}

Comment on lines 56 to 102
func (s *Server) groupsSyncHandler(w http.ResponseWriter, r *http.Request) {
var req models.StateArtifact
if err := DecodeRequestBody(r, &req); err != nil {
log.Println(err)
HandleAppError(w, err)
return
}

projects := utils.GetProjectNames(&req.Artifacts)
params := database.CreateGroupParams{
GroupName: req.GroupName,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
GroupName: req.Group,
RegistryUrl: os.Getenv("HARBOR_URL"),
Projects: projects,
}

// Call the database query to create Group
result, err := s.dbQueries.CreateGroup(r.Context(), params)
if err != nil {
err = &AppError{
Message: err.Error(),
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
return
}

WriteJSONResponse(w, http.StatusCreated, result)
}

func (s *Server) createLabelHandler(w http.ResponseWriter, r *http.Request) {
var req LabelRequestParams
if err := DecodeRequestBody(r, &req); err != nil {
satellites, err := s.dbQueries.GroupSatelliteList(r.Context(), result.ID)
if err != nil {
HandleAppError(w, err)
return
}
params := database.CreateLabelParams{
LabelName: req.LabelName,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),

for _, satellite := range satellites {
robotAcc, err := s.dbQueries.GetRobotAccBySatelliteID(r.Context(), satellite.SatelliteID)
if err != nil {
HandleAppError(w, err)
return
}
// update robot account projects permission
_, err = utils.UpdateRobotProjects(r.Context(), projects, robotAcc.RobotName, robotAcc.RobotID)
if err != nil {
HandleAppError(w, err)
return
}
}

result, err := s.dbQueries.CreateLabel(r.Context(), params)
// Create State Artifact for the group
err = utils.CreateStateArtifact(&req)
if err != nil {
HandleAppError(w, err)
return
}

WriteJSONResponse(w, http.StatusCreated, result)
WriteJSONResponse(w, http.StatusOK, result)
Copy link

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 empty projects list.

The groupsSyncHandler should validate that the projects list is not empty before creating a group. Additionally, error messages should be more descriptive.

Add validation after line 64:

 projects := utils.GetProjectNames(&req.Artifacts)
+if len(projects) == 0 {
+    err := &AppError{
+        Message: "No valid projects found in artifacts",
+        Code:    http.StatusBadRequest,
+    }
+    HandleAppError(w, err)
+    return
+}
📝 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 (s *Server) groupsSyncHandler(w http.ResponseWriter, r *http.Request) {
var req models.StateArtifact
if err := DecodeRequestBody(r, &req); err != nil {
log.Println(err)
HandleAppError(w, err)
return
}
projects := utils.GetProjectNames(&req.Artifacts)
params := database.CreateGroupParams{
GroupName: req.GroupName,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
GroupName: req.Group,
RegistryUrl: os.Getenv("HARBOR_URL"),
Projects: projects,
}
// Call the database query to create Group
result, err := s.dbQueries.CreateGroup(r.Context(), params)
if err != nil {
err = &AppError{
Message: err.Error(),
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
return
}
WriteJSONResponse(w, http.StatusCreated, result)
}
func (s *Server) createLabelHandler(w http.ResponseWriter, r *http.Request) {
var req LabelRequestParams
if err := DecodeRequestBody(r, &req); err != nil {
satellites, err := s.dbQueries.GroupSatelliteList(r.Context(), result.ID)
if err != nil {
HandleAppError(w, err)
return
}
params := database.CreateLabelParams{
LabelName: req.LabelName,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
for _, satellite := range satellites {
robotAcc, err := s.dbQueries.GetRobotAccBySatelliteID(r.Context(), satellite.SatelliteID)
if err != nil {
HandleAppError(w, err)
return
}
// update robot account projects permission
_, err = utils.UpdateRobotProjects(r.Context(), projects, robotAcc.RobotName, robotAcc.RobotID)
if err != nil {
HandleAppError(w, err)
return
}
}
result, err := s.dbQueries.CreateLabel(r.Context(), params)
// Create State Artifact for the group
err = utils.CreateStateArtifact(&req)
if err != nil {
HandleAppError(w, err)
return
}
WriteJSONResponse(w, http.StatusCreated, result)
WriteJSONResponse(w, http.StatusOK, result)
func (s *Server) groupsSyncHandler(w http.ResponseWriter, r *http.Request) {
var req models.StateArtifact
if err := DecodeRequestBody(r, &req); err != nil {
log.Println(err)
HandleAppError(w, err)
return
}
projects := utils.GetProjectNames(&req.Artifacts)
if len(projects) == 0 {
err := &AppError{
Message: "No valid projects found in artifacts",
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
return
}
params := database.CreateGroupParams{
GroupName: req.Group,
RegistryUrl: os.Getenv("HARBOR_URL"),
Projects: projects,
}
result, err := s.dbQueries.CreateGroup(r.Context(), params)
if err != nil {
HandleAppError(w, err)
return
}
satellites, err := s.dbQueries.GroupSatelliteList(r.Context(), result.ID)
if err != nil {
HandleAppError(w, err)
return
}
for _, satellite := range satellites {
robotAcc, err := s.dbQueries.GetRobotAccBySatelliteID(r.Context(), satellite.SatelliteID)
if err != nil {
HandleAppError(w, err)
return
}
// update robot account projects permission
_, err = utils.UpdateRobotProjects(r.Context(), projects, robotAcc.RobotName, robotAcc.RobotID)
if err != nil {
HandleAppError(w, err)
return
}
}
// Create State Artifact for the group
err = utils.CreateStateArtifact(&req)
if err != nil {
HandleAppError(w, err)
return
}
WriteJSONResponse(w, http.StatusOK, result)

Comment on lines +12 to +21
const addRobotAccount = `-- name: AddRobotAccount :one
INSERT INTO robot_accounts (robot_name, robot_secret, robot_id, satellite_id, created_at, updated_at)
VALUES ($1, $2, $3, $4, NOW(), NOW())
ON CONFLICT (robot_id)
DO UPDATE SET
robot_name = EXCLUDED.robot_name,
robot_secret = EXCLUDED.robot_secret,
updated_at = NOW()
RETURNING id, robot_name, robot_secret, robot_id, satellite_id, created_at, updated_at
`
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider including 'satellite_id' in the ON CONFLICT DO UPDATE clause

Currently, if there's a conflict on robot_id, the satellite_id is not updated. If a robot account's satellite_id can change, consider including it in the DO UPDATE SET clause to ensure it reflects the latest association.

Apply this diff to include satellite_id in the update:

 INSERT INTO robot_accounts (robot_name, robot_secret, robot_id, satellite_id, created_at, updated_at)
 VALUES ($1, $2, $3, $4, NOW(), NOW())
   ON CONFLICT (robot_id)
   DO UPDATE SET
     robot_name = EXCLUDED.robot_name,
     robot_secret = EXCLUDED.robot_secret,
+    satellite_id = EXCLUDED.satellite_id,
     updated_at = NOW()
 RETURNING id, robot_name, robot_secret, robot_id, satellite_id, created_at, updated_at
📝 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
const addRobotAccount = `-- name: AddRobotAccount :one
INSERT INTO robot_accounts (robot_name, robot_secret, robot_id, satellite_id, created_at, updated_at)
VALUES ($1, $2, $3, $4, NOW(), NOW())
ON CONFLICT (robot_id)
DO UPDATE SET
robot_name = EXCLUDED.robot_name,
robot_secret = EXCLUDED.robot_secret,
updated_at = NOW()
RETURNING id, robot_name, robot_secret, robot_id, satellite_id, created_at, updated_at
`
const addRobotAccount = `-- name: AddRobotAccount :one
INSERT INTO robot_accounts (robot_name, robot_secret, robot_id, satellite_id, created_at, updated_at)
VALUES ($1, $2, $3, $4, NOW(), NOW())
ON CONFLICT (robot_id)
DO UPDATE SET
robot_name = EXCLUDED.robot_name,
robot_secret = EXCLUDED.robot_secret,
satellite_id = EXCLUDED.satellite_id,
updated_at = NOW()
RETURNING id, robot_name, robot_secret, robot_id, satellite_id, created_at, updated_at
`

Comment on lines +104 to +133
func (q *Queries) ListRobotAccounts(ctx context.Context) ([]RobotAccount, error) {
rows, err := q.db.QueryContext(ctx, listRobotAccounts)
if err != nil {
return nil, err
}
defer rows.Close()
var items []RobotAccount
for rows.Next() {
var i RobotAccount
if err := rows.Scan(
&i.ID,
&i.RobotName,
&i.RobotSecret,
&i.RobotID,
&i.SatelliteID,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Redundant call to rows.Close(), already deferred

The call to rows.Close() is deferred at line 109, so the explicit call to rows.Close() at line 126 is unnecessary. Removing the extra rows.Close() call prevents potential double-closing of the rows resource.

Apply this diff to remove the redundant call:

     defer rows.Close()
     var items []RobotAccount
     for rows.Next() {
         // Processing each row
     }
-    if err := rows.Close(); err != nil {
-        return nil, err
-    }
     if err := rows.Err(); err != nil {
         return nil, err
     }
📝 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 (q *Queries) ListRobotAccounts(ctx context.Context) ([]RobotAccount, error) {
rows, err := q.db.QueryContext(ctx, listRobotAccounts)
if err != nil {
return nil, err
}
defer rows.Close()
var items []RobotAccount
for rows.Next() {
var i RobotAccount
if err := rows.Scan(
&i.ID,
&i.RobotName,
&i.RobotSecret,
&i.RobotID,
&i.SatelliteID,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
func (q *Queries) ListRobotAccounts(ctx context.Context) ([]RobotAccount, error) {
rows, err := q.db.QueryContext(ctx, listRobotAccounts)
if err != nil {
return nil, err
}
defer rows.Close()
var items []RobotAccount
for rows.Next() {
var i RobotAccount
if err := rows.Scan(
&i.ID,
&i.RobotName,
&i.RobotSecret,
&i.RobotID,
&i.SatelliteID,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

Copy link

@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: 9

🧹 Outside diff range and nitpick comments (2)
ground-control/internal/server/routes.go (2)

15-15: Remove commented out code.

Instead of keeping commented code, consider removing it entirely. If this functionality might be needed later, it should be tracked in version control history or as a GitHub issue.


24-25: Convert TODO into a GitHub issue.

Instead of keeping inline TODOs, create a GitHub issue to track the missing functionality for listing satellites attached to a group. This will help with project tracking and visibility.

Would you like me to create a GitHub issue for tracking this enhancement?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between a1d7827 and 524285f.

📒 Files selected for processing (5)
  • ground-control/.env (1 hunks)
  • ground-control/internal/models/models.go (1 hunks)
  • ground-control/internal/server/handlers.go (4 hunks)
  • ground-control/internal/server/routes.go (1 hunks)
  • ground-control/internal/utils/helper.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • ground-control/.env
  • ground-control/internal/models/models.go
🔇 Additional comments (4)
ground-control/internal/server/handlers.go (4)

259-360: LGTM! Well-implemented Zero Touch Registration handler.

The implementation includes proper:

  • Transaction management
  • Token validation and cleanup
  • Error handling
  • Group state assembly

460-471: ⚠️ Potential issue

Improve token generation for odd character lengths.

The current implementation might generate tokens shorter than requested when charLength is odd. Also, add input validation.

Apply these improvements:

func GenerateRandomToken(charLength int) (string, error) {
+    if charLength <= 0 {
+        return "", fmt.Errorf("token length must be positive")
+    }
+
     // The number of bytes needed to generate a token with the required number of hex characters
-    byteLength := charLength / 2
+    byteLength := (charLength + 1) / 2  // Handle odd lengths correctly
 
     // Create a byte slice of the required length
     token := make([]byte, byteLength)
     _, err := rand.Read(token)
     if err != nil {
         return "", err
     }
 
     // Return the token as a hex-encoded string
-    return hex.EncodeToString(token), nil
+    hexToken := hex.EncodeToString(token)
+    // Ensure exact length for odd charLength
+    if len(hexToken) > charLength {
+        hexToken = hexToken[:charLength]
+    }
+    return hexToken, nil
}

Likely invalid or redundant comment.


187-239: 🛠️ Refactor suggestion

Optimize group validation to reduce database calls.

The current implementation makes multiple database and API calls within a loop. Consider validating all groups upfront to reduce the number of database calls.

Consider this optimization:

if req.Groups != nil {
+    // Validate all groups first
+    validGroups := make(map[string]database.Group)
+    for _, groupName := range req.Groups {
+        group, err := q.GetGroupByName(r.Context(), groupName)
+        if err != nil {
+            HandleAppError(w, &AppError{
+                Message: fmt.Sprintf("Invalid group name: %v", groupName),
+                Code:    http.StatusBadRequest,
+            })
+            tx.Rollback()
+            return
+        }
+        validGroups[groupName] = group
+    }
+
+    // Process validated groups
+    for _, group := range validGroups {
+        err = q.AddSatelliteToGroup(r.Context(), database.AddSatelliteToGroupParams{
+            SatelliteID: satellite.ID,
+            GroupID:     group.ID,
+        })
+        if err != nil {
+            HandleAppError(w, err)
+            tx.Rollback()
+            return
+        }
+    }
}

Likely invalid or redundant comment.


372-380: ⚠️ Potential issue

Fix incorrect implementation of getSatelliteByID.

The handler incorrectly uses ListSatellites instead of retrieving a specific satellite by ID.

Apply this fix:

func (s *Server) getSatelliteByID(w http.ResponseWriter, r *http.Request) {
-    result, err := s.dbQueries.ListSatellites(r.Context())
+    vars := mux.Vars(r)
+    idStr := vars["id"]
+    
+    id, err := strconv.ParseInt(idStr, 10, 32)
+    if err != nil {
+        HandleAppError(w, &AppError{
+            Message: "Invalid satellite ID",
+            Code:    http.StatusBadRequest,
+        })
+        return
+    }
+
+    result, err := s.dbQueries.GetSatelliteByID(r.Context(), int32(id))
     if err != nil {
+        if err == sql.ErrNoRows {
+            HandleAppError(w, &AppError{
+                Message: "Satellite not found",
+                Code:    http.StatusNotFound,
+            })
+            return
+        }
         HandleAppError(w, err)
         return
     }
     WriteJSONResponse(w, http.StatusOK, result)
}

Likely invalid or redundant comment.

// r.HandleFunc("/images", s.addImageListHandler).Methods("POST")
// r.HandleFunc("/group", s.deleteGroupHandler).Methods("DELETE")
r.HandleFunc("/satellites/register", s.registerSatelliteHandler).Methods("POST")
r.HandleFunc("/satellites/ztr/{token}", s.ztrHandler).Methods("GET")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider using POST instead of GET for ZTR endpoint.

Using GET with tokens in the URL path can expose sensitive information in server logs and browser history. Consider using POST method instead:

-r.HandleFunc("/satellites/ztr/{token}", s.ztrHandler).Methods("GET")
+r.HandleFunc("/satellites/ztr", s.ztrHandler).Methods("POST")

The token can be sent in the request body or as an Authorization header.

Committable suggestion was skipped due to low confidence.

Comment on lines 18 to 34
r.HandleFunc("/groups/sync", s.groupsSyncHandler).Methods("POST")
r.HandleFunc("/groups/list", s.listGroupHandler).Methods("GET")
r.HandleFunc("/groups/{group}", s.getGroupHandler).Methods("GET")
r.HandleFunc("/groups/satellite", s.addSatelliteToGroup).Methods("PUT")
r.HandleFunc("/groups/satellite", s.removeSatelliteFromGroup).Methods("PUT")

r.HandleFunc("/group", s.createGroupHandler).Methods("POST")
r.HandleFunc("/label", s.createLabelHandler).Methods("POST")
r.HandleFunc("/image", s.addImageHandler).Methods("POST")
r.HandleFunc("/satellite", s.addSatelliteHandler).Methods("POST")
// to-do: listing functionality to list satellites attached to group
// r.HandleFunc("/groups/{group}/list", s.groupSatelliteHandler).Methods("GET")

r.HandleFunc("/group/satellite", s.addSatelliteToGroup).Methods("POST")
r.HandleFunc("/label/satellite", s.addSatelliteToLabel).Methods("POST")

r.HandleFunc("/group/images", s.assignImageToGroup).Methods("POST")
r.HandleFunc("/label/images", s.assignImageToLabel).Methods("POST")
// r.HandleFunc("/satellites", s.addSatelliteHandler).Methods("POST")

r.HandleFunc("/satellite/images", s.GetImagesForSatellite).Methods("GET")

// Satellite based routes
// r.HandleFunc("/images", s.getImageListHandler).Methods("GET")
// r.HandleFunc("/images", s.addImageListHandler).Methods("POST")
// r.HandleFunc("/group", s.deleteGroupHandler).Methods("DELETE")
r.HandleFunc("/satellites/register", s.registerSatelliteHandler).Methods("POST")
r.HandleFunc("/satellites/ztr/{token}", s.ztrHandler).Methods("GET")
r.HandleFunc("/satellites/list", s.listSatelliteHandler).Methods("GET")
r.HandleFunc("/satellites/{satellite}", s.getSatelliteByID).Methods("GET")
r.HandleFunc("/satellites/{satellite}", s.deleteSatelliteByID).Methods("DELETE")
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider organizing routes by resource type.

For better maintainability, consider grouping related routes together and adding comments to separate different resource types:

 // Health check routes
 r.HandleFunc("/ping", s.Ping).Methods("GET")
 r.HandleFunc("/health", s.healthHandler).Methods("GET")

+// Group management routes
 r.HandleFunc("/groups/sync", s.groupsSyncHandler).Methods("POST")
 r.HandleFunc("/groups/list", s.listGroupHandler).Methods("GET")
 r.HandleFunc("/groups/{group}", s.getGroupHandler).Methods("GET")
 r.HandleFunc("/groups/satellite", s.addSatelliteToGroup).Methods("PUT")
 r.HandleFunc("/groups/satellite", s.removeSatelliteFromGroup).Methods("PUT")

+// Satellite management routes
 r.HandleFunc("/satellites/register", s.registerSatelliteHandler).Methods("POST")
 r.HandleFunc("/satellites/ztr", s.ztrHandler).Methods("POST")
 r.HandleFunc("/satellites/list", s.listSatelliteHandler).Methods("GET")
 r.HandleFunc("/satellites/{satellite}", s.getSatelliteByID).Methods("GET")
 r.HandleFunc("/satellites/{satellite}", s.deleteSatelliteByID).Methods("DELETE")

Committable suggestion was skipped due to low confidence.

Comment on lines 30 to 34
r.HandleFunc("/satellites/register", s.registerSatelliteHandler).Methods("POST")
r.HandleFunc("/satellites/ztr/{token}", s.ztrHandler).Methods("GET")
r.HandleFunc("/satellites/list", s.listSatelliteHandler).Methods("GET")
r.HandleFunc("/satellites/{satellite}", s.getSatelliteByID).Methods("GET")
r.HandleFunc("/satellites/{satellite}", s.deleteSatelliteByID).Methods("DELETE")
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

ZTR implementation needs additional security measures

The current Zero Touch Registration (ZTR) implementation has several gaps compared to standard security requirements:

  • No token expiration mechanism in the schema or handler implementation
  • Missing token validation beyond simple existence check
  • No rate limiting for token verification attempts
  • Token storage lacks a revocation status field
  • Missing secure token transmission guidelines

Required changes:

  1. Add token expiration timestamp to satellite_token table
  2. Implement token expiration check in ztrHandler
  3. Add rate limiting for /satellites/ztr/{token} endpoint
  4. Include token revocation status in schema
  5. Document secure token transmission requirements
🔗 Analysis chain

Verify ZTR implementation against requirements.

Let's verify if the current implementation aligns with the Zero Touch Registration requirements from issues #41 and #36.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Analyze the ZTR implementation

# Test 1: Check the ZTR handler implementation
echo "Checking ZTR handler implementation..."
ast-grep --pattern 'func $_($$) {
  $$$
  ztrHandler
  $$$
}'

# Test 2: Look for any configuration or environment variables related to ZTR
echo "Checking ZTR configuration..."
rg -l "ZTR|ZERO_TOUCH" --type=env --type=yaml --type=json

# Test 3: Check for any database schema changes related to ZTR
echo "Checking database schema for ZTR..."
fd -e sql -x rg -l "token|registration"

Length of output: 575


Script:

#!/bin/bash
# Let's analyze the ZTR implementation more thoroughly

# Check the content of schema and query files
echo "=== Schema file content ==="
cat ./ground-control/sql/schema/014_satellite_token.sql

echo -e "\n=== Satellite token queries ==="
cat ./ground-control/sql/queries/satellite_token.sql

# Check the ZTR handler implementation
echo -e "\n=== ZTR Handler Implementation ==="
rg -A 10 "func.*ztrHandler" ./ground-control/internal/server/

# Check for any configuration related to token expiry or validation
echo -e "\n=== Token Configuration ==="
rg "token" ./ground-control/internal/config/ -A 3

# Look for token validation logic
echo -e "\n=== Token Validation Logic ==="
rg "ValidateToken|verifyToken|validateToken" ./ground-control/internal/

Length of output: 2266


Script:

#!/bin/bash
# Let's see the complete ZTR handler implementation and related code

# Get the full ZTR handler implementation with more context
echo "=== Complete ZTR Handler ==="
rg -A 30 "func.*ztrHandler" ./ground-control/internal/server/handlers.go

# Check for satellite registration related code
echo -e "\n=== Satellite Registration Handler ==="
rg -A 20 "func.*registerSatelliteHandler" ./ground-control/internal/server/handlers.go

# Look for any token generation or validation utilities
echo -e "\n=== Token Utils ==="
rg -A 10 "func.*[Tt]oken" ./ground-control/internal/

# Check for any ZTR-related types/structs
echo -e "\n=== ZTR Related Types ==="
ast-grep --pattern 'type $_ struct {
  $$$
  [Tt]oken
  $$$
}'

Length of output: 8148

Comment on lines 38 to 41
// Add "https://" scheme if missing
if !strings.Contains(rawURL, "://") {
rawURL = "https://" + rawURL
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add URL validation for security

The automatic addition of "https://" scheme could lead to security issues if the URL contains malicious characters or schemes.

Consider this safer implementation:

-	if !strings.Contains(rawURL, "://") {
-		rawURL = "https://" + rawURL
-	}
+	// Validate URL scheme
+	if !strings.HasPrefix(rawURL, "https://") {
+		if strings.Contains(rawURL, "://") {
+			return reg.Images{}, fmt.Errorf("only https scheme is allowed")
+		}
+		rawURL = "https://" + rawURL
+	}
📝 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
// Add "https://" scheme if missing
if !strings.Contains(rawURL, "://") {
rawURL = "https://" + rawURL
}
// Validate URL scheme
if !strings.HasPrefix(rawURL, "https://") {
if strings.Contains(rawURL, "://") {
return reg.Images{}, fmt.Errorf("only https scheme is allowed")
}
rawURL = "https://" + rawURL
}

Comment on lines 99 to 121
func CreateRobotAccForSatellite(ctx context.Context, repos []string, name string) (*models.RobotCreated, error) {
// get harbor client
harborClient, err := harbor.GetClient()
if err != nil {
return nil, fmt.Errorf("error getting Harbor client: %w", err)
}

var projects []string
// get projects from repos
for _, project := range repos {
proj := strings.Split(project, "/")
projects = append(projects, proj[0])
}

robotTemp := harbor.RobotAccountTemplate(name, projects)

robot, err := harbor.CreateRobotAccount(ctx, robotTemp, harborClient)
if err != nil {
return nil, fmt.Errorf("error creating robot account: %w", err)
}

return robot.Payload, nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve context handling in CreateRobotAccForSatellite

The function accepts a context but doesn't propagate it to Harbor client operations.

Modify the function to use context in Harbor client operations:

 func CreateRobotAccForSatellite(ctx context.Context, repos []string, name string) (*models.RobotCreated, error) {
-	harborClient, err := harbor.GetClient()
+	harborClient, err := harbor.GetClient(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("error getting Harbor client: %w", err)
 	}

Committable suggestion was skipped due to low confidence.

Comment on lines 189 to 200
err = crane.Push(img, destinationRepo, options...)
if err != nil {
return fmt.Errorf("push image failed: %v", err)
}
err = crane.Tag(destinationRepo, fmt.Sprintf("%d", time.Now().Unix()), options...)
if err != nil {
return fmt.Errorf("tag image failed: %v", err)
}
err = crane.Tag(destinationRepo, "latest", options...)
if err != nil {
return fmt.Errorf("tag image failed: %v", err)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure atomic operations for state updates

The current implementation has a race condition where the state update is not atomic. If multiple updates occur simultaneously, they could interfere with each other.

Consider this atomic implementation:

+	// Generate unique timestamp for atomic operation
+	timestamp := fmt.Sprintf("%d", time.Now().UnixNano())
+	timestampTag := fmt.Sprintf("%s:%s", destinationRepo, timestamp)
+
-	err = crane.Push(img, destinationRepo, options...)
+	// Push with timestamp tag first
+	err = crane.Push(img, timestampTag, options...)
 	if err != nil {
 		return fmt.Errorf("push image failed: %v", err)
 	}
-	err = crane.Tag(destinationRepo, fmt.Sprintf("%d", time.Now().Unix()), options...)
-	if err != nil {
-		return fmt.Errorf("tag image failed: %v", err)
-	}
-	err = crane.Tag(destinationRepo, "latest", options...)
+
+	// Atomically update latest tag
+	err = crane.Tag(timestampTag, destinationRepo+":latest", options...)
 	if err != nil {
+		// Cleanup on failure
+		_ = crane.Delete(timestampTag, options...)
 		return fmt.Errorf("tag image failed: %v", err)
 	}
📝 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
err = crane.Push(img, destinationRepo, options...)
if err != nil {
return fmt.Errorf("push image failed: %v", err)
}
err = crane.Tag(destinationRepo, fmt.Sprintf("%d", time.Now().Unix()), options...)
if err != nil {
return fmt.Errorf("tag image failed: %v", err)
}
err = crane.Tag(destinationRepo, "latest", options...)
if err != nil {
return fmt.Errorf("tag image failed: %v", err)
}
// Generate unique timestamp for atomic operation
timestamp := fmt.Sprintf("%d", time.Now().UnixNano())
timestampTag := fmt.Sprintf("%s:%s", destinationRepo, timestamp)
// Push with timestamp tag first
err = crane.Push(img, timestampTag, options...)
if err != nil {
return fmt.Errorf("push image failed: %v", err)
}
// Atomically update latest tag
err = crane.Tag(timestampTag, destinationRepo+":latest", options...)
if err != nil {
// Cleanup on failure
_ = crane.Delete(timestampTag, options...)
return fmt.Errorf("tag image failed: %v", err)
}

Comment on lines 21 to 24
type RegisterSatelliteParams struct {
Name string `json:"satellite_name"`
Groups *[]string `json:"groups,omitempty"`
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unnecessary pointer to slice in RegisterSatelliteParams.

Using a pointer to a slice (*[]string) adds unnecessary complexity since slices in Go are already reference types.

Apply this diff to simplify the struct:

type RegisterSatelliteParams struct {
    Name   string    `json:"satellite_name"`
-   Groups *[]string `json:"groups,omitempty"`
+   Groups []string  `json:"groups,omitempty"`
}
📝 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
type RegisterSatelliteParams struct {
Name string `json:"satellite_name"`
Groups *[]string `json:"groups,omitempty"`
}
type RegisterSatelliteParams struct {
Name string `json:"satellite_name"`
Groups []string `json:"groups,omitempty"`
}

Comment on lines +65 to +69
projects := utils.GetProjectNames(&req.Artifacts)
params := database.CreateGroupParams{
GroupName: req.GroupName,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
GroupName: req.Group,
RegistryUrl: os.Getenv("HARBOR_URL"),
Projects: projects,
Copy link

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 empty projects list.

The handler should validate that the projects list is not empty before creating a group.

Add validation after retrieving project names:

projects := utils.GetProjectNames(&req.Artifacts)
+if len(projects) == 0 {
+    HandleAppError(w, &AppError{
+        Message: "No valid projects found in artifacts",
+        Code:    http.StatusBadRequest,
+    })
+    return
+}
📝 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
projects := utils.GetProjectNames(&req.Artifacts)
params := database.CreateGroupParams{
GroupName: req.GroupName,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
GroupName: req.Group,
RegistryUrl: os.Getenv("HARBOR_URL"),
Projects: projects,
projects := utils.GetProjectNames(&req.Artifacts)
if len(projects) == 0 {
HandleAppError(w, &AppError{
Message: "No valid projects found in artifacts",
Code: http.StatusBadRequest,
})
return
}
params := database.CreateGroupParams{
GroupName: req.Group,
RegistryUrl: os.Getenv("HARBOR_URL"),
Projects: projects,

Comment on lines 382 to 389
func (s *Server) deleteSatelliteByID(w http.ResponseWriter, r *http.Request) {
result, err := s.dbQueries.ListSatellites(r.Context())
if err != nil {
log.Printf("Error: Failed to assign image to label: %v", err)
HandleAppError(w, err)
return
}

WriteJSONResponse(w, http.StatusOK, map[string]string{})
WriteJSONResponse(w, http.StatusOK, result)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect implementation of deleteSatelliteByID.

Similar to getSatelliteByID, this handler incorrectly uses ListSatellites instead of deleting a specific satellite.

Apply this fix:

func (s *Server) deleteSatelliteByID(w http.ResponseWriter, r *http.Request) {
-    result, err := s.dbQueries.ListSatellites(r.Context())
+    vars := mux.Vars(r)
+    idStr := vars["id"]
+    
+    id, err := strconv.ParseInt(idStr, 10, 32)
+    if err != nil {
+        HandleAppError(w, &AppError{
+            Message: "Invalid satellite ID",
+            Code:    http.StatusBadRequest,
+        })
+        return
+    }
+
+    err = s.dbQueries.DeleteSatellite(r.Context(), int32(id))
     if err != nil {
+        if err == sql.ErrNoRows {
+            HandleAppError(w, &AppError{
+                Message: "Satellite not found",
+                Code:    http.StatusNotFound,
+            })
+            return
+        }
         HandleAppError(w, err)
         return
     }
-    WriteJSONResponse(w, http.StatusOK, result)
+    WriteJSONResponse(w, http.StatusOK, map[string]string{"status": "deleted"})
}
📝 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 (s *Server) deleteSatelliteByID(w http.ResponseWriter, r *http.Request) {
result, err := s.dbQueries.ListSatellites(r.Context())
if err != nil {
log.Printf("Error: Failed to assign image to label: %v", err)
HandleAppError(w, err)
return
}
WriteJSONResponse(w, http.StatusOK, map[string]string{})
WriteJSONResponse(w, http.StatusOK, result)
func (s *Server) deleteSatelliteByID(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
idStr := vars["id"]
id, err := strconv.ParseInt(idStr, 10, 32)
if err != nil {
HandleAppError(w, &AppError{
Message: "Invalid satellite ID",
Code: http.StatusBadRequest,
})
return
}
err = s.dbQueries.DeleteSatellite(r.Context(), int32(id))
if err != nil {
if err == sql.ErrNoRows {
HandleAppError(w, &AppError{
Message: "Satellite not found",
Code: http.StatusNotFound,
})
return
}
HandleAppError(w, err)
return
}
WriteJSONResponse(w, http.StatusOK, map[string]string{"status": "deleted"})
}

@bupd bupd marked this pull request as ready for review October 31, 2024 12:39
@bupd bupd marked this pull request as draft October 31, 2024 12:40
Signed-off-by: bupd <[email protected]>
Signed-off-by: bupd <[email protected]>
Copy link

@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: 32

🧹 Outside diff range and nitpick comments (9)
ground-control/sql/schema/008_satellite_token.sql (2)

3-9: Enhance security measures for token management.

Consider implementing these security improvements:

  1. Add token expiration mechanism (expires_at TIMESTAMP)
  2. Consider hashing tokens before storage
  3. Add a status field to enable token revocation
 CREATE TABLE satellite_token (
   id SERIAL PRIMARY KEY,
   satellite_id INT NOT NULL REFERENCES satellites(id) ON DELETE CASCADE,
   token VARCHAR(64) UNIQUE NOT NULL,
+  token_hash VARCHAR(128) UNIQUE NOT NULL,
+  status VARCHAR(16) NOT NULL DEFAULT 'active',
+  expires_at TIMESTAMP NOT NULL,
   created_at TIMESTAMP NOT NULL DEFAULT NOW(),
   updated_at TIMESTAMP NOT NULL DEFAULT NOW()
 );
+
+CREATE INDEX idx_satellite_token_expires_at ON satellite_token(expires_at);
+
+-- Ensure only valid status values
+ALTER TABLE satellite_token ADD CONSTRAINT chk_status 
+  CHECK (status IN ('active', 'revoked', 'expired'));

11-12: Consider data preservation in down migration.

The current down migration will permanently delete all token data. For production safety, consider adding a data preservation mechanism before dropping the table.

 -- +goose Down
+-- Archive existing tokens before dropping
+CREATE TABLE IF NOT EXISTS archived_satellite_tokens AS
+SELECT * FROM satellite_token;
+
 DROP TABLE satellite_token;
ground-control/sql/schema/004_robots.sql (1)

13-14: Consider data preservation in rollback strategy

The current rollback strategy will permanently delete all robot data. For a production system, consider:

  1. Adding a data export step before dropping the table
  2. Cleaning up any dependent objects
  3. Documenting the rollback implications

Consider adding these safety measures:

 -- +goose Down
+-- Archive robots data before dropping
+CREATE TABLE IF NOT EXISTS archived_robots AS SELECT * FROM robots;
 DROP TABLE robots;
ground-control/sql/schema/007_robot_accounts.sql (1)

8-8: Consider soft deletes for audit purposes.

The ON DELETE CASCADE might lead to loss of historical data. Consider implementing soft deletes if audit trail is important for security or compliance.

-  satellite_id INT NOT NULL REFERENCES satellites(id) ON DELETE CASCADE,
+  satellite_id INT NOT NULL REFERENCES satellites(id),
+  deleted_at TIMESTAMP,
ground-control/reg/harbor/client.go (1)

1-26: Consider implementing a more robust Harbor client factory for Zero Touch Registration.

For a critical component like Zero Touch Registration, the Harbor client initialization should be more robust. Consider implementing a proper client factory pattern with:

  1. Connection pooling
  2. Circuit breaker pattern for failure handling
  3. Metrics/monitoring for client operations
  4. Graceful shutdown handling

This would improve reliability and observability of the satellite registration process.

Would you like assistance in designing this improved architecture?

ground-control/sql/queries/satellites.sql (2)

2-3: LGTM! Architectural change noted.

The removal of the token field aligns well with the Zero Touch Registration implementation, shifting away from token-based authentication. Using NOW() for timestamps is a good practice.


17-19: Remove commented-out code if token-based auth is deprecated.

If token-based authentication is being fully replaced by Zero Touch Registration, it would be cleaner to remove this code entirely rather than keeping it commented out. This prevents confusion and maintains cleaner code.

ground-control/reg/harbor/project.go (1)

1-41: Consider architectural improvements for better maintainability

To better support Zero Touch Registration (ZTR) for satellites, consider these architectural improvements:

  1. Create a configuration structure for Harbor settings:
type HarborConfig struct {
    ProjectName   string
    IsPublic      bool
    StorageLimit  int64
    RequestTimeout time.Duration
}
  1. Implement a proper client management pattern:
type HarborManager struct {
    client  *v2client.HarborAPI
    config  *HarborConfig
}
  1. Add metrics and structured logging for better observability

This would make the code more maintainable and configurable for different deployment scenarios.

ground-control/internal/server/routes.go (1)

27-31: Standardize handler naming convention.

The satellite routes mix different naming conventions:

  • Some use camelCase: GetSatelliteByName, DeleteSatelliteByName
  • Others use lowerCase: registerSatelliteHandler, listSatelliteHandler

Consider standardizing to one convention:

 r.HandleFunc("/satellites/register", s.registerSatelliteHandler).Methods("POST")
 r.HandleFunc("/satellites/ztr/{token}", s.ztrHandler).Methods("GET")
 r.HandleFunc("/satellites/list", s.listSatelliteHandler).Methods("GET")
-r.HandleFunc("/satellites/{satellite}", s.GetSatelliteByName).Methods("GET")
-r.HandleFunc("/satellites/{satellite}", s.DeleteSatelliteByName).Methods("DELETE")
+r.HandleFunc("/satellites/{satellite}", s.getSatelliteHandler).Methods("GET")
+r.HandleFunc("/satellites/{satellite}", s.deleteSatelliteHandler).Methods("DELETE")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 524285f and ab3befd.

📒 Files selected for processing (18)
  • ground-control/.env (1 hunks)
  • ground-control/internal/database/satellites.sql.go (3 hunks)
  • ground-control/internal/models/models.go (1 hunks)
  • ground-control/internal/server/handlers.go (4 hunks)
  • ground-control/internal/server/routes.go (1 hunks)
  • ground-control/internal/utils/helper.go (1 hunks)
  • ground-control/reg/adapter.go (0 hunks)
  • ground-control/reg/adapter_test.go (0 hunks)
  • ground-control/reg/harbor/client.go (1 hunks)
  • ground-control/reg/harbor/project.go (1 hunks)
  • ground-control/reg/harbor/replication.go (1 hunks)
  • ground-control/reg/harbor/robot.go (7 hunks)
  • ground-control/sql/queries/satellites.sql (1 hunks)
  • ground-control/sql/schema/004_robots.sql (1 hunks)
  • ground-control/sql/schema/005_projects.sql (1 hunks)
  • ground-control/sql/schema/006_robot_projects.sql (1 hunks)
  • ground-control/sql/schema/007_robot_accounts.sql (1 hunks)
  • ground-control/sql/schema/008_satellite_token.sql (1 hunks)
💤 Files with no reviewable changes (2)
  • ground-control/reg/adapter.go
  • ground-control/reg/adapter_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • ground-control/.env
  • ground-control/internal/database/satellites.sql.go
  • ground-control/internal/models/models.go
  • ground-control/reg/harbor/replication.go
  • ground-control/reg/harbor/robot.go
🔇 Additional comments (9)
ground-control/sql/schema/005_projects.sql (1)

10-11: LGTM: Down migration looks correct

The down migration properly removes the table, which is appropriate for this schema change.

ground-control/sql/schema/006_robot_projects.sql (1)

9-10: LGTM!

The down migration correctly drops the table, which is appropriate for a new table migration.

ground-control/sql/schema/008_satellite_token.sql (1)

1-12: Verify integration with codebase.

Let's verify the implementation of SQL queries using this table and ensure proper error handling.

✅ Verification successful

SQL implementation is properly integrated with appropriate error handling

The verification shows that:

  • The table is properly integrated with generated SQLC code
  • Complete CRUD operations are implemented with proper error handling:
    • AddToken: Inserts new tokens with satellite association
    • DeleteToken: Removes tokens
    • GetSatelliteIDByToken: Retrieves satellite ID from token
    • GetToken: Fetches complete token record
    • ListToken: Lists all tokens
  • Each operation includes context support and error handling
  • Foreign key constraint to satellites table is enforced at database level
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for SQL query implementations and error handling
echo "Checking for SQL query implementations..."
rg -l "satellite_token" --type go

echo "Checking for proper error handling in token operations..."
ast-grep --pattern 'err := $$.QueryRow("INSERT INTO satellite_token' -l

Length of output: 487


Script:

#!/bin/bash
# Let's check the content of the generated SQL file and any related Go files
echo "=== Checking generated SQL file ==="
cat ground-control/internal/database/satellite_token.sql.go

echo -e "\n=== Checking for token-related operations in Go files ==="
rg -g '*.go' -B 2 -A 2 'satellite_token'

echo -e "\n=== Looking for token validation or error handling patterns ==="
ast-grep --pattern 'if err := $$.QueryRow($$$); err != nil {
  $$$
}' --lang go

Length of output: 5246

ground-control/sql/schema/007_robot_accounts.sql (2)

6-6: Consider encrypting sensitive data.

The robot_secret column stores sensitive credentials. Consider using pgcrypto extension to encrypt this data at rest.
[security]

-  robot_secret VARCHAR(255) NOT NULL,
+  robot_secret TEXT NOT NULL CHECK (robot_secret = crypt(robot_secret, gen_salt('bf'))),

5-5: Clarify the distinction between robot_name and robot_id.

Both fields are unique identifiers. Could you clarify the intended use case for each? If they serve similar purposes, consider consolidating them to reduce redundancy.

Also applies to: 7-7

ground-control/reg/harbor/client.go (1)

21-26: ⚠️ Potential issue

Improve error handling and add connection validation for Harbor client.

Converting errors to panics in a service component is risky, especially for Zero Touch Registration where reliability is crucial. Consider:

  1. Proper error handling instead of panic
  2. Connection validation
  3. Retry mechanism for transient failures

Here's a suggested improvement:

-func GetClientByConfig(clientConfig *harbor.ClientSetConfig) *v2client.HarborAPI {
+func GetClientByConfig(clientConfig *harbor.ClientSetConfig) (*v2client.HarborAPI, error) {
     cs, err := harbor.NewClientSet(clientConfig)
     if err != nil {
-        panic(err)
+        return nil, fmt.Errorf("failed to create Harbor client: %w", err)
     }
-    return cs.V2()
+    client := cs.V2()
+    
+    // Validate connection
+    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+    defer cancel()
+    if _, err := client.System.GetSystemInfo(ctx); err != nil {
+        return nil, fmt.Errorf("failed to validate Harbor connection: %w", err)
+    }
+    
+    return client, nil
 }

Let's check if there are any direct dependencies on this client that need to be updated:

#!/bin/bash
# Search for GetClient or GetClientByConfig usage
ast-grep --pattern 'GetClient()' 
ast-grep --pattern 'GetClientByConfig($_)'
ground-control/sql/queries/satellites.sql (1)

25-27: Verify safety measures for satellite deletion.

The name-based deletion operation raises several concerns:

  1. Ensure satellite names are unique to prevent accidental multiple deletions
  2. Consider implementing soft delete for audit purposes
  3. Add safety checks for active connections or dependencies
✅ Verification successful

Satellite deletion is properly constrained but requires documentation

The verification shows that the implementation is actually well-designed:

  • The name column has a UNIQUE constraint, preventing duplicate satellite names
  • All foreign key references have appropriate ON DELETE clauses:
    • satellite_token: CASCADE - tokens are deleted with the satellite
    • satellite_groups: CASCADE - group associations are removed
    • robots: SET NULL - robots are preserved but disassociated
    • robot_accounts: CASCADE - associated accounts are removed

While the technical implementation is sound, consider documenting these cascading effects in comments for maintainers.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if satellite names have a UNIQUE constraint
echo "Checking for UNIQUE constraint on satellite names..."
rg -A 5 "CREATE TABLE satellites" 

# Check for potential multiple references to a satellite
echo "Checking for foreign key references to satellites..."
rg "REFERENCES satellites"

Length of output: 1197

ground-control/reg/harbor/project.go (1)

1-11: LGTM! Clean and focused imports

The imports are appropriate for the Harbor project management functionality.

ground-control/internal/server/routes.go (1)

22-24: Implement the group satellite listing functionality.

The TODO comment indicates a missing feature for listing satellites within a group. This functionality would be valuable for ground control admins and follows good REST practices.

Consider implementing this endpoint as it would complement the existing group management features.

Would you like me to help implement the groupSatelliteHandler or create an issue to track this task?

Comment on lines 3 to 8
CREATE TABLE projects (
id SERIAL PRIMARY KEY,
projects VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add trigger for updated_at timestamp

The updated_at column should be automatically updated whenever a row is modified. Add a trigger to handle this.

CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_projects_updated_at
    BEFORE UPDATE ON projects
    FOR EACH ROW
    EXECUTE FUNCTION update_updated_at_column();

🛠️ Refactor suggestion

Consider adding a unique constraint on the name column

Since this appears to be a project management table, it would be logical to prevent duplicate project names.

 CREATE TABLE projects (
   id SERIAL PRIMARY KEY,
-  projects VARCHAR(255) NOT NULL,
+  projects VARCHAR(255) NOT NULL UNIQUE,
   created_at TIMESTAMP DEFAULT NOW() NOT NULL,
   updated_at TIMESTAMP DEFAULT NOW() NOT NULL
 );
📝 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
CREATE TABLE projects (
id SERIAL PRIMARY KEY,
projects VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);
CREATE TABLE projects (
id SERIAL PRIMARY KEY,
projects VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);

⚠️ Potential issue

Review column naming convention

The column name projects (plural) in a table named projects is unusual and potentially confusing. Consider renaming it to name or project_name (singular) to better represent a single project's name.

 CREATE TABLE projects (
   id SERIAL PRIMARY KEY,
-  projects VARCHAR(255) NOT NULL,
+  name VARCHAR(255) NOT NULL,
   created_at TIMESTAMP DEFAULT NOW() NOT NULL,
   updated_at TIMESTAMP DEFAULT NOW() NOT NULL
 );
📝 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
CREATE TABLE projects (
id SERIAL PRIMARY KEY,
projects VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);
CREATE TABLE projects (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);

💡 Codebase verification

Consider adding indexes for robot_projects and projects tables

Based on the codebase analysis, here are the recommended indexes:

  • For the projects table:
    CREATE INDEX idx_projects_name ON projects(projects);
  • For the robot_projects table:
    CREATE INDEX idx_robot_projects_project_id ON robot_projects(project_id);
    CREATE INDEX idx_robot_projects_robot_id ON robot_projects(robot_id);

The indexes are recommended because:

  1. Projects are frequently looked up by name during robot account creation and group operations
  2. The robot_projects junction table will be queried frequently to find project associations
  3. Both robot_id and project_id are used in JOIN operations and foreign key relationships
🔗 Analysis chain

Consider adding indexes based on access patterns

Given this is part of a Zero Touch Satellite Registration system, you might want to add indexes based on how you'll query this table. For example, if you'll be frequently looking up projects by name:

CREATE INDEX idx_projects_name ON projects(projects);

Please verify the query patterns in your application code to determine if additional indexes would be beneficial.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for SQL queries involving the projects table to understand access patterns
rg -A 3 "SELECT.*FROM.*projects" --type sql

Length of output: 45


Script:

#!/bin/bash
# Let's search for any SQL queries involving the projects table, including JOINs and WHERE clauses
rg -i "projects.*where|from.*projects|join.*projects" --type-add 'code:*.{js,ts,go,rb,py}' --type code -A 3

# Also search for repository files that might contain SQL queries
fd -e sql -e js -e ts -e go -e rb -e py -x grep -l -i "projects" {}

Length of output: 576


Script:

#!/bin/bash
# Let's examine the content of these files to understand the query patterns
cat ./ground-control/sql/queries/groups.sql
cat ./ground-control/internal/database/groups.sql.go
cat ./ground-control/sql/schema/006_robot_projects.sql

# Also check for any model definitions or structs related to projects
rg -A 5 "type.*Project" ./ground-control/internal/database/models.go

Length of output: 3981


Script:

#!/bin/bash
# Let's check for any direct queries on the projects table
rg -A 3 "SELECT.*FROM.*projects|INSERT.*INTO.*projects|UPDATE.*projects" ./ground-control/sql/queries/

# Let's also check handlers to see how projects are accessed
cat ./ground-control/internal/server/handlers.go

Length of output: 16651

Comment on lines 3 to 7
CREATE TABLE robot_projects (
robot_id INT REFERENCES robots(id) ON DELETE CASCADE,
project_id INT REFERENCES projects(id) ON DELETE CASCADE,
PRIMARY KEY (robot_id, project_id)
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding audit fields and indexes for better performance.

The table structure correctly implements the many-to-many relationship between robots and projects with proper foreign key constraints. However, consider the following improvements:

  1. Add timestamp fields for auditing: created_at and updated_at
  2. Add an index on project_id for queries filtering by project
  3. Consider adding a status field for soft deletion capability

Here's the enhanced table definition:

 CREATE TABLE robot_projects (
   robot_id INT REFERENCES robots(id) ON DELETE CASCADE,
   project_id INT REFERENCES projects(id) ON DELETE CASCADE,
-  PRIMARY KEY (robot_id, project_id)
+  PRIMARY KEY (robot_id, project_id),
+  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+  updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
 );
+
+CREATE INDEX idx_robot_projects_project_id ON robot_projects(project_id);
+
+-- Add trigger to update updated_at timestamp
+CREATE TRIGGER update_robot_projects_updated_at
+  BEFORE UPDATE ON robot_projects
+  FOR EACH ROW
+  EXECUTE FUNCTION update_updated_at_column();

Committable suggestion was skipped due to low confidence.

Comment on lines 1 to 10
-- +goose Up

CREATE TABLE robot_projects (
robot_id INT REFERENCES robots(id) ON DELETE CASCADE,
project_id INT REFERENCES projects(id) ON DELETE CASCADE,
PRIMARY KEY (robot_id, project_id)
);

-- +goose Down
DROP TABLE robot_projects;
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Schema changes need adjustments for Zero Touch Registration

Based on the verification results, there are several issues that need to be addressed:

  1. The projects table has a column named projects (possibly a typo) instead of an expected name column
  2. The projects table lacks essential columns for access control (like owner_id or role-related fields)
  3. No existing robot registration endpoints or handlers were found in the codebase
  4. The satellite-related endpoints are limited to a ping endpoint in test code

The current schema changes for robot_projects are foundational but insufficient for Zero Touch Registration without the proper supporting infrastructure.

🔗 Analysis chain

Verify integration with Zero Touch Registration flow.

The schema changes support the Zero Touch Registration requirements by enabling satellite-to-project associations. However, we should verify:

  1. The registration flow's ability to create these associations
  2. The existence of required permissions/roles in the projects table
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if projects table has necessary columns for ZTR
echo "Verifying projects table structure..."
rg -A 10 "CREATE TABLE projects" 

# Check for any existing robot registration related functions
echo "Checking for robot registration procedures..."
rg -l "FUNCTION.*register.*robot"

Length of output: 903


Script:

#!/bin/bash
# Let's check for any robot registration related code in Go files
echo "Checking for robot registration handlers..."
rg -A 5 "func.*Register.*Robot"

# Check for any robot-related API endpoints
echo "Checking for robot API endpoints..."
rg -A 5 "(/api/.*robot|/api/.*satellite)"

# Check for any robot registration related migrations or procedures
echo "Checking for robot registration migrations..."
fd -e sql -x grep -l -i "robot.*registration"

# Let's also check the projects table column name
echo "Verifying projects column name..."
rg "projects\s+VARCHAR" ground-control/sql/schema/005_projects.sql

Length of output: 781

Comment on lines 3 to 9
CREATE TABLE satellite_token (
id SERIAL PRIMARY KEY,
satellite_id INT NOT NULL REFERENCES satellites(id) ON DELETE CASCADE,
token VARCHAR(64) UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider enhancing table structure for better security and performance.

While the basic structure is sound, consider the following improvements:

  1. Increase token length to accommodate more secure tokens (recommend VARCHAR(128))
  2. Add an explicit index on satellite_id for better query performance
  3. Add table/column comments to document the purpose
 CREATE TABLE satellite_token (
   id SERIAL PRIMARY KEY,
   satellite_id INT NOT NULL REFERENCES satellites(id) ON DELETE CASCADE,
-  token VARCHAR(64) UNIQUE NOT NULL,
+  token VARCHAR(128) UNIQUE NOT NULL,
   created_at TIMESTAMP NOT NULL DEFAULT NOW(),
   updated_at TIMESTAMP NOT NULL DEFAULT NOW()
 );
+
+CREATE INDEX idx_satellite_token_satellite_id ON satellite_token(satellite_id);
+
+COMMENT ON TABLE satellite_token IS 'Stores authentication tokens for zero-touch satellite registration';
+COMMENT ON COLUMN satellite_token.token IS 'Unique authentication token used during satellite registration';
📝 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
CREATE TABLE satellite_token (
id SERIAL PRIMARY KEY,
satellite_id INT NOT NULL REFERENCES satellites(id) ON DELETE CASCADE,
token VARCHAR(64) UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE TABLE satellite_token (
id SERIAL PRIMARY KEY,
satellite_id INT NOT NULL REFERENCES satellites(id) ON DELETE CASCADE,
token VARCHAR(128) UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_satellite_token_satellite_id ON satellite_token(satellite_id);
COMMENT ON TABLE satellite_token IS 'Stores authentication tokens for zero-touch satellite registration';
COMMENT ON COLUMN satellite_token.token IS 'Unique authentication token used during satellite registration';

Comment on lines 3 to 11
CREATE TABLE robots (
id SERIAL PRIMARY KEY,
robot_name VARCHAR(255) UNIQUE NOT NULL,
secret VARCHAR(255) NOT NULL,
satellite_id INT UNIQUE
REFERENCES satellites(id) ON DELETE SET NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance security and add missing database features

Several improvements could be made to strengthen security and follow best practices:

  1. The secret column stores sensitive data and should have:

    • Minimum length requirement
    • Hash format validation
    • Consider using a more appropriate type like CHAR(60) for bcrypt hashes
  2. Add an update trigger for updated_at

  3. Consider adding appropriate indexes for common query patterns

Apply these improvements:

 CREATE TABLE robots (
   id SERIAL PRIMARY KEY,
   robot_name VARCHAR(255) UNIQUE NOT NULL,
-  secret VARCHAR(255) NOT NULL,
+  secret CHAR(60) NOT NULL CHECK (LENGTH(secret) = 60),
   satellite_id INT UNIQUE
   REFERENCES satellites(id) ON DELETE SET NULL,
   created_at TIMESTAMP DEFAULT NOW() NOT NULL,
   updated_at TIMESTAMP DEFAULT NOW() NOT NULL
 );
+
+CREATE INDEX idx_robots_robot_name ON robots(robot_name);
+
+CREATE TRIGGER update_robots_updated_at
+  BEFORE UPDATE ON robots
+  FOR EACH ROW
+  EXECUTE FUNCTION update_updated_at_column();

Committable suggestion was skipped due to low confidence.

Comment on lines 53 to 72
// Update robot account
func UpdateRobotProjects(ctx context.Context, projects []string, name string, id string) (*robot.UpdateRobotOK, error) {
ID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
return nil, fmt.Errorf("error invalid ID: %w", err)
}
robot, err := harbor.GetRobotAccount(ctx, ID)
if err != nil {
return nil, fmt.Errorf("error getting robot account: %w", err)
}

robot.Permissions = harbor.GenRobotPerms(projects)

updated, err := harbor.UpdateRobotAccount(ctx, robot)
if err != nil {
return nil, fmt.Errorf("error updating robot account: %w", err)
}

return updated, nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input validation and improve error messages

The function needs input validation for projects array and name.

Add input validation:

 func UpdateRobotProjects(ctx context.Context, projects []string, name string, id string) (*robot.UpdateRobotOK, error) {
+	if len(projects) == 0 {
+		return nil, fmt.Errorf("projects list cannot be empty")
+	}
+	if name == "" {
+		return nil, fmt.Errorf("robot name cannot be empty")
+	}
+	if id == "" {
+		return nil, fmt.Errorf("robot ID cannot be empty")
+	}
+
 	ID, err := strconv.ParseInt(id, 10, 64)
 	if err != nil {
-		return nil, fmt.Errorf("error invalid ID: %w", err)
+		return nil, fmt.Errorf("invalid robot ID %q: %w", id, err)
 	}
📝 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
// Update robot account
func UpdateRobotProjects(ctx context.Context, projects []string, name string, id string) (*robot.UpdateRobotOK, error) {
ID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
return nil, fmt.Errorf("error invalid ID: %w", err)
}
robot, err := harbor.GetRobotAccount(ctx, ID)
if err != nil {
return nil, fmt.Errorf("error getting robot account: %w", err)
}
robot.Permissions = harbor.GenRobotPerms(projects)
updated, err := harbor.UpdateRobotAccount(ctx, robot)
if err != nil {
return nil, fmt.Errorf("error updating robot account: %w", err)
}
return updated, nil
}
// Update robot account
func UpdateRobotProjects(ctx context.Context, projects []string, name string, id string) (*robot.UpdateRobotOK, error) {
if len(projects) == 0 {
return nil, fmt.Errorf("projects list cannot be empty")
}
if name == "" {
return nil, fmt.Errorf("robot name cannot be empty")
}
if id == "" {
return nil, fmt.Errorf("robot ID cannot be empty")
}
ID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid robot ID %q: %w", id, err)
}
robot, err := harbor.GetRobotAccount(ctx, ID)
if err != nil {
return nil, fmt.Errorf("error getting robot account: %w", err)
}
robot.Permissions = harbor.GenRobotPerms(projects)
updated, err := harbor.UpdateRobotAccount(ctx, robot)
if err != nil {
return nil, fmt.Errorf("error updating robot account: %w", err)
}
return updated, nil
}

Comment on lines 248 to 271
// check if project satellite exists and if does not exist create project satellite
satExist, err := harbor.GetProject(r.Context(), "satellite")
if err != nil {
log.Println(err)
err := &AppError{
Message: fmt.Sprintf("Error: Checking satellite project: %v", err),
Code: http.StatusBadGateway,
}
HandleAppError(w, err)
tx.Rollback()
return
}
if !satExist {
_, err := harbor.CreateSatelliteProject(r.Context())
if err != nil {
log.Println(err)
err := &AppError{
Message: fmt.Sprintf("Error: creating satellite project: %v", err),
Code: http.StatusBadGateway,
}
HandleAppError(w, err)
tx.Rollback()
return
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract satellite project creation logic into a shared function.

This code block is duplicated in both groupsSyncHandler and registerSatelliteHandler. Consider extracting it into a shared function to improve maintainability.

func (s *Server) ensureSatelliteProjectExists(ctx context.Context) error {
    satExist, err := harbor.GetProject(ctx, "satellite")
    if err != nil {
        return fmt.Errorf("checking satellite project: %w", err)
    }
    
    if !satExist {
        if _, err := harbor.CreateSatelliteProject(ctx); err != nil {
            return fmt.Errorf("creating satellite project: %w", err)
        }
    }
    return nil
}

Also applies to: 117-140

Comment on lines 329 to 363
func (s *Server) ztrHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
token := vars["token"]

// Start a new transaction
tx, err := s.db.BeginTx(r.Context(), nil)
if err != nil {
log.Println(err)
HandleAppError(w, err)
return
}

params := database.AddSatelliteToGroupParams{
SatelliteID: int32(req.SatelliteID),
GroupID: int32(req.GroupID),
q := s.dbQueries.WithTx(tx)

defer func() {
if p := recover(); p != nil {
// If there's a panic, rollback the transaction
tx.Rollback()
panic(p) // Re-throw the panic after rolling back
} else if err != nil {
tx.Rollback() // Rollback transaction on error
}
}()

satelliteID, err := q.GetSatelliteIDByToken(r.Context(), token)
if err != nil {
log.Println("Invalid Satellite Token")
log.Println(err)
err := &AppError{
Message: "Error: Invalid Token",
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
tx.Rollback()
return
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve token validation security.

Several security concerns in the token validation process:

  1. Tokens in URLs can be logged by proxies
  2. No rate limiting for validation attempts
  3. Token is deleted before verifying the complete registration chain

Consider these improvements:

  1. Accept token in request body or header instead of URL
  2. Implement rate limiting for token validation
  3. Move token deletion to after successful validation

Example rate limiting implementation:

func (s *Server) rateLimitedZTR(token string) error {
    key := fmt.Sprintf("ztr_attempts:%s", token)
    attempts, err := s.cache.Incr(key)
    if err != nil {
        return err
    }
    
    // Set key expiration on first attempt
    if attempts == 1 {
        s.cache.Expire(key, 1*time.Hour)
    }
    
    if attempts > 5 {
        return fmt.Errorf("too many validation attempts")
    }
    
    return nil
}

Comment on lines 437 to 449
func (s *Server) listSatelliteHandler(w http.ResponseWriter, r *http.Request) {
result, err := s.dbQueries.ListSatellites(r.Context())
if err != nil {
log.Printf("Error: Failed to add satellite to label: %v", err)
log.Printf("Error: Failed to List Satellites: %v", err)
err := &AppError{
Message: "Error: Failed to List Satellites",
Code: http.StatusInternalServerError,
}
HandleAppError(w, err)
return
}

WriteJSONResponse(w, http.StatusOK, map[string]string{})
WriteJSONResponse(w, http.StatusOK, result)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add pagination to satellite listing.

The current implementation loads all satellites at once, which could cause performance issues with a large number of satellites.

Add pagination support:

type ListSatellitesParams struct {
    Limit  int32 `json:"limit"`
    Offset int32 `json:"offset"`
}

func (s *Server) listSatelliteHandler(w http.ResponseWriter, r *http.Request) {
    params := ListSatellitesParams{
        Limit:  20, // Default limit
        Offset: 0,
    }
    
    if limit := r.URL.Query().Get("limit"); limit != "" {
        if l, err := strconv.ParseInt(limit, 10, 32); err == nil {
            params.Limit = int32(l)
        }
    }
    
    if offset := r.URL.Query().Get("offset"); offset != "" {
        if o, err := strconv.ParseInt(offset, 10, 32); err == nil {
            params.Offset = int32(o)
        }
    }
    
    result, total, err := s.dbQueries.ListSatellitesPaginated(r.Context(), params)
    // ... error handling ...
    
    response := struct {
        Items []database.Satellite `json:"items"`
        Total int                 `json:"total"`
    }{
        Items: result,
        Total: total,
    }
    
    WriteJSONResponse(w, http.StatusOK, response)
}

Comment on lines 488 to 527
func (s *Server) addSatelliteToGroup(w http.ResponseWriter, r *http.Request) {
var req SatelliteGroupParams
if err := DecodeRequestBody(r, &req); err != nil {
HandleAppError(w, err)
return
}

params := database.AssignImageToGroupParams{
GroupID: int32(req.GroupID),
ImageID: int32(req.ImageID),
sat, err := s.dbQueries.GetSatelliteByName(r.Context(), req.Satellite)
if err != nil {
log.Printf("Error: Satellite Not Found: %v", err)
err := &AppError{
Message: "Error: Satellite Not Found",
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
return
}
grp, err := s.dbQueries.GetGroupByName(r.Context(), req.Group)
if err != nil {
log.Printf("Error: Group Not Found: %v", err)
err := &AppError{
Message: "Error: Group Not Found",
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
return
}

params := database.AddSatelliteToGroupParams{
SatelliteID: int32(sat.ID),
GroupID: int32(grp.ID),
}

err := s.dbQueries.AssignImageToGroup(r.Context(), params)
err = s.dbQueries.AddSatelliteToGroup(r.Context(), params)
if err != nil {
log.Printf("Error: Failed to assign image to group: %v", err)
log.Printf("Error: Failed to Add Satellite to Group: %v", err)
err := &AppError{
Message: "Error: Failed to Add Satellite to Group",
Code: http.StatusInternalServerError,
}
Copy link

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 group relationships.

The current implementation doesn't validate group relationships, which could lead to:

  1. Circular dependencies between groups
  2. Orphaned resources when groups are deleted

Consider adding these validations:

func (s *Server) validateGroupRelationship(ctx context.Context, satelliteID, groupID int32) error {
    // Check for circular dependencies
    visited := make(map[int32]bool)
    var checkCycle func(int32) error
    
    checkCycle = func(gID int32) error {
        if visited[gID] {
            return fmt.Errorf("circular dependency detected")
        }
        
        visited[gID] = true
        parentGroups, err := s.dbQueries.GetParentGroups(ctx, gID)
        if err != nil {
            return err
        }
        
        for _, parent := range parentGroups {
            if err := checkCycle(parent.ID); err != nil {
                return err
            }
        }
        
        return nil
    }
    
    return checkCycle(groupID)
}

@bupd bupd self-assigned this Nov 2, 2024
@bupd bupd marked this pull request as ready for review November 2, 2024 09:44
Copy link

@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: 13

🧹 Outside diff range and nitpick comments (5)
ground-control/sql/schema/005_satellite_token.sql (1)

3-9: Consider enhancing the schema with additional security and performance features.

The table structure is well-designed with appropriate constraints and relationships. However, consider the following improvements:

  1. Add a token expiration timestamp for enhanced security:
 CREATE TABLE satellite_token (
   id SERIAL PRIMARY KEY,
   satellite_id INT NOT NULL REFERENCES satellites(id) ON DELETE CASCADE,
   token VARCHAR(64) UNIQUE NOT NULL,
+  expires_at TIMESTAMPTZ,
-  created_at TIMESTAMP NOT NULL DEFAULT NOW(),
-  updated_at TIMESTAMP NOT NULL DEFAULT NOW()
+  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
 );
  1. Add an index to optimize token lookups:
CREATE INDEX idx_satellite_token_token ON satellite_token(token);
config.toml (2)

Line range hint 1-1: Fix typo in comment: "Wether" should be "Whether".

-# Wether to us the built-in Zot registry or not
+# Whether to use the built-in Zot registry or not

Line range hint 1-19: Improve configuration documentation and validation.

Consider enhancing the configuration file with:

  1. Type hints and validation rules for each setting
  2. Consistent use of quotes (some values use double quotes, others don't)
  3. Grouping related settings together (e.g., all registry settings)
  4. Documentation about which settings are required for ZTR

Would you like me to propose a fully documented and structured version of this configuration file?

main.go (1)

33-36: Consider enhancing Zero Touch Registration robustness.

To improve the Zero Touch Registration implementation, consider these architectural enhancements:

  1. Add validation for required ZTR-specific configurations
  2. Implement health checks to verify ZTR readiness
  3. Add retry logic for handling transient initialization failures

I can help implement these enhancements. Would you like me to:

  1. Generate code for ZTR configuration validation?
  2. Implement health check endpoints for ZTR status?
  3. Add retry logic with backoff for initialization?
internal/config/config.go (1)

49-81: Enhance error messages with more context

The error messages in the ztr function are generic and may not provide enough context when issues arise. Including additional information can aid in debugging.

Refine error messages to include request details:

 if resp.StatusCode != http.StatusOK {
-    return nil, fmt.Errorf("unexpected response from Ground Control: %v", resp.Status)
+    return nil, fmt.Errorf("unexpected response from Ground Control at %s: %v", url, resp.Status)
 }

 // Decode the JSON response
 var res ZtrResult
 if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
-    return nil, fmt.Errorf("failed to decode JSON response: %v", err)
+    return nil, fmt.Errorf("failed to decode JSON response from %s: %v", url, err)
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ab3befd and 13781f4.

⛔ Files ignored due to path filters (1)
  • ground-control/go.sum is excluded by !**/*.sum
📒 Files selected for processing (7)
  • config.toml (1 hunks)
  • ground-control/go.mod (2 hunks)
  • ground-control/internal/database/models.go (3 hunks)
  • ground-control/sql/schema/004_robot_accounts.sql (1 hunks)
  • ground-control/sql/schema/005_satellite_token.sql (1 hunks)
  • internal/config/config.go (5 hunks)
  • main.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ground-control/go.mod
🔇 Additional comments (7)
ground-control/sql/schema/005_satellite_token.sql (1)

11-12: LGTM!

The rollback command appropriately drops the table, maintaining a clean migration history.

ground-control/sql/schema/004_robot_accounts.sql (2)

1-2: LGTM: Migration structure follows best practices

The migration file is properly structured with both Up and Down migrations, allowing for proper version control and rollback capabilities.

Also applies to: 13-14


5-7: Clarify the relationship between robot_name and robot_id

Both robot_name and robot_id are unique identifiers. Please clarify:

  1. What's the semantic difference between these fields?
  2. Could one of them be redundant?
  3. Should there be any specific format validation?

Let's check if these fields are used consistently across the codebase:

✅ Verification successful

Let me search for any additional context about these fields in the codebase, particularly in Go files that might show their usage patterns:


Based on the code analysis, I can now provide a clear explanation about the fields:

robot_name and robot_id serve distinct purposes - both fields are necessary

The codebase reveals that:

  • robot_name is a human-readable identifier used for Harbor robot account creation (e.g., satellite name)
  • robot_id stores Harbor's internal numeric ID as a string (converted from int64), used for API operations
  • Both fields are used in different contexts:
    • robot_name is used for template creation and display
    • robot_id is used for Harbor API operations (delete, refresh, update)

Example from the code:

// Creation
RobotName:   rbt.Name,      // Human readable name
RobotID:     strconv.Itoa(int(rbt.ID)),  // Harbor's internal ID

Format validation should be added:

  • robot_name should follow Harbor's naming conventions
  • robot_id should be a valid numeric string
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usage patterns of robot_name and robot_id
echo "Searching for robot_name usage:"
rg "robot_name" -A 3
echo -e "\nSearching for robot_id usage:"
rg "robot_id" -A 3

Length of output: 7413


Script:

#!/bin/bash
# Search for robot account related structs and types
ast-grep --pattern 'type $_ struct {
  $$$
  RobotName $_
  $$$
}'

echo -e "\nSearching for robot account related code comments:"
rg -i "robot.*name|robot.*id" --type go -A 2

Length of output: 13509

config.toml (1)

18-19: 🛠️ Refactor suggestion

Reconsider error-only logging for new feature deployment.

Setting log_level to "error" might make it difficult to debug Zero Touch Registration issues during initial deployment. Consider using "info" level initially and documenting the recommended production level.

 # Set logging level: debug, info, warn, error, fatal, panic
-log_level = "error"
+# Development: Use "info" or "debug" for better visibility during ZTR implementation
+# Production: Use "error" for minimal logging once stable
+log_level = "info"
ground-control/internal/database/models.go (1)

Line range hint 1-47: Models support Zero Touch Registration requirements.

The new and modified data structures align well with the PR objectives for implementing Zero Touch Registration (ZTR) for satellites:

  • Group structure supports satellite group management
  • RobotAccount structure enables automated authentication
  • SatelliteToken structure facilitates secure registration

Note: This is an autogenerated file by sqlc, so no direct code modifications should be made here.

Let's verify if the corresponding SQL migrations exist for these model changes:

✅ Verification successful

Generated models match their SQL schema definitions perfectly

The autogenerated models in models.go correctly reflect all the SQL schema migrations:

  • All tables (satellites, groups, satellite_groups, robot_accounts, satellite_token) have corresponding migrations
  • Field types and names in the Go structs match their SQL counterparts
  • Foreign key relationships are properly represented
  • Timestamps (created_at, updated_at) are consistently defined
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for related migration files that define these models
# Expected: Find SQL files containing CREATE TABLE statements for the new structures

# Look for migration files
fd -e sql | while read -r file; do
  echo "=== Checking $file ==="
  rg -A 3 'CREATE TABLE.*(groups|robot_accounts|satellite_tokens)' "$file"
done

Length of output: 3102


Script:

#!/bin/bash
# Let's check for satellite_token table definition which wasn't found in the previous search
# and verify the complete schema for all tables

# First, let's check for satellite_token table specifically
echo "=== Checking for satellite_token table ==="
rg -p "CREATE TABLE.*satellite_token" ground-control/sql/schema/

# Now let's verify all table definitions match our models
echo -e "\n=== Checking schema details for all related tables ==="
for file in $(fd -e sql . ground-control/sql/schema/); do
  echo -e "\n--- Content of $file ---"
  cat "$file"
done

Length of output: 3209

main.go (2)

21-21: LGTM! Good addition for Zero Touch Registration.

The automatic loading of environment variables via godotenv/autoload aligns well with the Zero Touch Registration requirements, ensuring configuration is loaded without manual intervention.


Line range hint 21-35: Verify environment variable usage and error handling patterns.

Let's verify the consistency of environment variable handling and error patterns across the codebase.

✅ Verification successful

Let me gather more specific information about the environment variable usage patterns to ensure consistency.


Environment variable handling and error patterns are consistent across the codebase

The verification shows:

  • Consistent use of godotenv/autoload for environment variable loading across multiple packages
  • Proper config initialization pattern in main.go that aligns with the implementation in internal/config/config.go
  • Consistent error wrapping patterns using fmt.Errorf with appropriate context
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check environment variable usage and error handling patterns

# Check for environment variable usage patterns
echo "Checking environment variable usage patterns..."
rg -l "os\.Getenv|godotenv"

# Check error handling patterns
echo -e "\nChecking error handling patterns..."
ast-grep --pattern 'if err := $_; err != nil {
  return fmt.Errorf($_, err)
}'

Length of output: 1175


Script:

#!/bin/bash
# Check specific environment variable usage patterns in config initialization
echo "Checking config initialization and env patterns..."
rg -A 5 "InitConfig|LoadConfig" 

# Check godotenv usage patterns
echo -e "\nChecking godotenv usage patterns..."
rg -A 3 "godotenv"

# Check error wrapping patterns in config related files
echo -e "\nChecking error handling in config files..."
rg -A 2 "fmt\.Errorf.*config" internal/

Length of output: 4525

robot_name VARCHAR(255) UNIQUE NOT NULL,
robot_secret VARCHAR(255) NOT NULL,
robot_id VARCHAR(255) UNIQUE NOT NULL,
satellite_id INT NOT NULL REFERENCES satellites(id) ON DELETE CASCADE,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add index for foreign key performance

The satellite_id foreign key should be indexed to improve query performance, especially for lookups and joins.

Add this after the table creation:

CREATE INDEX idx_robot_accounts_satellite_id ON robot_accounts(satellite_id);

Comment on lines +9 to +10
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add trigger for updated_at timestamp

The updated_at timestamp won't automatically update on record changes.

Add this after the table creation:

CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_robot_accounts_updated_at
    BEFORE UPDATE ON robot_accounts
    FOR EACH ROW
    EXECUTE FUNCTION update_updated_at_column();

Comment on lines +3 to +11
CREATE TABLE robot_accounts (
id SERIAL PRIMARY KEY,
robot_name VARCHAR(255) UNIQUE NOT NULL,
robot_secret VARCHAR(255) NOT NULL,
robot_id VARCHAR(255) UNIQUE NOT NULL,
satellite_id INT NOT NULL REFERENCES satellites(id) ON DELETE CASCADE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider additional schema improvements

A few suggestions to enhance the schema:

  1. Add CHECK constraints for field formats if applicable
  2. Consider adding a comment to the table explaining its purpose
  3. Consider adding a status field for account lifecycle management

Example improvements:

COMMENT ON TABLE robot_accounts IS 'Stores robot account credentials for zero-touch satellite registration';

-- Add format validation if there's a specific format requirement
ALTER TABLE robot_accounts 
  ADD CONSTRAINT check_robot_id_format 
  CHECK (robot_id ~ '^[A-Za-z0-9_-]+$');

-- Add status field for account lifecycle
ALTER TABLE robot_accounts 
  ADD COLUMN status VARCHAR(50) NOT NULL 
  DEFAULT 'active' 
  CHECK (status IN ('active', 'inactive', 'suspended'));

CREATE TABLE robot_accounts (
id SERIAL PRIMARY KEY,
robot_name VARCHAR(255) UNIQUE NOT NULL,
robot_secret VARCHAR(255) NOT NULL,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security concern: Sensitive data storage

The robot_secret column stores secrets in plaintext. Consider using a more secure approach.

Apply this diff to hash the secrets:

-  robot_secret VARCHAR(255) NOT NULL,
+  robot_secret_hash VARCHAR(255) NOT NULL,

Additionally, ensure that the application layer hashes the secrets before storage.

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

Comment on lines +14 to +16
# ground control
ground_control_url = "http://localhost:8080"
# eg. ground_control = "https://ground-control.bupd.xyz"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security: Use HTTPS for ground control communication.

The default ground control URL uses HTTP, which is insecure for satellite communication. Consider:

  1. Making HTTPS mandatory for production environments
  2. Adding a comment about security implications
 # ground control
-ground_control_url = "http://localhost:8080"
+ground_control_url = "https://localhost:8080"
-# eg. ground_control = "https://ground-control.bupd.xyz"
+# Production deployments must use HTTPS, eg: "https://ground-control.bupd.xyz"
+# Note: Using HTTP in production is insecure and not recommended for satellite communication
📝 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
# ground control
ground_control_url = "http://localhost:8080"
# eg. ground_control = "https://ground-control.bupd.xyz"
# ground control
ground_control_url = "https://localhost:8080"
# Production deployments must use HTTPS, eg: "https://ground-control.bupd.xyz"
# Note: Using HTTP in production is insecure and not recommended for satellite communication

Comment on lines 54 to 55
// Construct the request URL
url := fmt.Sprintf("%s/satellites/ztr/%s", gcUrl, token)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure ground_control_url is not empty

The gcUrl parameter should be validated to prevent constructing invalid URLs, which could cause HTTP request failures.

Add a check for gcUrl:

 // Construct the request URL
+if gcUrl == "" {
+    return nil, fmt.Errorf("ground_control_url is empty")
+}
 url := fmt.Sprintf("%s/satellites/ztr/%s", gcUrl, 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
// Construct the request URL
url := fmt.Sprintf("%s/satellites/ztr/%s", gcUrl, token)
// Construct the request URL
if gcUrl == "" {
return nil, fmt.Errorf("ground_control_url is empty")
}
url := fmt.Sprintf("%s/satellites/ztr/%s", gcUrl, token)

Comment on lines +75 to +79
viper.Set("auth.name", res.Auth.Name) // Set auth name in config
viper.Set("auth.secret", res.Auth.Secret) // Set auth secret in config
viper.Set("auth.registry", res.Auth.Registry) // Set auth registry in config
viper.Set("states", res.States) // Set states array in config
viper.WriteConfigAs("config.gen.json")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle errors when writing the configuration file

The viper.WriteConfigAs("config.gen.json") call does not check for errors. If writing the configuration file fails, the application might proceed with incomplete or incorrect settings without any warning.

Modify the code to handle potential errors:

     viper.Set("auth.name", res.Auth.Name)         // Set auth name in config
     viper.Set("auth.secret", res.Auth.Secret)     // Set auth secret in config
     viper.Set("auth.registry", res.Auth.Registry) // Set auth registry in config
     viper.Set("states", res.States)               // Set states array in config
-    viper.WriteConfigAs("config.gen.json")
+    if err := viper.WriteConfigAs("config.gen.json"); err != nil {
+        return nil, fmt.Errorf("failed to write configuration file: %v", err)
+    }

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


⚠️ Potential issue

Potential concurrency issues with Viper's global instance

If your application is or becomes concurrent, using Viper's global instance (viper.Set, viper.WriteConfigAs) without proper synchronization can lead to race conditions.

Consider creating a new Viper instance within the ztr function:

 func ztr(gcUrl string) (*ZtrResult, error) {
+    localViper := viper.New()
     // Get environment variables
     token := os.Getenv("TOKEN")
     // Construct the request URL
     url := fmt.Sprintf("%s/satellites/ztr/%s", gcUrl, token)
     // [Rest of the code]
-    viper.Set("auth.name", res.Auth.Name)
-    viper.Set("auth.secret", res.Auth.Secret)
-    viper.Set("auth.registry", res.Auth.Registry)
-    viper.Set("states", res.States)
-    if err := viper.WriteConfigAs("config.gen.json"); err != nil {
+    localViper.Set("auth.name", res.Auth.Name)
+    localViper.Set("auth.secret", res.Auth.Secret)
+    localViper.Set("auth.registry", res.Auth.Registry)
+    localViper.Set("states", res.States)
+    if err := localViper.WriteConfigAs("config.gen.json"); err != nil {
         return nil, fmt.Errorf("failed to write configuration file: %v", err)
     }

This isolates configurations and prevents unintended side effects.

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

internal/config/config.go Outdated Show resolved Hide resolved
Comment on lines +21 to +36
ground_control_url string
input string
zot_url string
registry string
repository string
user_input string
scheme string
api_version string
image string
token string
states []string
harbor_password string
harbor_username string
env string
use_unsecure bool
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Export Config struct fields for external package access

All fields in the Config struct are unexported due to starting with lowercase letters. This restricts access to these fields from other packages, which could limit the configurability of your application.

Consider exporting the fields by capitalizing the first letter of each field:

 type Config struct {
-    log_level          string
-    own_registry       bool
-    own_registry_adr   string
-    own_registry_port  string
-    zot_config_path    string
-    ground_control_url string
-    input              string
-    zot_url            string
-    registry           string
-    repository         string
-    user_input         string
-    scheme             string
-    api_version        string
-    image              string
-    token              string
-    states             []string
-    harbor_password    string
-    harbor_username    string
-    env                string
-    use_unsecure       bool
+    LogLevel          string
+    OwnRegistry       bool
+    OwnRegistryAdr    string
+    OwnRegistryPort   string
+    ZotConfigPath     string
+    GroundControlURL  string
+    Input             string
+    ZotURL            string
+    Registry          string
+    Repository        string
+    UserInput         string
+    Scheme            string
+    APIVersion        string
+    Image             string
+    Token             string
+    States            []string
+    HarborPassword    string
+    HarborUsername    string
+    Env               string
+    UseUnsecure       bool
 }

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

internal/config/config.go Outdated Show resolved Hide resolved
@bupd
Copy link
Collaborator Author

bupd commented Nov 2, 2024

Sample config.gen.json Created by Satellite

The generated JSON configuration file provides the following parameters:

Example content:

{
  "auth": {                 // Credentials for accessing the registry.
    "name": "robot$sat10",
    "registry": "https://registry.bupd.xyz",
    "secret": "w3BBguuGhmQAMc2hPYJplQnTbcBA8GHt"
  },
  "bring_own_registry": false,
  "ground_control_url": "http://localhost:8080",
  "log_level": "error",
  "own_registry_adr": "127.0.0.1",
  "own_registry_port": "8585",
  "states": [                          // contains array of state artifact urls to be followed.
    "https://registry.bupd.xyz/satellite/group10/state:latest"
  ],
  "url_or_file": "https://demo.goharbor.io/v2/myproject/album-server",  // this should be removed
  "zotconfigpath": "./registry/config.json"
}

Copy link

@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

🧹 Outside diff range and nitpick comments (3)
ground-control/internal/server/handlers.go (3)

294-306: Consider moving robot account creation before satellite creation.

If robot account creation fails, we'll have an orphaned satellite record. Consider creating the robot account first or implementing a cleanup mechanism.


457-469: Add pagination to satellite listing.

Loading all satellites at once could cause performance issues with a large number of satellites.

Add pagination support:

type ListSatellitesParams struct {
    Limit  int32 `json:"limit"`
    Offset int32 `json:"offset"`
}

func (s *Server) listSatelliteHandler(w http.ResponseWriter, r *http.Request) {
    params := ListSatellitesParams{
        Limit:  20, // Default limit
        Offset: 0,
    }
    
    if limit := r.URL.Query().Get("limit"); limit != "" {
        if l, err := strconv.ParseInt(limit, 10, 32); err == nil {
            params.Limit = int32(l)
        }
    }
    
    if offset := r.URL.Query().Get("offset"); offset != "" {
        if o, err := strconv.ParseInt(offset, 10, 32); err == nil {
            params.Offset = int32(o)
        }
    }
    
    result, total, err := s.dbQueries.ListSatellitesPaginated(r.Context(), params)
    // ... error handling ...
    
    response := struct {
        Items []database.Satellite `json:"items"`
        Total int                 `json:"total"`
    }{
        Items: result,
        Total: total,
    }
    
    WriteJSONResponse(w, http.StatusOK, response)
}

490-507: Improve error handling in DeleteSatelliteByName.

The function should handle sql.ErrNoRows to provide a better error message when the satellite doesn't exist.

    err := s.dbQueries.DeleteSatelliteByName(r.Context(), satellite)
    if err != nil {
+       if err == sql.ErrNoRows {
+           HandleAppError(w, &AppError{
+               Message: fmt.Sprintf("Satellite '%s' not found", satellite),
+               Code:    http.StatusNotFound,
+           })
+           return
+       }
        log.Printf("error: failed to delete satellite: %v", err)
        err := &AppError{
            Message: "Error: Failed to Delete Satellite",
            Code:    http.StatusInternalServerError,
        }
        HandleAppError(w, err)
        return
    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 13781f4 and db8fcf1.

📒 Files selected for processing (2)
  • ground-control/internal/server/handlers.go (4 hunks)
  • ground-control/reg/harbor/robot.go (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ground-control/reg/harbor/robot.go

Comment on lines 117 to 140
// check if project satellite exists and if does not exist create project satellite
satExist, err := harbor.GetProject(r.Context(), "satellite")
if err != nil {
log.Println(err)
err := &AppError{
Message: fmt.Sprintf("Error: Checking satellite project: %v", err),
Code: http.StatusBadGateway,
}
HandleAppError(w, err)
tx.Rollback()
return
}
params := database.CreateLabelParams{
LabelName: req.LabelName,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
if !satExist {
_, err := harbor.CreateSatelliteProject(r.Context())
if err != nil {
log.Println(err)
err := &AppError{
Message: fmt.Sprintf("Error: creating satellite project: %v", err),
Code: http.StatusBadGateway,
}
HandleAppError(w, err)
tx.Rollback()
return
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract duplicated satellite project creation logic.

The satellite project creation logic is duplicated in both groupsSyncHandler and registerSatelliteHandler. This violates the DRY principle and makes maintenance harder.

Extract the logic into a shared function:

func (s *Server) ensureSatelliteProjectExists(ctx context.Context) error {
    satExist, err := harbor.GetProject(ctx, "satellite")
    if err != nil {
        return fmt.Errorf("checking satellite project: %w", err)
    }
    
    if !satExist {
        if _, err := harbor.CreateSatelliteProject(ctx); err != nil {
            return fmt.Errorf("creating satellite project: %w", err)
        }
    }
    return nil
}

Also applies to: 268-291

Comment on lines +163 to +170
if len(req.Name) < 1 {
log.Println("name should be atleast one character long.")
err := &AppError{
Message: fmt.Sprintf("Error: name should be atleast one character long."),
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
return
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance satellite name validation.

The current validation only checks for length. Consider adding more comprehensive validation:

  • Check for valid characters (e.g., alphanumeric and hyphens only)
  • Prevent reserved names
  • Enforce naming conventions
func validateSatelliteName(name string) error {
    if len(name) < 1 {
        return fmt.Errorf("name should be at least one character long")
    }
    
    if !regexp.MustCompile(`^[a-z0-9][a-z0-9-]*[a-z0-9]$`).MatchString(name) {
        return fmt.Errorf("name must contain only lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen")
    }
    
    reservedNames := map[string]bool{"system": true, "admin": true}
    if reservedNames[name] {
        return fmt.Errorf("name '%s' is reserved", name)
    }
    
    return nil
}

Comment on lines 349 to 455
panic(p) // Re-throw the panic after rolling back
} else if err != nil {
tx.Rollback() // Rollback transaction on error
}
}()

satelliteID, err := q.GetSatelliteIDByToken(r.Context(), token)
if err != nil {
log.Println("Invalid Satellite Token")
log.Println(err)
err := &AppError{
Message: "Error: Invalid Token",
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
tx.Rollback()
return
}

err := s.dbQueries.AddSatelliteToLabel(r.Context(), params)
err = q.DeleteToken(r.Context(), token)
if err != nil {
log.Printf("Error: Failed to add satellite to label: %v", err)
log.Println("error deleting token")
log.Println(err)
err := &AppError{
Message: "Error: Error deleting token",
Code: http.StatusInternalServerError,
}
HandleAppError(w, err)
tx.Rollback()
return
}

WriteJSONResponse(w, http.StatusOK, map[string]string{})
}
robot, err := q.GetRobotAccBySatelliteID(r.Context(), satelliteID)
if err != nil {
log.Println("Robot Account Not Found")
log.Println(err)
err := &AppError{
Message: "Error: Robot Account Not Found for Satellite",
Code: http.StatusInternalServerError,
}
HandleAppError(w, err)
tx.Rollback()
return
}

func (s *Server) assignImageToLabel(w http.ResponseWriter, r *http.Request) {
var req AssignImageToLabelParams
if err := DecodeRequestBody(r, &req); err != nil {
// groups attached to satellite
groups, err := q.SatelliteGroupList(r.Context(), satelliteID)
if err != nil {
log.Printf("failed to list groups for satellite: %v, %v", satelliteID, err)
log.Println(err)
err := &AppError{
Message: "Error: Satellite Groups List Failed",
Code: http.StatusInternalServerError,
}
HandleAppError(w, err)
tx.Rollback()
return
}

params := database.AssignImageToLabelParams{
LabelID: int32(req.LabelID),
ImageID: int32(req.ImageID),
var states []string
for _, group := range groups {
grp, err := q.GetGroupByID(r.Context(), group.GroupID)
if err != nil {
log.Printf("failed to get group by ID: %v, %v", group.GroupID, err)
log.Println(err)
err := &AppError{
Message: "Error: Get Group By ID Failed",
Code: http.StatusInternalServerError,
}
HandleAppError(w, err)
tx.Rollback()
return
}
state := utils.AssembleGroupState(grp.GroupName)
states = append(states, state)
}

err := s.dbQueries.AssignImageToLabel(r.Context(), params)
result := models.ZtrResult{
States: states,
Auth: models.Account{
Name: robot.RobotName,
Secret: robot.RobotSecret,
Registry: os.Getenv("HARBOR_URL"),
},
}

tx.Commit()
WriteJSONResponse(w, http.StatusOK, result)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve token validation security.

Several security concerns in the token validation process:

  1. Tokens in URLs can be logged by proxies
  2. No rate limiting for validation attempts
  3. Token is deleted before verifying the complete registration chain

Consider these improvements:

  1. Accept token in request body or header
  2. Implement rate limiting
  3. Move token deletion after successful validation

Example rate limiting implementation:

func (s *Server) rateLimitedZTR(token string) error {
    key := fmt.Sprintf("ztr_attempts:%s", token)
    attempts, err := s.cache.Incr(key)
    if err != nil {
        return err
    }
    
    // Set key expiration on first attempt
    if attempts == 1 {
        s.cache.Expire(key, 1*time.Hour)
    }
    
    if attempts > 5 {
        return fmt.Errorf("too many validation attempts")
    }
    
    return nil
}

Comment on lines 508 to 552
func (s *Server) addSatelliteToGroup(w http.ResponseWriter, r *http.Request) {
var req SatelliteGroupParams
if err := DecodeRequestBody(r, &req); err != nil {
HandleAppError(w, err)
return
}

sat, err := s.dbQueries.GetSatelliteByName(r.Context(), req.Satellite)
if err != nil {
log.Printf("Error: Satellite Not Found: %v", err)
err := &AppError{
Message: "Error: Satellite Not Found",
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
return
}
result, err := s.dbQueries.GetImagesForSatellite(r.Context(), token)
grp, err := s.dbQueries.GetGroupByName(r.Context(), req.Group)
if err != nil {
log.Printf("Error: Failed to get image for satellite: %v", err)
log.Printf("Error: Group Not Found: %v", err)
err := &AppError{
Message: "Error: Group Not Found",
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
return
}

WriteJSONResponse(w, http.StatusOK, result)
}
params := database.AddSatelliteToGroupParams{
SatelliteID: int32(sat.ID),
GroupID: int32(grp.ID),
}

func (s *Server) listGroupHandler(w http.ResponseWriter, r *http.Request) {
result, err := s.dbQueries.ListGroups(r.Context())
err = s.dbQueries.AddSatelliteToGroup(r.Context(), params)
if err != nil {
log.Printf("Error: Failed to Add Satellite to Group: %v", err)
err := &AppError{
Message: "Error: Failed to Add Satellite to Group",
Code: http.StatusInternalServerError,
}
HandleAppError(w, err)
return
}

WriteJSONResponse(w, http.StatusOK, result)
WriteJSONResponse(w, http.StatusOK, map[string]string{})
Copy link

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 group relationships.

The current implementation doesn't validate group relationships, which could lead to:

  1. Circular dependencies between groups
  2. Orphaned resources when groups are deleted
func (s *Server) validateGroupRelationship(ctx context.Context, satelliteID, groupID int32) error {
    // Check for circular dependencies
    visited := make(map[int32]bool)
    var checkCycle func(int32) error
    
    checkCycle = func(gID int32) error {
        if visited[gID] {
            return fmt.Errorf("circular dependency detected")
        }
        
        visited[gID] = true
        parentGroups, err := s.dbQueries.GetParentGroups(ctx, gID)
        if err != nil {
            return err
        }
        
        for _, parent := range parentGroups {
            if err := checkCycle(parent.ID); err != nil {
                return err
            }
        }
        
        return nil
    }
    
    return checkCycle(groupID)
}

Copy link

@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: 3

🧹 Outside diff range and nitpick comments (1)
internal/config/config.go (1)

222-235: Consider using environment variables consistently

The configuration mixes values from environment variables and the config file inconsistently. For example, token uses an environment variable while ground_control_url uses the config file.

Consider implementing a clear precedence:

  1. Environment variables (override everything)
  2. Generated config (config.gen.json)
  3. Base config (config.toml)

This could be implemented by adding a helper function:

func getConfigValue(envKey, configKey string) string {
    if value := os.Getenv(envKey); value != "" {
        return value
    }
    return viper.GetString(configKey)
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between db8fcf1 and ca26bdd.

📒 Files selected for processing (1)
  • internal/config/config.go (5 hunks)
🔇 Additional comments (3)
internal/config/config.go (3)

38-47: LGTM! Well-structured response types

The ZtrResult and Account structs are well-defined with appropriate JSON tags for serialization/deserialization.


104-106: LGTM! Consistent getter implementations

The new getter methods follow the existing pattern and are properly implemented.

Also applies to: 172-178


75-79: ⚠️ Potential issue

Add error handling for configuration writing

The configuration writing operation lacks error handling.

Add error handling for the Viper operations:

     viper.Set("auth.name", res.Auth.Name)
     viper.Set("auth.secret", res.Auth.Secret)
     viper.Set("auth.registry", res.Auth.Registry)
     viper.Set("states", res.States)
-    viper.WriteConfigAs("config.gen.json")
+    if err := viper.WriteConfigAs("config.gen.json"); err != nil {
+        return nil, fmt.Errorf("failed to write configuration: %v", err)
+    }

Likely invalid or redundant comment.

Comment on lines +189 to +209
// Check if config.gen.json exists
if _, err := os.Stat("config.gen.json"); err == nil {
// If config.gen.json exists, set it as the configuration file
viper.SetConfigFile("config.gen.json")
fmt.Println("Using config.gen.json for configuration.")
} else if os.IsNotExist(err) {
// fall back to config.toml
viper.SetConfigName("config")
viper.SetConfigType("toml")
viper.AddConfigPath(".")

if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("error reading config file at path '%s': %w", viper.ConfigFileUsed(), err)
}

gcURL := viper.GetString("ground_control_url")
// Call ztr function to fetch and set the necessary configuration
_, err := ztr(gcURL)
if err != nil {
return nil, fmt.Errorf("Error in ztr function: %v\n", err)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor config loading logic for better separation of concerns

The configuration loading logic has become complex and mixes multiple responsibilities. Consider splitting the functionality and improving error messages.

Suggested refactor:

+func loadGeneratedConfig() error {
+    if _, err := os.Stat("config.gen.json"); err != nil {
+        return err
+    }
+    viper.SetConfigFile("config.gen.json")
+    return viper.ReadInConfig()
+}
+
+func loadAndGenerateConfig() error {
+    viper.SetConfigName("config")
+    viper.SetConfigType("toml")
+    viper.AddConfigPath(".")
+
+    if err := viper.ReadInConfig(); err != nil {
+        return fmt.Errorf("failed to read config.toml: %w", err)
+    }
+
+    gcURL := viper.GetString("ground_control_url")
+    if gcURL == "" {
+        return fmt.Errorf("ground_control_url not set in config.toml")
+    }
+
+    if _, err := ztr(gcURL); err != nil {
+        return fmt.Errorf("failed to generate config: %w", err)
+    }
+    return nil
+}

 func LoadConfig() (*Config, error) {
-    if _, err := os.Stat("config.gen.json"); err == nil {
-        viper.SetConfigFile("config.gen.json")
-        fmt.Println("Using config.gen.json for configuration.")
-    } else if os.IsNotExist(err) {
-        viper.SetConfigName("config")
-        // ... existing fallback code ...
-    } else {
-        return nil, fmt.Errorf("Error checking config file: %v\n", err)
+    if err := loadGeneratedConfig(); err != nil {
+        if !os.IsNotExist(err) {
+            return nil, fmt.Errorf("failed to check config.gen.json: %w", err)
+        }
+        if err := loadAndGenerateConfig(); err != nil {
+            return nil, err
+        }
     }

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

Comment on lines +16 to +35
log_level string
own_registry bool
own_registry_adr string
own_registry_port string
zot_config_path string
ground_control_url string
input string
zot_url string
registry string
repository string
user_input string
scheme string
api_version string
image string
token string
states []string
harbor_password string
harbor_username string
env string
use_unsecure bool
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Standardize field naming convention and consider field visibility

The Config struct fields use inconsistent naming conventions (snake_case) which differs from Go's standard practice of using camelCase. Additionally, all fields are unexported which may limit the struct's usability in other packages.

Consider standardizing the field names and selectively exporting fields:

 type Config struct {
-    log_level          string
-    own_registry       bool
-    own_registry_adr   string
-    own_registry_port  string
-    zot_config_path    string
-    ground_control_url string
+    LogLevel          string
+    OwnRegistry       bool
+    OwnRegistryAddr   string
+    OwnRegistryPort   string
+    ZotConfigPath     string
+    GroundControlURL  string
     // ... similar changes for other fields
 }

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

Comment on lines +50 to +63
func ztr(gcUrl string) (*ZtrResult, error) {
// Get environment variables
token := os.Getenv("TOKEN")

// Construct the request URL
gcUrl = fmt.Sprintf("%s/satellites/ztr/%s", gcUrl, token)

// Make the GET request
resp, err := http.Get(gcUrl)
if err != nil {
return nil, fmt.Errorf("failed to fetch data from Ground Control: %v", err)
}
defer resp.Body.Close()

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input validation and response size limit

The function lacks validation for critical inputs and doesn't limit the response size, which could lead to security issues.

Add the following safeguards:

 func ztr(gcUrl string) (*ZtrResult, error) {
+    if gcUrl == "" {
+        return nil, fmt.Errorf("ground control URL cannot be empty")
+    }
     token := os.Getenv("TOKEN")
+    if token == "" {
+        return nil, fmt.Errorf("TOKEN environment variable is not set")
+    }

     gcUrl = fmt.Sprintf("%s/satellites/ztr/%s", gcUrl, token)
-    resp, err := http.Get(gcUrl)
+    client := &http.Client{}
+    req, err := http.NewRequest("GET", gcUrl, nil)
+    if err != nil {
+        return nil, fmt.Errorf("failed to create request: %v", err)
+    }
+    resp, err := client.Do(req)
     if err != nil {
         return nil, fmt.Errorf("failed to fetch data from Ground Control: %v", err)
     }
     defer resp.Body.Close()
+
+    // Limit response size to prevent memory exhaustion
+    const maxSize = 1 << 20 // 1 MB
+    resp.Body = http.MaxBytesReader(nil, resp.Body, maxSize)
📝 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 ztr(gcUrl string) (*ZtrResult, error) {
// Get environment variables
token := os.Getenv("TOKEN")
// Construct the request URL
gcUrl = fmt.Sprintf("%s/satellites/ztr/%s", gcUrl, token)
// Make the GET request
resp, err := http.Get(gcUrl)
if err != nil {
return nil, fmt.Errorf("failed to fetch data from Ground Control: %v", err)
}
defer resp.Body.Close()
func ztr(gcUrl string) (*ZtrResult, error) {
if gcUrl == "" {
return nil, fmt.Errorf("ground control URL cannot be empty")
}
token := os.Getenv("TOKEN")
if token == "" {
return nil, fmt.Errorf("TOKEN environment variable is not set")
}
gcUrl = fmt.Sprintf("%s/satellites/ztr/%s", gcUrl, token)
client := &http.Client{}
req, err := http.NewRequest("GET", gcUrl, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to fetch data from Ground Control: %v", err)
}
defer resp.Body.Close()
// Limit response size to prevent memory exhaustion
const maxSize = 1 << 20 // 1 MB
resp.Body = http.MaxBytesReader(nil, resp.Body, maxSize)

Copy link

@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: 6

🧹 Outside diff range and nitpick comments (1)
ground-control/internal/server/handlers.go (1)

458-471: Add pagination to satellite listing.

The current implementation loads all satellites at once, which could cause performance issues with a large number of satellites.

Add pagination support:

type ListSatellitesParams struct {
    Limit  int32 `json:"limit"`
    Offset int32 `json:"offset"`
}

func (s *Server) listSatelliteHandler(w http.ResponseWriter, r *http.Request) {
    params := ListSatellitesParams{
        Limit:  20,
        Offset: 0,
    }
    
    if limit := r.URL.Query().Get("limit"); limit != "" {
        if l, err := strconv.ParseInt(limit, 10, 32); err == nil {
            params.Limit = int32(l)
        }
    }
    
    if offset := r.URL.Query().Get("offset"); offset != "" {
        if o, err := strconv.ParseInt(offset, 10, 32); err == nil {
            params.Offset = int32(o)
        }
    }
    
    result, total, err := s.dbQueries.ListSatellitesPaginated(r.Context(), params)
    if err != nil {
        HandleAppError(w, err)
        return
    }
    
    response := struct {
        Items []database.Satellite `json:"items"`
        Total int                 `json:"total"`
    }{
        Items: result,
        Total: total,
    }
    
    WriteJSONResponse(w, http.StatusOK, response)
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ca26bdd and 7dbfc0f.

📒 Files selected for processing (2)
  • ground-control/internal/server/handlers.go (4 hunks)
  • ground-control/internal/utils/helper.go (1 hunks)
🔇 Additional comments (9)
ground-control/internal/utils/helper.go (5)

117-121: 🛠️ Refactor suggestion

Improve URL handling

The current URL handling is fragile and could be improved:

Consider using the url package for proper URL parsing:

-	destinationRepo := fmt.Sprintf("%s/%s/%s", stateArtifact.Registry, repo, "state")
-	if strings.Contains(destinationRepo, "://") {
-		destinationRepo = strings.SplitN(destinationRepo, "://", 2)[1]
-	}
+	// Parse and normalize the registry URL
+	u, err := url.Parse(stateArtifact.Registry)
+	if err != nil {
+		return fmt.Errorf("invalid registry URL: %w", err)
+	}
+	// Use only the host part of the URL
+	destinationRepo := fmt.Sprintf("%s/%s/%s", u.Host, repo, "state")

Likely invalid or redundant comment.


42-51: ⚠️ Potential issue

Add input validation and improve error context

The function needs input validation and better error context:

Apply these improvements:

func CreateRobotAccForSatellite(ctx context.Context, projects []string, name string) (*models.RobotCreated, error) {
+	if len(projects) == 0 {
+		return nil, fmt.Errorf("projects list cannot be empty")
+	}
+	if name == "" {
+		return nil, fmt.Errorf("robot name cannot be empty")
+	}
+	if ctx == nil {
+		return nil, fmt.Errorf("context cannot be nil")
+	}

 	robotTemp := harbor.RobotAccountTemplate(name, projects)
 	robt, err := harbor.CreateRobotAccount(ctx, robotTemp)
 	if err != nil {
-		return nil, fmt.Errorf("error creating robot account: %w", err)
+		return nil, fmt.Errorf("failed to create robot account for %q: %w", name, err)
 	}

 	return robt.Payload, nil
}

Likely invalid or redundant comment.


128-134: ⚠️ Potential issue

Implement atomic tag updates

The current implementation of tag updates is not atomic and could lead to race conditions:

Apply these improvements:

-	// Tag the image with timestamp and latest tags
-	tags := []string{fmt.Sprintf("%d", time.Now().Unix()), "latest"}
-	for _, tag := range tags {
-		if err := crane.Tag(destinationRepo, tag, options...); err != nil {
-			return fmt.Errorf("failed to tag image with %s: %v", tag, err)
-		}
-	}
+	// Generate unique timestamp for atomic operation
+	timestamp := fmt.Sprintf("%d", time.Now().UnixNano())
+	timestampTag := fmt.Sprintf("%s:%s", destinationRepo, timestamp)
+
+	// Tag with timestamp first
+	if err := crane.Tag(destinationRepo, timestamp, options...); err != nil {
+		return fmt.Errorf("failed to tag image with timestamp: %w", err)
+	}
+
+	// Atomically update latest tag
+	if err := crane.Tag(timestampTag, destinationRepo+":latest", options...); err != nil {
+		// Cleanup on failure
+		_ = crane.Delete(timestampTag, options...)
+		return fmt.Errorf("failed to update latest tag: %w", err)
+	}

Likely invalid or redundant comment.


78-81: ⚠️ Potential issue

Add environment and input validation

The function needs validation for both environment variables and input:

Apply these improvements:

-func AssembleGroupState(groupName string) string {
+func AssembleGroupState(groupName string) (string, error) {
+	if groupName == "" {
+		return "", fmt.Errorf("group name cannot be empty")
+	}
+
+	harborURL := os.Getenv("HARBOR_URL")
+	if harborURL == "" {
+		return "", fmt.Errorf("HARBOR_URL environment variable is not set")
+	}
+
+	// Validate group name format to prevent path traversal
+	if strings.Contains(groupName, "..") || strings.Contains(groupName, "/") {
+		return "", fmt.Errorf("invalid group name format: %s", groupName)
+	}
+
-	state := fmt.Sprintf("%s/satellite/%s/state:latest", os.Getenv("HARBOR_URL"), groupName)
-	return state
+	state := fmt.Sprintf("%s/satellite/%s/state:latest", harborURL, groupName)
+	return state, nil
}

Likely invalid or redundant comment.


83-95: ⚠️ Potential issue

Add input validation and improve error handling

The function needs input validation and better error messages:

Apply these improvements:

func CreateStateArtifact(stateArtifact *m.StateArtifact) error {
+	if stateArtifact == nil {
+		return fmt.Errorf("state artifact cannot be nil")
+	}
+	if stateArtifact.Group == "" {
+		return fmt.Errorf("group name cannot be empty")
+	}

 	// Set the registry URL from environment variable
 	stateArtifact.Registry = os.Getenv("HARBOR_URL")
 	if stateArtifact.Registry == "" {
 		return fmt.Errorf("HARBOR_URL environment variable is not set")
 	}

 	// Marshal the state artifact to JSON format
 	data, err := json.Marshal(stateArtifact)
 	if err != nil {
-		return fmt.Errorf("failed to marshal state artifact to JSON: %v", err)
+		return fmt.Errorf("failed to marshal state artifact for group %q: %w", stateArtifact.Group, err)
 	}

Likely invalid or redundant comment.

ground-control/internal/server/handlers.go (4)

21-24: Remove unnecessary pointer to slice in RegisterSatelliteParams.

The Groups field is declared as a pointer to slice (*[]string), but slices in Go are already reference types. Using a pointer to a slice adds unnecessary complexity.


374-397: Improve token validation security.

The token is deleted before verifying the complete registration chain, which could lead to orphaned satellites if subsequent operations fail.


768-779: Improve token generation security.

The function should handle odd character lengths correctly and validate input.


83-88: ⚠️ Potential issue

Add validation for empty projects list.

The handler should validate that the projects list is not empty before creating a group.

projects := utils.GetProjectNames(&req.Artifacts)
+if len(projects) == 0 {
+    HandleAppError(w, &AppError{
+        Message: "No valid projects found in artifacts",
+        Code:    http.StatusBadRequest,
+    })
+    return
+}

Likely invalid or redundant comment.

Comment on lines +20 to +40
// GetProjectNames parses artifacts & returns project names
func GetProjectNames(artifacts *[]m.Artifact) []string {
uniqueProjects := make(map[string]struct{}) // Map to track unique project names
var projects []string

for _, artifact := range *artifacts {
if artifact.Deleted {
continue
}
// Extract project name from repository
project := strings.Split(artifact.Repository, "/")[0]

// Check if the project is already added
if _, exists := uniqueProjects[project]; !exists {
uniqueProjects[project] = struct{}{}
projects = append(projects, project)
}
}

return projects
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input validation and improve error handling

The function needs several improvements for robustness and efficiency:

  1. Missing nil check for input parameter
  2. Potential panic when splitting malformed repository paths
  3. Inefficient storage using both map and slice
  4. No error handling for invalid repository formats

Consider this improved implementation:

-func GetProjectNames(artifacts *[]m.Artifact) []string {
+func GetProjectNames(artifacts *[]m.Artifact) ([]string, error) {
+	if artifacts == nil {
+		return nil, fmt.Errorf("artifacts cannot be nil")
+	}
+
 	uniqueProjects := make(map[string]struct{})
-	var projects []string
 
 	for _, artifact := range *artifacts {
 		if artifact.Deleted {
 			continue
 		}
-		project := strings.Split(artifact.Repository, "/")[0]
+		parts := strings.SplitN(artifact.Repository, "/", 2)
+		if len(parts) == 0 {
+			return nil, fmt.Errorf("invalid repository format: %s", artifact.Repository)
+		}
+		project := parts[0]
 		if _, exists := uniqueProjects[project]; !exists {
 			uniqueProjects[project] = struct{}{}
-			projects = append(projects, project)
 		}
 	}
 
-	return projects
+	projects := make([]string, 0, len(uniqueProjects))
+	for project := range uniqueProjects {
+		projects = append(projects, project)
+	}
+	return projects, nil
}
📝 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
// GetProjectNames parses artifacts & returns project names
func GetProjectNames(artifacts *[]m.Artifact) []string {
uniqueProjects := make(map[string]struct{}) // Map to track unique project names
var projects []string
for _, artifact := range *artifacts {
if artifact.Deleted {
continue
}
// Extract project name from repository
project := strings.Split(artifact.Repository, "/")[0]
// Check if the project is already added
if _, exists := uniqueProjects[project]; !exists {
uniqueProjects[project] = struct{}{}
projects = append(projects, project)
}
}
return projects
}
// GetProjectNames parses artifacts & returns project names
func GetProjectNames(artifacts *[]m.Artifact) ([]string, error) {
if artifacts == nil {
return nil, fmt.Errorf("artifacts cannot be nil")
}
uniqueProjects := make(map[string]struct{}) // Map to track unique project names
for _, artifact := range *artifacts {
if artifact.Deleted {
continue
}
// Extract project name from repository
parts := strings.SplitN(artifact.Repository, "/", 2)
if len(parts) == 0 {
return nil, fmt.Errorf("invalid repository format: %s", artifact.Repository)
}
project := parts[0]
if _, exists := uniqueProjects[project]; !exists {
uniqueProjects[project] = struct{}{}
}
}
projects := make([]string, 0, len(uniqueProjects))
for project := range uniqueProjects {
projects = append(projects, project)
}
return projects, nil
}

Comment on lines +53 to +76
// Update robot account
func UpdateRobotProjects(ctx context.Context, projects []string, id string) (*robot.UpdateRobotOK, error) {
ID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
return nil, fmt.Errorf("error invalid ID: %w", err)
}
robot, err := harbor.GetRobotAccount(ctx, ID)
if err != nil {
return nil, fmt.Errorf("error getting robot account: %w", err)
}

// satellites should always have permission to satellite project by default
// to get state artifacts
projects = append(projects, "satellite")

robot.Permissions = harbor.GenRobotPerms(projects)

updated, err := harbor.UpdateRobotAccount(ctx, robot)
if err != nil {
return nil, fmt.Errorf("error updating robot account: %w", err)
}

return updated, nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input validation and clarify satellite project addition

The function has several areas for improvement:

  1. Missing input validation
  2. Implicit behavior of adding "satellite" project
  3. Error messages could be more descriptive

Consider these improvements:

-func UpdateRobotProjects(ctx context.Context, projects []string, id string) (*robot.UpdateRobotOK, error) {
+func UpdateRobotProjectsWithSatellite(ctx context.Context, projects []string, id string) (*robot.UpdateRobotOK, error) {
+	if ctx == nil {
+		return nil, fmt.Errorf("context cannot be nil")
+	}
+	if len(projects) == 0 {
+		return nil, fmt.Errorf("projects list cannot be empty")
+	}
+	if id == "" {
+		return nil, fmt.Errorf("robot ID cannot be empty")
+	}

 	ID, err := strconv.ParseInt(id, 10, 64)
 	if err != nil {
-		return nil, fmt.Errorf("error invalid ID: %w", err)
+		return nil, fmt.Errorf("invalid robot ID %q: %w", id, err)
 	}
 	robot, err := harbor.GetRobotAccount(ctx, ID)
 	if err != nil {
-		return nil, fmt.Errorf("error getting robot account: %w", err)
+		return nil, fmt.Errorf("failed to get robot account with ID %d: %w", ID, err)
 	}

 	// satellites should always have permission to satellite project by default
 	// to get state artifacts
 	projects = append(projects, "satellite")

 	robot.Permissions = harbor.GenRobotPerms(projects)

 	updated, err := harbor.UpdateRobotAccount(ctx, robot)
 	if err != nil {
-		return nil, fmt.Errorf("error updating robot account: %w", err)
+		return nil, fmt.Errorf("failed to update robot account %d with projects %v: %w", ID, projects, err)
 	}

 	return updated, nil
}
📝 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
// Update robot account
func UpdateRobotProjects(ctx context.Context, projects []string, id string) (*robot.UpdateRobotOK, error) {
ID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
return nil, fmt.Errorf("error invalid ID: %w", err)
}
robot, err := harbor.GetRobotAccount(ctx, ID)
if err != nil {
return nil, fmt.Errorf("error getting robot account: %w", err)
}
// satellites should always have permission to satellite project by default
// to get state artifacts
projects = append(projects, "satellite")
robot.Permissions = harbor.GenRobotPerms(projects)
updated, err := harbor.UpdateRobotAccount(ctx, robot)
if err != nil {
return nil, fmt.Errorf("error updating robot account: %w", err)
}
return updated, nil
}
// Update robot account
func UpdateRobotProjectsWithSatellite(ctx context.Context, projects []string, id string) (*robot.UpdateRobotOK, error) {
if ctx == nil {
return nil, fmt.Errorf("context cannot be nil")
}
if len(projects) == 0 {
return nil, fmt.Errorf("projects list cannot be empty")
}
if id == "" {
return nil, fmt.Errorf("robot ID cannot be empty")
}
ID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid robot ID %q: %w", id, err)
}
robot, err := harbor.GetRobotAccount(ctx, ID)
if err != nil {
return nil, fmt.Errorf("failed to get robot account with ID %d: %w", ID, err)
}
// satellites should always have permission to satellite project by default
// to get state artifacts
projects = append(projects, "satellite")
robot.Permissions = harbor.GenRobotPerms(projects)
updated, err := harbor.UpdateRobotAccount(ctx, robot)
if err != nil {
return nil, fmt.Errorf("failed to update robot account %d with projects %v: %w", ID, projects, err)
}
return updated, nil
}

Comment on lines +65 to +82
// Start a new transaction
tx, err := s.db.BeginTx(r.Context(), nil)
if err != nil {
log.Println(err)
HandleAppError(w, err)
return
}
// Create a new Queries object bound to the transaction
q := s.dbQueries.WithTx(tx)
// Ensure proper transaction handling with defer
defer func() {
if p := recover(); p != nil {
// If there's a panic, rollback the transaction
tx.Rollback()
} else if err != nil {
tx.Rollback() // Rollback transaction on error
}
}()
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract transaction management into a helper function.

Transaction management code is duplicated across multiple handlers. This makes the code harder to maintain and increases the risk of inconsistent transaction handling.

Create a helper function:

func (s *Server) withTransaction(ctx context.Context, fn func(*database.Queries) error) error {
    tx, err := s.db.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    
    q := s.dbQueries.WithTx(tx)
    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p)
        } else if err != nil {
            tx.Rollback()
        }
    }()
    
    if err = fn(q); err != nil {
        return err
    }
    
    return tx.Commit()
}

Comment on lines +269 to +293
// check if project satellite exists and if does not exist create project satellite
satExist, err := harbor.GetProject(r.Context(), "satellite")
if err != nil {
log.Println(err)
err := &AppError{
Message: fmt.Sprintf("Error: Checking satellite project: %v", err),
Code: http.StatusBadGateway,
}
HandleAppError(w, err)
tx.Rollback()
return
}
if !satExist {
_, err := harbor.CreateSatelliteProject(r.Context())
if err != nil {
log.Println(err)
err := &AppError{
Message: fmt.Sprintf("Error: creating satellite project: %v", err),
Code: http.StatusBadGateway,
}
HandleAppError(w, err)
tx.Rollback()
return
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract satellite project creation logic into a helper function.

This code block is duplicated in both groupsSyncHandler and registerSatelliteHandler.

Create a helper function:

func (s *Server) ensureSatelliteProjectExists(ctx context.Context) error {
    satExist, err := harbor.GetProject(ctx, "satellite")
    if err != nil {
        return fmt.Errorf("checking satellite project: %w", err)
    }
    
    if !satExist {
        if _, err := harbor.CreateSatelliteProject(ctx); err != nil {
            return fmt.Errorf("creating satellite project: %w", err)
        }
    }
    return nil
}

Comment on lines +228 to +266
for _, groupName := range *req.Groups {
// check if groups are declared in replication
replications, err := harbor.ListReplication(r.Context(), harbor.ListParams{
Q: fmt.Sprintf("name=%s", groupName),
})
if len(replications) < 1 {
if err != nil {
log.Println(err)
err := &AppError{
Message: fmt.Sprintf("Error: Group Name: %s, does not exist in replication, Please give a Valid Group Name", groupName),
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
tx.Rollback()
return
}
}
group, err := q.GetGroupByName(r.Context(), groupName)
if err != nil {
log.Println(err)
err := &AppError{
Message: fmt.Sprintf("Error: Invalid Group Name: %v", groupName),
Code: http.StatusBadRequest,
}
HandleAppError(w, err)
tx.Rollback()
return
}
err = q.AddSatelliteToGroup(r.Context(), database.AddSatelliteToGroupParams{
SatelliteID: satellite.ID,
GroupID: group.ID,
})
if err != nil {
log.Println(err)
HandleAppError(w, err)
tx.Rollback()
return
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize group validation to reduce API calls.

The current implementation makes multiple API calls within a loop. Consider validating all groups upfront to improve performance.

func (s *Server) validateGroups(ctx context.Context, q *database.Queries, groupNames []string) error {
    // Validate all groups exist in replication first
    replicationFilter := strings.Join(groupNames, "|")
    replications, err := harbor.ListReplication(ctx, harbor.ListParams{
        Q: fmt.Sprintf("name=~(%s)", replicationFilter),
    })
    if err != nil {
        return err
    }
    
    validReplications := make(map[string]bool)
    for _, r := range replications {
        validReplications[r.Name] = true
    }
    
    for _, name := range groupNames {
        if !validReplications[name] {
            return fmt.Errorf("group %s does not exist in replication", name)
        }
    }
    return nil
}

Comment on lines +59 to +60
log.Println("what the fuck")
log.Println(err)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove inappropriate debug log message.

The debug log message contains inappropriate language and should be removed or replaced with a professional message.

-    log.Println("what the fuck")
+    log.Println("error decoding request body")
📝 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
log.Println("what the fuck")
log.Println(err)
log.Println("error decoding request body")
log.Println(err)

Signed-off-by: bupd <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement ZTR for satellites zero touch device registration
1 participant