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

Generrates Containerd config.toml for harbor satellite #59

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b0304ff
adding state artifact fetcher to fetch the state from harbor
Mehul-Kumar-27 Sep 23, 2024
8a5ba11
completing the url fetcher
Mehul-Kumar-27 Sep 25, 2024
c291e6a
adding schedulers and process to satellite
Mehul-Kumar-27 Sep 29, 2024
fe299f8
adding simple notifier to fetch state process
Mehul-Kumar-27 Sep 29, 2024
b6b5dea
added description to the scheduler
Mehul-Kumar-27 Sep 29, 2024
91222d4
coderabbit fixes and changes to fetcher and schedulers
Mehul-Kumar-27 Sep 29, 2024
09419ce
adding new format of the state file
Mehul-Kumar-27 Oct 2, 2024
c60f7a4
adding config to process new state artifact file
Mehul-Kumar-27 Oct 8, 2024
18b5633
Merge branch 'main' into satellite-state-fetcher
Mehul-Kumar-27 Oct 8, 2024
dec1ba0
coderabbit review
Mehul-Kumar-27 Oct 8, 2024
ef0d82a
added ./zot to gitignore
Mehul-Kumar-27 Oct 8, 2024
74fc4b9
fixing the replication process
Mehul-Kumar-27 Oct 10, 2024
3d0e209
fixing the replication and deletion process
Mehul-Kumar-27 Oct 12, 2024
6e54a14
fixing paning while removing the null tags
Mehul-Kumar-27 Oct 13, 2024
8904e99
using repository name instead of the image name while uploading the i…
Mehul-Kumar-27 Oct 16, 2024
d14af7e
adding container runtime config
Mehul-Kumar-27 Oct 20, 2024
7254c1b
containerd function and changing the harbor satellite to a cobra cli …
Mehul-Kumar-27 Oct 21, 2024
d111454
generating config file for containerd
Mehul-Kumar-27 Oct 21, 2024
222c666
adding better logging
Mehul-Kumar-27 Oct 21, 2024
d2180bf
fix
Mehul-Kumar-27 Oct 21, 2024
fcb7426
adding containerd config to gitignore
Mehul-Kumar-27 Oct 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ ZOT_URL="127.0.0.1:8585"
TOKEN=""
ENV=dev
USE_UNSECURE=true
GROUP_NAME=satellite-test-group-state
STATE_ARTIFACT_NAME=state
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ dist/
zot/cache.db
secrets.txt
__debug_bin1949266242

/zot
runtime/containerd/config.toml
2 changes: 0 additions & 2 deletions ci/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ func (m *HarborSatellite) Service(
AsService()
}


// builds given component from source

