Skip to content

Commit

Permalink
Merge pull request #6 from aurora-is-near/container_name_config
Browse files Browse the repository at this point in the history
Add support for different service names and container names
  • Loading branch information
spilin authored Dec 19, 2024
2 parents 62d2846 + 3552be9 commit deb5a4b
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 61 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ supabaseRealtimeUrl: "wss://your-project.supabase.co/realtime/v1/websocket"
supabaseAnonKey: "your-anon-key"
pathToDockerCompose: "./config/docker-compose.yaml"
frontendServiceName: "frontend"
frontendContainerName: "frontend"
backendServiceName: "backend"
backendContainerName: "backend"
statsServiceName: "stats"
statsContainerName: "stats"
table: "silos"
chainId: 10
```
Expand Down Expand Up @@ -126,8 +129,11 @@ blockscout-vc/
| `supabaseAnonKey` | Supabase Anonymous Key | Yes |
| `pathToDockerCompose` | Path to the Docker Compose file | Yes |
| `frontendServiceName` | Name of the frontend service | Yes |
| `frontendContainerName` | Name of the frontend container | Yes |
| `backendServiceName` | Name of the backend service | Yes |
| `backendContainerName` | Name of the backend container | Yes |
| `statsServiceName` | Name of the stats service | Yes |
| `statsContainerName` | Name of the stats container | Yes |
| `table` | Name of the table to listen to | Yes |
| `chainId` | Chain ID to listen to | Yes |
Expand Down
3 changes: 3 additions & 0 deletions config/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ supabaseRealtimeUrl: "wss://localhost:5432/realtime/v1/websocket"
supabaseAnonKey: "replace-with-actual-anon-key"
pathToDockerCompose: "./config/docker-compose.yaml"
frontendServiceName: "frontend"
frontendContainerName: "frontend"
backendServiceName: "backend"
backendContainerName: "backend"
statsServiceName: "stats"
statsContainerName: "stats"
table: "silos"
chainId: "replace-with-actual-chain-id"
54 changes: 38 additions & 16 deletions internal/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,40 @@ func NewDocker() *Docker {
}
}

type Container struct {
Name string
ServiceName string
}

// RecreateContainers stops, removes and recreates specified containers
// It uses docker-compose to handle the container lifecycle
func (d *Docker) RecreateContainers(containerNames []string) error {
func (d *Docker) RecreateContainers(containers []Container) error {
pathToDockerCompose := viper.GetString("pathToDockerCompose")
uniqueContainers := d.UniqueContainerNames(containerNames)
uniqueContainers := d.UniqueContainers(containers)

// Define the sequence of commands to execute
containerNames := d.GetContainerNames(uniqueContainers)
serviceNames := d.GetServiceNames(uniqueContainers)
commands := []struct {
args []string
desc string
errMessage string
}{
{
args: []string{"rm", "-f"},
args: append([]string{"rm", "-f"}, containerNames...),
desc: "Stopping and removing containers",
errMessage: "Error stopping and removing containers",
},
{
args: []string{"compose", "-f", pathToDockerCompose, "up", "-d", "--force-recreate"},
args: append([]string{"compose", "-f", pathToDockerCompose, "up", "-d", "--force-recreate"}, serviceNames...),
desc: "Recreating containers",
errMessage: "Error recreating containers",
},
}

// Execute each command in sequence
for _, cmd := range commands {
args := append(cmd.args, uniqueContainers...)

execCmd := exec.Command("docker", args...)
execCmd := exec.Command("docker", cmd.args...)
execCmd.Stdout = os.Stdout
execCmd.Stderr = os.Stderr

Expand Down Expand Up @@ -123,15 +128,32 @@ func (d *Docker) UpdateServiceEnv(compose map[string]interface{}, serviceName st
}

// UniqueContainerNames returns a sorted list of unique container names
func (d *Docker) UniqueContainerNames(containerNames []string) []string {
unique := make(map[string]bool)
for _, name := range containerNames {
unique[name] = true
func (d *Docker) UniqueContainers(containers []Container) []Container {
unique := make(map[string]Container)
for _, container := range containers {
unique[container.Name] = container
}
keys := make([]string, 0, len(unique))
for k := range unique {
keys = append(keys, k)
uniqueContainers := make([]Container, 0, len(unique))
for _, container := range unique {
uniqueContainers = append(uniqueContainers, container)
}
return uniqueContainers
}

func (d *Docker) GetContainerNames(containers []Container) []string {
names := make([]string, 0, len(containers))
for _, container := range containers {
names = append(names, container.Name)
}
sort.Strings(names)
return names
}

func (d *Docker) GetServiceNames(containers []Container) []string {
names := make([]string, 0, len(containers))
for _, container := range containers {
names = append(names, container.ServiceName)
}
sort.Strings(keys)
return keys
sort.Strings(names)
return names
}
45 changes: 29 additions & 16 deletions internal/handlers/coin.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers

import (
"blockscout-vc/internal/docker"
"fmt"

"github.com/spf13/viper"
Expand Down Expand Up @@ -33,34 +34,46 @@ func (h *CoinHandler) Handle(record *Record) HandlerResult {
result.Error = fmt.Errorf("failed to read compose file: %w", err)
return result
}
frontendService := viper.GetString("frontendServiceName")
backendService := viper.GetString("backendServiceName")
statsService := viper.GetString("statsServiceName")

// Define environment updates for each service
updates := map[string]map[string]interface{}{
frontendService: {
"NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL": record.Coin,
updates := []EnvUpdate{
{
ServiceName: viper.GetString("frontendServiceName"),
Key: "NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL",
Value: record.Coin,
ContainerName: viper.GetString("frontendContainerName"),
},
backendService: {
"COIN": record.Coin,
{
ServiceName: viper.GetString("backendServiceName"),
Key: "COIN",
Value: record.Coin,
ContainerName: viper.GetString("backendContainerName"),
},
statsService: {
"STATS_CHARTS__TEMPLATE_VALUES__NATIVE_COIN_SYMBOL": record.Coin,
{
ServiceName: viper.GetString("statsServiceName"),
Key: "STATS_CHARTS__TEMPLATE_VALUES__NATIVE_COIN_SYMBOL",
Value: record.Coin,
ContainerName: viper.GetString("statsContainerName"),
},
}

// Define environment updates for each service

// Apply updates to each service
for service, env := range updates {
for _, env := range updates {
var updated bool
compose, updated, err = h.docker.UpdateServiceEnv(compose, service, env)
compose, updated, err = h.docker.UpdateServiceEnv(compose, env.ServiceName, map[string]interface{}{
env.Key: env.Value,
})
if err != nil {
result.Error = fmt.Errorf("failed to update %s service environment: %w", service, err)
result.Error = fmt.Errorf("failed to update %s service environment: %w", env.ServiceName, err)
return result
}
if updated {
fmt.Printf("Updated %s service environment: %+v\n", service, env)
result.ContainersToRestart = append(result.ContainersToRestart, service)
fmt.Printf("Updated %s service environment: %+v\n", env.ServiceName, env)
result.ContainersToRestart = append(result.ContainersToRestart, docker.Container{
Name: env.ContainerName,
ServiceName: env.ServiceName,
})
}
}

Expand Down
69 changes: 54 additions & 15 deletions internal/handlers/image.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package handlers

import (
"blockscout-vc/internal/docker"
"fmt"
"net/http"
"net/url"
"strings"
"time"

"github.com/spf13/viper"
)
Expand All @@ -11,11 +16,15 @@ const MaxImageLength = 2000

type ImageHandler struct {
BaseHandler
client *http.Client
}

func NewImageHandler() *ImageHandler {
return &ImageHandler{
BaseHandler: NewBaseHandler(),
client: &http.Client{
Timeout: 10 * time.Second,
},
}
}

Expand All @@ -35,34 +44,32 @@ func (h *ImageHandler) Handle(record *Record) HandlerResult {
return result
}

frontendService := viper.GetString("frontendServiceName")
frontendServiceName := viper.GetString("frontendServiceName")
frontendContainerName := viper.GetString("frontendContainerName")

updates := map[string]map[string]interface{}{
frontendService: {},
frontendServiceName: {},
}

// Validate and update light logo URL
if err := h.validateImage(record.LightLogoURL); err != nil {
result.Error = fmt.Errorf("invalid light logo URL: %w", err)
return result
} else {
updates[frontendService]["NEXT_PUBLIC_NETWORK_LOGO"] = record.LightLogoURL
updates[frontendServiceName]["NEXT_PUBLIC_NETWORK_LOGO"] = record.LightLogoURL
}

// Validate and update dark logo URL
if err := h.validateImage(record.DarkLogoURL); err != nil {
result.Error = fmt.Errorf("invalid dark logo URL: %w", err)
return result
} else {
updates[frontendService]["NEXT_PUBLIC_NETWORK_LOGO_DARK"] = record.DarkLogoURL
updates[frontendServiceName]["NEXT_PUBLIC_NETWORK_LOGO_DARK"] = record.DarkLogoURL
}

// Validate and update favicon URL
if err := h.validateImage(record.FaviconURL); err != nil {
result.Error = fmt.Errorf("invalid favicon URL: %w", err)
return result
} else {
updates[frontendService]["NEXT_PUBLIC_NETWORK_ICON"] = record.FaviconURL
updates[frontendServiceName]["NEXT_PUBLIC_NETWORK_ICON"] = record.FaviconURL
}

// Apply updates to services
Expand All @@ -75,7 +82,12 @@ func (h *ImageHandler) Handle(record *Record) HandlerResult {
}
if updated {
fmt.Printf("Updated %s service environment: %+v\n", service, env)
result.ContainersToRestart = append(result.ContainersToRestart, service)
fmt.Printf("Frontend container name: %s\n", frontendContainerName)
fmt.Printf("Frontend service name: %s\n", frontendServiceName)
result.ContainersToRestart = append(result.ContainersToRestart, docker.Container{
Name: frontendContainerName,
ServiceName: frontendServiceName,
})
}
}

Expand All @@ -89,15 +101,42 @@ func (h *ImageHandler) Handle(record *Record) HandlerResult {
}

// validateImage checks if the image URL meets the required criteria
func (h *ImageHandler) validateImage(image string) error {
if image == "" {
return fmt.Errorf("image cannot be empty")
}
if len(image) == 0 {
func (h *ImageHandler) validateImage(imageURL string) error {
if imageURL == "" {
return fmt.Errorf("image cannot be empty")
}
if len(image) > MaxImageLength {

if len(imageURL) > MaxImageLength {
return fmt.Errorf("image length cannot exceed %d characters", MaxImageLength)
}

// Parse and validate URL
parsedURL, err := url.Parse(imageURL)
if err != nil {
return fmt.Errorf("invalid URL format: %w", err)
}

// Check if scheme is http or https
if !strings.HasPrefix(parsedURL.Scheme, "http") {
return fmt.Errorf("URL must start with http:// or https://")
}

// Check if image is accessible
resp, err := h.client.Head(imageURL)
if err != nil {
return fmt.Errorf("failed to access image: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("image not accessible, status code: %d", resp.StatusCode)
}

// Optionally verify content type
contentType := resp.Header.Get("Content-Type")
if !strings.HasPrefix(contentType, "image/") {
return fmt.Errorf("URL does not point to an image (content-type: %s)", contentType)
}

return nil
}
11 changes: 9 additions & 2 deletions internal/handlers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ type Handler interface {

// HandlerResult represents the outcome of a handler's processing
type HandlerResult struct {
Error error // Any error that occurred during handling
ContainersToRestart []string // List of container names that need to be restarted
Error error // Any error that occurred during handling
ContainersToRestart []docker.Container // List of container names that need to be restarted
}

// Record represents the common data structure for all handlers
Expand All @@ -37,3 +37,10 @@ func NewBaseHandler() BaseHandler {
docker: docker.NewDocker(),
}
}

type EnvUpdate struct {
ServiceName string
Key string
Value string
ContainerName string
}
3 changes: 2 additions & 1 deletion internal/subscription/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package subscription

import (
"blockscout-vc/internal/client"
"blockscout-vc/internal/docker"
"blockscout-vc/internal/handlers"
"blockscout-vc/internal/worker"
"database/sql"
Expand Down Expand Up @@ -150,7 +151,7 @@ func (p *PostgresChanges) HandleMessage() error {
handlers.NewImageHandler(),
}

containersToRestart := []string{}
containersToRestart := []docker.Container{}
for _, handler := range handlers {
result := handler.Handle(&p.Payload.Data.Record)
if result.Error != nil {
Expand Down
Loading

0 comments on commit deb5a4b

Please sign in to comment.