Skip to content

Commit

Permalink
fix: couchbase containers intermittently hang on startup
Browse files Browse the repository at this point in the history
  • Loading branch information
couchbaseEd committed Jul 16, 2024
1 parent eb22239 commit a0c8e35
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 32 deletions.
1 change: 1 addition & 0 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,7 @@ var permanentClientErrors = []func(error) bool{
errdefs.IsUnauthorized,
errdefs.IsForbidden,
errdefs.IsNotImplemented,
errdefs.IsSystem,
}

func isPermanentClientError(err error) bool {
Expand Down
79 changes: 52 additions & 27 deletions docker_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"net/url"
"os"
"sync"

"github.com/cpuguy83/dockercfg"
"github.com/docker/docker/api/types/registry"
Expand Down Expand Up @@ -79,52 +80,76 @@ func defaultRegistry(ctx context.Context) string {
return info.IndexServerAddress
}

// authConfig represents the details of the auth config for a registry.
type authConfig struct {
key string
cfg registry.AuthConfig
}

// getDockerAuthConfigs returns a map with the auth configs from the docker config file
// using the registry as the key
func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) {
var getDockerAuthConfigs = sync.OnceValues(func() (map[string]registry.AuthConfig, error) {
cfg, err := getDockerConfig()
if err != nil {
return nil, err
}

cfgs := map[string]registry.AuthConfig{}
results := make(chan authConfig, len(cfg.AuthConfigs)+len(cfg.CredentialHelpers))
var wg sync.WaitGroup
wg.Add(len(cfg.AuthConfigs) + len(cfg.CredentialHelpers))
for k, v := range cfg.AuthConfigs {
ac := registry.AuthConfig{
Auth: v.Auth,
Email: v.Email,
IdentityToken: v.IdentityToken,
Password: v.Password,
RegistryToken: v.RegistryToken,
ServerAddress: v.ServerAddress,
Username: v.Username,
}
go func(k string, v dockercfg.AuthConfig) {
defer wg.Done()

ac := registry.AuthConfig{
Auth: v.Auth,
Email: v.Email,
IdentityToken: v.IdentityToken,
Password: v.Password,
RegistryToken: v.RegistryToken,
ServerAddress: v.ServerAddress,
Username: v.Username,
}

if v.Username == "" && v.Password == "" {
u, p, _ := dockercfg.GetRegistryCredentials(k)
ac.Username = u
ac.Password = p
}

if v.Auth == "" {
ac.Auth = base64.StdEncoding.EncodeToString([]byte(ac.Username + ":" + ac.Password))
}
results <- authConfig{key: k, cfg: ac}
}(k, v)
}

// in the case where the auth field in the .docker/conf.json is empty, and the user has credential helpers registered
// the auth comes from there
for k := range cfg.CredentialHelpers {
go func(k string) {
defer wg.Done()

if v.Username == "" && v.Password == "" {
ac := registry.AuthConfig{}
u, p, _ := dockercfg.GetRegistryCredentials(k)
ac.Username = u
ac.Password = p
}

if v.Auth == "" {
ac.Auth = base64.StdEncoding.EncodeToString([]byte(ac.Username + ":" + ac.Password))
}

cfgs[k] = ac
results <- authConfig{key: k, cfg: ac}
}(k)
}

// in the case where the auth field in the .docker/conf.json is empty, and the user has credential helpers registered
// the auth comes from there
for k := range cfg.CredentialHelpers {
ac := registry.AuthConfig{}
u, p, _ := dockercfg.GetRegistryCredentials(k)
ac.Username = u
ac.Password = p
go func() {
wg.Wait()
close(results)
}()

cfgs[k] = ac
for ac := range results {
cfgs[ac.key] = ac.cfg
}

return cfgs, nil
}
})

// getDockerConfig returns the docker config file. It will internally check, in this particular order:
// 1. the DOCKER_AUTH_CONFIG environment variable, unmarshalling it into a dockercfg.Config
Expand Down
11 changes: 8 additions & 3 deletions modules/couchbase/couchbase.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,9 +491,14 @@ func (c *CouchbaseContainer) createPrimaryIndex(ctx context.Context, bucket buck
body := map[string]string{
"statement": "CREATE PRIMARY INDEX on `" + bucket.name + "`",
}

_, err := c.doHttpRequest(ctx, QUERY_PORT, "/query/service", http.MethodPost, body, true)

err := backoff.Retry(func() error {
response, err := c.doHttpRequest(ctx, QUERY_PORT, "/query/service", http.MethodPost, body, true)
firstError := gjson.Get(string(response), "errors.0.code").Int()
if firstError != 0 {
return errors.New("index creation failed")
}
return err
}, backoff.WithContext(backoff.NewExponentialBackOff(), ctx))
return err
}

Expand Down
12 changes: 10 additions & 2 deletions modules/couchbase/couchbase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

const (
// dockerImages {
enterpriseEdition = "couchbase:enterprise-7.1.3"
enterpriseEdition = "couchbase:enterprise-7.6.1"
communityEdition = "couchbase:community-7.1.1"
// }
)
Expand Down Expand Up @@ -54,7 +54,15 @@ func TestCouchbaseWithEnterpriseContainer(t *testing.T) {
ctx := context.Background()

bucketName := "testBucket"
container, err := tccouchbase.Run(ctx, enterpriseEdition, tccouchbase.WithBuckets(tccouchbase.NewBucket(bucketName)))
bucket := tccouchbase.NewBucket(bucketName).
WithQuota(100).
WithReplicas(0).
WithFlushEnabled(true).
WithPrimaryIndex(true)
container, err := tccouchbase.Run(ctx,
enterpriseEdition,
tccouchbase.WithBuckets(bucket),
)
if err != nil {
t.Fatal(err)
}
Expand Down

0 comments on commit a0c8e35

Please sign in to comment.