Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8141a4d
fix: allow downloading more than just remote_config, for #6892
rfay Jul 30, 2025
e8f2589
feat: refactor remote config to support arbitrary JSONC downloads and…
rfay Jul 30, 2025
911d7ac
fix: address test failures and improve remote config robustness
rfay Jul 31, 2025
081c3fd
prefer require instead of assert in test
rfay Aug 1, 2025
0ef7b6c
Add a ddev debug gob-decode command [skip ci]
rfay Aug 1, 2025
4f64015
Add ddev debug gob-decode command and fix remote config structure
rfay Aug 1, 2025
84662ce
Add comprehensive tests for ddev debug gob-decode command
rfay Aug 1, 2025
45c5ad9
feat: enhance ddev debug gob-decode with multi-type support and compr…
rfay Aug 1, 2025
2ef04f6
feat: add ddev debug download-json-file command with comprehensive re…
rfay Aug 2, 2025
c4961f9
GitHub Copilot-assisted refactoring of tests and test data
rfay Aug 2, 2025
ba8f0a2
feat: integrate sponsorship data into user experience
rfay Aug 2, 2025
7a959c1
security: fix path traversal and HTTP timeout vulnerabilities in debu…
rfay Aug 2, 2025
92e96ba
refactor: improve error handling consistency in debug commands
rfay Aug 2, 2025
4ebd612
test: add comprehensive tests for tip-of-the-day and sponsorship func…
rfay Aug 2, 2025
d3a432b
Use upstream sponsorship information for goal and message
rfay Aug 2, 2025
731952f
download json file from URL no longer in there
rfay Aug 3, 2025
3f37762
Make comments in global_config so they can be uncommented [skip ci]
rfay Aug 4, 2025
899af74
fix: ensure debug and sponsorship commands use configured global conf…
rfay Aug 4, 2025
fa5d92c
Rename download-json-file, make hidden, update docs, add ddev debug -…
rfay Aug 4, 2025
87d376e
Update default sponsorship message [skip ci]
rfay Aug 4, 2025
eb51222
feat: rename debug command and implement hidden command functionality
rfay Aug 5, 2025
77ac753
fix: debug remote-data test failures in CI environment
rfay Aug 5, 2025
d4e3e9a
fix: initialize RemoteConfig defaults in global config to prevent CI …
rfay Aug 5, 2025
2b7128e
refactor: simplify remote config to use URLs instead of GitHub parame…
rfay Aug 5, 2025
daf1b8a
feat: complete URL-based remote config system refactoring
rfay Aug 5, 2025
622fdc4
refactor: use consistent global initialization pattern for sponsorship
rfay Aug 5, 2025
fa873df
fix: update remoteconfig tests to use URL-based configuration
rfay Aug 5, 2025
5bb7da6
fix: improve remote config error handling and visibility
rfay Aug 5, 2025
f9dfab9
fix: update error logging to use %v instead of %s for error messages
rfay Aug 5, 2025
f060cc9
fix: complete migration to URL-based remote config and improve error …
rfay Aug 5, 2025
2bb7fa3
docs: fix remote config documentation to reflect URL-based configurat…
rfay Aug 5, 2025
01d56b1
Remove some extra verbosity in remote-config.md
rfay Aug 5, 2025
51e020d
fix: add default URLs for remote config and sponsorship data in globa…
rfay Aug 5, 2025
822af5d
@stasadev suggestions (output.Printf()) thanks
rfay Aug 7, 2025
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
1 change: 1 addition & 0 deletions .spellcheckwordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ jetbrains
jq
js
json
jsonc
kcachegrind
kbox
keychain
Expand Down
9 changes: 9 additions & 0 deletions cmd/ddev/cmd/config-global.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,15 @@ func handleGlobalConfig(cmd *cobra.Command, _ []string) {
keys = append(keys, tagWithDashes)
}
}

// Add remote config URLs to the display
valMap["remote-config-url"] = globalconfig.DdevGlobalConfig.RemoteConfig.RemoteConfigURL
keys = append(keys, "remote-config-url")
valMap["sponsorship-data-url"] = globalconfig.DdevGlobalConfig.RemoteConfig.SponsorshipDataURL
keys = append(keys, "sponsorship-data-url")
valMap["remote-config-update-interval"] = fmt.Sprintf("%d", globalconfig.DdevGlobalConfig.RemoteConfig.UpdateInterval)
keys = append(keys, "remote-config-update-interval")