func (m *HarborSatellite) build(source *dagger.Directory, component string) *dagger.Directory {
fmt.Printf("Building %s\n", component)

Expand Down
126 changes: 126 additions & 0 deletions cmd/container_runtime/containerd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package runtime

import (
"fmt"
"os"
"path/filepath"

"container-registry.com/harbor-satellite/internal/config"
"container-registry.com/harbor-satellite/internal/utils"
"container-registry.com/harbor-satellite/logger"
"container-registry.com/harbor-satellite/registry"
containerd "github.com/containerd/containerd/pkg/cri/config"
toml "github.com/pelletier/go-toml"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
)

const (
ContainerDCertPath = "/etc/containerd/certs.d"
DefaultGeneratedTomlName = "config.toml"
)

var DefaultGenPath string

func init() {
cwd, err := os.Getwd()
if err != nil {
fmt.Printf("Error getting current working directory: %v\n", err)
DefaultGenPath = "/runtime/containerd" // Fallback in case of error
} else {
DefaultGenPath = filepath.Join(cwd, "runtime/containerd")
}
}
Comment on lines +25 to +33
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 DefaultGenPath is valid and writable

In the init() function, if os.Getwd() fails, DefaultGenPath is set to "/runtime/containerd". This fallback path may not exist or might not be writable, potentially causing issues when attempting to write the configuration file later.

Consider verifying the existence and writability of DefaultGenPath after setting it:

 if err != nil {
     fmt.Printf("Error getting current working directory: %v\n", err)
     DefaultGenPath = "/runtime/containerd" // Fallback in case of error
+    // Ensure the fallback path exists and is writable
+    if _, err := os.Stat(DefaultGenPath); os.IsNotExist(err) {
+        err := os.MkdirAll(DefaultGenPath, os.ModePerm)
+        if err != nil {
+            fmt.Printf("Error creating fallback directory: %v\n", err)
+        }
+    }
 } else {
     DefaultGenPath = filepath.Join(cwd, "runtime/containerd")
 }

Committable suggestion was skipped due to low confidence.


func NewContainerdCommand() *cobra.Command {
var generateConfig bool
var defaultZotConfig *registry.DefaultZotConfig

Comment on lines +37 to +38
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Initialize defaultZotConfig to prevent nil pointer dereference

The variable defaultZotConfig is declared but not initialized. If config.GetOwnRegistry() returns true, defaultZotConfig remains nil, leading to a nil pointer dereference when accessing its fields in lines 54-55.

Initialize defaultZotConfig before it's used:

 var generateConfig bool
-var defaultZotConfig *registry.DefaultZotConfig
+var defaultZotConfig = &registry.DefaultZotConfig{}
📝 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
var defaultZotConfig *registry.DefaultZotConfig
var defaultZotConfig = &registry.DefaultZotConfig{}

containerdCmd := &cobra.Command{
Use: "containerd",
Short: "Creates the config file for the containerd runtime to fetch the images from the local repository",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
var err error
utils.SetupContextForCommand(cmd)
log := logger.FromContext(cmd.Context())
if config.GetOwnRegistry() {
log.Info().Msg("Using own registry for config generation")
address, err := utils.ValidateRegistryAddress(config.GetOwnRegistryAdr(), config.GetOwnRegistryPort())
if err != nil {
log.Err(err).Msg("Error validating registry address")
return err
}
log.Info().Msgf("Registry address validated: %s", address)
defaultZotConfig.HTTP.Address = config.GetOwnRegistryAdr()
defaultZotConfig.HTTP.Port = config.GetOwnRegistryPort()
Comment on lines +54 to +55
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent nil pointer dereference when setting defaultZotConfig fields

As defaultZotConfig might be nil when config.GetOwnRegistry() is true, accessing its fields will cause a runtime panic.

Ensure defaultZotConfig is properly initialized before these lines. Refer to the previous comment for initialization.

} else {
log.Info().Msg("Using default registry for config generation")
defaultZotConfig, err = registry.ReadConfig(config.GetZotConfigPath())
if err != nil {
return fmt.Errorf("could not read config: %w", err)
}
log.Info().Msgf("Default config read successfully: %v", defaultZotConfig.HTTP.Address+":"+defaultZotConfig.HTTP.Port)
Comment on lines +58 to +62
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 potential error when reading default Zot configuration

In the else block, if registry.ReadConfig(config.GetZotConfigPath()) fails, defaultZotConfig might not be properly set, leading to issues later when it's used.

Ensure that defaultZotConfig is only used after confirming that err is nil:

 defaultZotConfig, err = registry.ReadConfig(config.GetZotConfigPath())
 if err != nil {
     return fmt.Errorf("could not read config: %w", err)
 }
+if defaultZotConfig == nil {
+    return fmt.Errorf("defaultZotConfig is nil after reading config")
+}
 log.Info().Msgf("Default config read successfully: %v", defaultZotConfig.HTTP.Address+":"+defaultZotConfig.HTTP.Port)
📝 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
defaultZotConfig, err = registry.ReadConfig(config.GetZotConfigPath())
if err != nil {
return fmt.Errorf("could not read config: %w", err)
}
log.Info().Msgf("Default config read successfully: %v", defaultZotConfig.HTTP.Address+":"+defaultZotConfig.HTTP.Port)
defaultZotConfig, err = registry.ReadConfig(config.GetZotConfigPath())
if err != nil {
return fmt.Errorf("could not read config: %w", err)
}
if defaultZotConfig == nil {
return fmt.Errorf("defaultZotConfig is nil after reading config")
}
log.Info().Msgf("Default config read successfully: %v", defaultZotConfig.HTTP.Address+":"+defaultZotConfig.HTTP.Port)

}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
log := logger.FromContext(cmd.Context())
if !generateConfig {
return nil
}
log.Info().Msg("Generating containerd config file for containerd ...")
return GenerateContainerdConfig(defaultZotConfig, log)
},
}

containerdCmd.Flags().BoolVarP(&generateConfig, "gen", "g", false, "Generate the containerd config file")

return containerdCmd
}