sort.Strings(keys)
if !output.JSONOutput {
for _, label := range keys {
Expand Down
223 changes: 223 additions & 0 deletions cmd/ddev/cmd/debug-gob-decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package cmd

import (
"encoding/gob"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"

"github.com/ddev/ddev/pkg/config/remoteconfig/types"
"github.com/ddev/ddev/pkg/output"
"github.com/spf13/cobra"
)

// fileStorageData is used for gob decoding of remote config storage
type fileStorageData struct {
RemoteConfig types.RemoteConfigData
}

// sponsorshipFileStorageData is used for gob decoding of sponsorship data storage
type sponsorshipFileStorageData struct {
SponsorshipData types.SponsorshipData `json:"sponsorship_data"`
}

// Amplitude event cache structures (not available in remoteconfig/types)
type StorageEvent struct {
EventType string `json:"event_type,omitempty"`
UserID string `json:"user_id,omitempty"`
DeviceID string `json:"device_id,omitempty"`
Time int64 `json:"time,omitempty"`
EventProps map[string]interface{} `json:"event_properties,omitempty"`
UserProps map[string]interface{} `json:"user_properties,omitempty"`
}

type eventCache struct {
LastSubmittedAt time.Time `json:"last_submitted_at"`
Events []*StorageEvent `json:"events"`
}

// DebugGobDecodeCmd implements the ddev debug gob-decode command
var DebugGobDecodeCmd = &cobra.Command{
Use: "gob-decode [file]",
Short: "Decode and display contents of a gob-encoded file",
Hidden: true,
Long: `Decode and display the contents of Go gob-encoded binary files.

This command can decode various gob files used by DDEV, including:
- .remote-config files (remote configuration cache)
- .amplitude.cache files (analytics event cache)
- sponsorship data files (contributor sponsorship information)

The decoder automatically detects the file type and uses the appropriate structure.
The output is displayed as formatted JSON for readability.

Note: Generic gob files with unknown concrete types may not be decodable due to
Go's gob encoding limitations.`,
Example: `ddev debug gob-decode ~/.ddev/.remote-config
ddev debug gob-decode ~/.ddev/.amplitude.cache
ddev debug gob-decode /path/to/some/file.gob`,
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
filename := args[0]

// Convert relative paths to absolute paths safely
if !filepath.IsAbs(filename) {
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("error getting current working directory: %w", err)
}
fullPath, err := filepath.Abs(filepath.Join(cwd, filename))
if err != nil {
return fmt.Errorf("failed to derive absolute path for %s: %w", filename, err)
}
filename = fullPath
}

// Check if file exists
if _, err := os.Stat(filename); os.IsNotExist(err) {
return fmt.Errorf("file does not exist: %s", filename)
}

// Try to decode the file
err := decodeGobFile(filename)
if err != nil {
return fmt.Errorf("error decoding gob file %s: %w", filename, err)
}

return nil
},
}

// decodeGobFile attempts to decode various known gob file types
func decodeGobFile(filename string) error {
// Try known specific types first
decoders := []struct {
name string
decoder func(string) error
}{
{"remote config", tryDecodeRemoteConfig},
{"sponsorship data", tryDecodeSponsorshipData},
{"amplitude event cache", tryDecodeAmplitudeCache},
}

for _, d := range decoders {
if err := d.decoder(filename); err == nil {
return nil
}
}

// Fall back to generic interface{} decoding
return tryDecodeGeneric(filename)
}

// tryDecodeGeneric attempts to decode as generic interface{}
func tryDecodeGeneric(filename string) error {
file, err := os.Open(filename)
if err != nil {
return fmt.Errorf("opening file: %w", err)
}
defer file.Close()

decoder := gob.NewDecoder(file)
var data interface{}
err = decoder.Decode(&data)
if err != nil {
// If we can't decode as interface{}, return the error
// This means the gob file contains a concrete type that wasn't registered
return fmt.Errorf("decoding gob data: %w", err)
}

// Convert to JSON for readable output
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
return fmt.Errorf("marshaling to JSON: %w", err)
}

output.UserErr.Printf("Generic gob file contents:\n")
output.UserOut.Printf("%s\n", string(jsonData))
return nil
}

// tryDecodeRemoteConfig attempts to decode the file as a remote config
func tryDecodeRemoteConfig(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()

var data fileStorageData
decoder := gob.NewDecoder(file)
err = decoder.Decode(&data)
if err != nil {
return err
}

// Convert to JSON for readable output
jsonData, err := json.MarshalIndent(data.RemoteConfig, "", " ")
if err != nil {
return err
}

output.UserErr.Printf("Remote config file contents:\n")
output.UserOut.Printf("%s\n", string(jsonData))
return nil
}

// tryDecodeSponsorshipData attempts to decode the file as sponsorship data
func tryDecodeSponsorshipData(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()

var data sponsorshipFileStorageData
decoder := gob.NewDecoder(file)
err = decoder.Decode(&data)
if err != nil {
return err
}

// Convert to JSON for readable output
jsonData, err := json.MarshalIndent(data.SponsorshipData, "", " ")
if err != nil {
return err
}

output.UserErr.Printf("Sponsorship data file contents:\n")
output.UserOut.Printf("%s\n", string(jsonData))
return nil
}

// tryDecodeAmplitudeCache attempts to decode the file as amplitude event cache
func tryDecodeAmplitudeCache(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()

var data eventCache
decoder := gob.NewDecoder(file)
err = decoder.Decode(&data)
if err != nil {
return err
}

// Convert to JSON for readable output
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}

output.UserErr.Printf("Amplitude event cache contents:\n")
output.UserOut.Printf("%s\n", string(jsonData))
return nil
}

func init() {
DebugCmd.AddCommand(DebugGobDecodeCmd)
}
Loading