func GenerateContainerdConfig(defaultZotConfig *registry.DefaultZotConfig, log *zerolog.Logger) error {
containerdConfig := containerd.Config{}
containerdConfig.PluginConfig = containerd.DefaultConfig()
containerdConfig.PluginConfig.Registry.ConfigPath = ContainerDCertPath

registryMirror := map[string]containerd.Mirror{
defaultZotConfig.HTTP.Address: {
Endpoints: []string{defaultZotConfig.HTTP.Address + ":" + defaultZotConfig.HTTP.Port},
},
}

registryConfig := map[string]containerd.RegistryConfig{
defaultZotConfig.HTTP.Address: {
TLS: &containerd.TLSConfig{
InsecureSkipVerify: config.UseUnsecure(),
},
},
}

containerdConfig.PluginConfig.Registry.Mirrors = registryMirror
containerdConfig.PluginConfig.Registry.Configs = registryConfig

generatedConfig, err := toml.Marshal(containerdConfig)
if err != nil {
log.Error().Err(err).Msg("Error marshalling config")
return fmt.Errorf("could not marshal config: %w", err)
}

filePath := filepath.Join(DefaultGenPath, DefaultGeneratedTomlName)
log.Info().Msgf("Writing config to file: %s", filePath)
file, err := os.Create(filePath)
if err != nil {
log.Err(err).Msg("Error creating file")
return fmt.Errorf("could not create file: %w", err)
}
defer file.Close()
Comment on lines +111 to +116
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle file closing errors and use deferred error checking

When using defer file.Close(), any error returned by file.Close() will be ignored. This might silently mask I/O errors.

Instead of deferring file.Close(), check the error explicitly:

 file, err := os.Create(filePath)
 if err != nil {
     log.Err(err).Msg("Error creating file")
     return fmt.Errorf("could not create file: %w", err)
 }
-defer file.Close()
+defer func() {
+    if err := file.Close(); err != nil {
+        log.Err(err).Msg("Error closing file")
+    }
+}()
📝 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
file, err := os.Create(filePath)
if err != nil {
log.Err(err).Msg("Error creating file")
return fmt.Errorf("could not create file: %w", err)
}
defer file.Close()
file, err := os.Create(filePath)
if err != nil {
log.Err(err).Msg("Error creating file")
return fmt.Errorf("could not create file: %w", err)
}
defer func() {
if err := file.Close(); err != nil {
log.Err(err).Msg("Error closing file")
}
}()


_, err = file.Write(generatedConfig)
if err != nil {
log.Err(err).Msg("Error writing to file")
return fmt.Errorf("could not write to file: %w", err)
}

log.Info().Msgf("Config file generated successfully at: %s", filePath)
return nil
}
47 changes: 47 additions & 0 deletions cmd/container_runtime/read_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package runtime

import (
"fmt"
"path/filepath"

"container-registry.com/harbor-satellite/internal/utils"
"container-registry.com/harbor-satellite/logger"
"github.com/spf13/cobra"
)

var (
DefaultContainerdConfigPath string = filepath.Join("/", "etc/containerd/config.toml")
)

func NewReadConfigCommand(runtime string) *cobra.Command {
var defaultPath string
switch runtime {
case "containerd":
defaultPath = DefaultContainerdConfigPath
default:
defaultPath = ""
}
readContainerdConfig := &cobra.Command{
Use: "read",
Short: fmt.Sprintf("Reads the config file for the %s runtime", runtime),
PersistentPreRun: func(cmd *cobra.Command, args []string) {
utils.SetupContextForCommand(cmd)
},
RunE: func(cmd *cobra.Command, args []string) error {
//Parse the flags
path, err := cmd.Flags().GetString("path")
if err != nil {
return fmt.Errorf("error reading the path flag: %v", err)
}
log := logger.FromContext(cmd.Context())
log.Info().Msgf("Reading the containerd config file from path: %s", path)
err = utils.ReadFile(path)
if err != nil {
return fmt.Errorf("error reading the containerd config file: %v", err)
}
return nil
},
}
readContainerdConfig.Flags().StringP("path", "p", defaultPath, "Path to the containerd config file of the container runtime")
return readContainerdConfig
}
160 changes: 160 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package cmd

import (
"context"
"fmt"

runtime "container-registry.com/harbor-satellite/cmd/container_runtime"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid aliasing imports as standard library package names to prevent confusion

The alias runtime may cause confusion with Go's standard library runtime package. It's advisable to use a different alias to prevent potential misunderstandings and improve code clarity.

Apply this diff to change the alias:

-import runtime "container-registry.com/harbor-satellite/cmd/container_runtime"
+import containerRuntime "container-registry.com/harbor-satellite/cmd/container_runtime"
📝 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
runtime "container-registry.com/harbor-satellite/cmd/container_runtime"
containerRuntime "container-registry.com/harbor-satellite/cmd/container_runtime"

"container-registry.com/harbor-satellite/internal/config"
"container-registry.com/harbor-satellite/internal/satellite"
"container-registry.com/harbor-satellite/internal/scheduler"
"container-registry.com/harbor-satellite/internal/server"
"container-registry.com/harbor-satellite/internal/state"
"container-registry.com/harbor-satellite/internal/utils"
"container-registry.com/harbor-satellite/logger"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)

func NewRootCommand() *cobra.Command {
rootCmd := &cobra.Command{
Use: "harbor-satellite",
Short: "Harbor Satellite is a tool to replicate images from source registry to Harbor registry",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
config.InitConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
ctx, cancel := utils.SetupContext(ctx)
ctx = logger.AddLoggerToContext(ctx, config.GetLogLevel())
return run(ctx, cancel)
},
}
rootCmd.AddCommand(runtime.NewContainerdCommand())
return rootCmd
}

func Execute() error {
return NewRootCommand().Execute()
}

func run(ctx context.Context, cancel context.CancelFunc) error {
g, ctx := errgroup.WithContext(ctx)
log := logger.FromContext(ctx)

// Set up router and app
app := setupServerApp(ctx, log)
app.SetupRoutes()
app.SetupServer(g)

// Handle registry setup
if err := handleRegistrySetup(g, log, cancel); err != nil {
return err
}
scheduler := scheduler.NewBasicScheduler(ctx)
ctx = context.WithValue(ctx, scheduler.GetSchedulerKey(), scheduler)
err := scheduler.Start()
if err != nil {
log.Error().Err(err).Msg("Error starting scheduler")
return err
}
// Process Input (file or URL)
stateArtifactFetcher, err := processInput(ctx, log)
if err != nil || stateArtifactFetcher == nil {
return fmt.Errorf("error processing input: %w", err)
}
Comment on lines +64 to +66
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 nil error when wrapping errors to prevent incorrect error messages

If err is nil but stateArtifactFetcher is nil, wrapping nil with %w in fmt.Errorf("error processing input: %w", err) will result in an incorrect error message. To ensure accurate error reporting, check if err is nil before wrapping or provide a default error message when stateArtifactFetcher is nil.

Apply this diff to fix the error handling:

-if err != nil || stateArtifactFetcher == nil {
-    return fmt.Errorf("error processing input: %w", err)
+if err != nil {
+    return fmt.Errorf("error processing input: %w", err)
+}
+if stateArtifactFetcher == nil {
+    return fmt.Errorf("stateArtifactFetcher is 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
if err != nil || stateArtifactFetcher == nil {
return fmt.Errorf("error processing input: %w", err)
}
if err != nil {
return fmt.Errorf("error processing input: %w", err)
}
if stateArtifactFetcher == nil {
return fmt.Errorf("stateArtifactFetcher is nil")
}


satelliteService := satellite.NewSatellite(ctx, stateArtifactFetcher, scheduler.GetSchedulerKey())

g.Go(func() error {
return satelliteService.Run(ctx)
})

log.Info().Msg("Startup complete 🚀")
return g.Wait()
}

func initConfig() error {
if err := config.InitConfig(); err != nil {
return fmt.Errorf("error initializing config: %w", err)
}
return nil
}

func setupServerApp(ctx context.Context, log *zerolog.Logger) *server.App {
router := server.NewDefaultRouter("/api/v1")
router.Use(server.LoggingMiddleware)

return server.NewApp(
router,
ctx,
log,
config.AppConfig,
&server.MetricsRegistrar{},
&server.DebugRegistrar{},
&satellite.SatelliteRegistrar{},
)
}

func handleRegistrySetup(g *errgroup.Group, log *zerolog.Logger, cancel context.CancelFunc) error {
if config.GetOwnRegistry() {
if err := utils.HandleOwnRegistry(); err != nil {
log.Error().Err(err).Msg("Error handling own registry")
return err
}
} else {
log.Info().Msg("Launching default registry")
g.Go(func() error {
if err := utils.LaunchDefaultZotRegistry(); err != nil {
log.Error().Err(err).Msg("Error launching default registry")
cancel()
return err
}
cancel()
return nil
})
}
return nil
}

func processInput(ctx context.Context, log *zerolog.Logger) (state.StateFetcher, error) {
input := config.GetInput()

if utils.IsValidURL(input) {
return processURLInput(input, log)
}

log.Info().Msg("Input is not a valid URL, checking if it is a file path")
if err := validateFilePath(input, log); err != nil {
return nil, err
}

return processFileInput(log)
}

func processURLInput(input string, log *zerolog.Logger) (state.StateFetcher, error) {
log.Info().Msg("Input is a valid URL")
config.SetRemoteRegistryURL(input)

stateArtifactFetcher := state.NewURLStateFetcher()

return stateArtifactFetcher, nil
}

func processFileInput(log *zerolog.Logger) (state.StateFetcher, error) {
stateArtifactFetcher := state.NewFileStateFetcher()
return stateArtifactFetcher, nil
}

func validateFilePath(path string, log *zerolog.Logger) error {
if utils.HasInvalidPathChars(path) {
log.Error().Msg("Path contains invalid characters")
return fmt.Errorf("invalid file path: %s", path)
}
if err := utils.GetAbsFilePath(path); err != nil {
log.Error().Err(err).Msg("No file found")
return fmt.Errorf("no file found: %s", path)
}
return nil
}
5 changes: 4 additions & 1 deletion config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ own_registry_adr = "127.0.0.1"
own_registry_port = "8585"

# URL of remote registry OR local file path
url_or_file = "https://demo.goharbor.io/v2/myproject/album-server"
# url_or_file = "https://demo.goharbor.io/v2/myproject/album-server"
url_or_file = "https://registry.bupd.xyz"
## for testing for local file
# url_or_file = "./image-list/images.json"

# Default path for Zot registry config.json
zotConfigPath = "./registry/config.json"
Expand Down
2 changes: 1 addition & 1 deletion dagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"name": "harbor-satellite",
"sdk": "go",
"source": "ci",
"engineVersion": "v0.13.3"
"engineVersion": "v0.13.0"
}
Loading