Files
Alano Terblanche 0c2d0f700c vendor: github.com/docker/secrets-engine/client v0.2.2
Add the Docker secrets engine client, used to read the Docker Desktop session for Docker Hub. It brings in github.com/docker/secrets-engine/x v0.8.5 and connectrpc.com/connect v1.19.1.

The client module requires go1.26.10, which raises the go directive in vendor.mod from 1.26.0.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2026-10-22 11:10:40 +02:00

174 lines
4.4 KiB
Go

// Copyright 2026 Docker, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"context"
"errors"
"fmt"
"io/fs"
"net"
"path/filepath"
"github.com/docker/secrets-engine/x/api"
)
var (
ErrSecretsEngineNotRunning = errors.New("secrets engine is not running")
ErrSecretsEnginePermissionDenied = errors.New("permission denied connecting to the secrets engine")
ErrSecretsEngineTimeout = errors.New("secrets engine did not respond in time")
)
type ConnectReason int
const (
ReasonUnknown ConnectReason = iota
ReasonNotRunning
ReasonPermissionDenied
ReasonTimeout
)
func (r ConnectReason) String() string {
switch r {
case ReasonNotRunning:
return "not running"
case ReasonPermissionDenied:
return "permission denied"
case ReasonTimeout:
return "timeout"
default:
return "unknown"
}
}
func (r ConnectReason) sentinel() error {
switch r {
case ReasonNotRunning:
return ErrSecretsEngineNotRunning
case ReasonPermissionDenied:
return ErrSecretsEnginePermissionDenied
case ReasonTimeout:
return ErrSecretsEngineTimeout
default:
return nil
}
}
type ConnectError struct {
Reason ConnectReason
SocketPath string
Err error
}
func (e *ConnectError) Error() string {
msg := "cannot connect to the secrets engine"
if s := e.Reason.sentinel(); s != nil {
msg = s.Error()
}
if e.SocketPath != "" {
msg += " at " + e.SocketPath
}
if e.Err != nil {
msg += ": " + e.Err.Error()
}
return msg
}
func (e *ConnectError) Unwrap() error { return e.Err }
func (e *ConnectError) Is(target error) bool {
s := e.Reason.sentinel()
return s != nil && target == s
}
const (
desktopNotRunningHint = "Start Docker Desktop and retry: the secrets engine runs as part of it."
desktopTimeoutHint = "The secrets engine did not respond. Restart Docker Desktop and retry."
standaloneNotRunningHint = "Start the standalone secrets engine and retry: nothing is listening on %s."
standaloneTimeoutHint = "The standalone secrets engine on %s did not respond. Restart it and retry."
customNotRunningHint = "Start the secrets engine and retry: nothing is listening on %s."
customTimeoutHint = "The secrets engine on %s did not respond. Restart it and retry."
permissionDeniedHint = "Your user lacks permission to connect to %s. Grant it read and write access to the socket and retry."
)
// Hint returns a user friendly message to help recover from the error.
func (e *ConnectError) Hint() string {
socket := "the secrets engine socket"
if e.SocketPath != "" {
socket = fmt.Sprintf("%q", e.SocketPath)
}
kind := classifySocket(e.SocketPath)
switch e.Reason {
case ReasonNotRunning:
switch kind {
case engineDesktop:
return desktopNotRunningHint
case engineStandalone:
return fmt.Sprintf(standaloneNotRunningHint, socket)
default:
return fmt.Sprintf(customNotRunningHint, socket)
}
case ReasonTimeout:
switch kind {
case engineDesktop:
return desktopTimeoutHint
case engineStandalone:
return fmt.Sprintf(standaloneTimeoutHint, socket)
default:
return fmt.Sprintf(customTimeoutHint, socket)
}
case ReasonPermissionDenied:
return fmt.Sprintf(permissionDeniedHint, socket)
default:
return ""
}
}
type engineKind int
const (
engineCustom engineKind = iota
engineDesktop
engineStandalone
)
func classifySocket(path string) engineKind {
if path == "" {
return engineCustom
}
switch filepath.Clean(path) {
case filepath.Clean(api.DesktopSocketPath()):
return engineDesktop
case filepath.Clean(api.StandaloneSocketPath()):
return engineStandalone
default:
return engineCustom
}
}
func connectReason(err error) ConnectReason {
var ne net.Error
switch {
case errors.Is(err, fs.ErrNotExist), errors.Is(err, errConnRefused):
return ReasonNotRunning
case errors.Is(err, fs.ErrPermission):
return ReasonPermissionDenied
case errors.Is(err, context.DeadlineExceeded), errors.As(err, &ne) && ne.Timeout():
return ReasonTimeout
default:
return ReasonUnknown
}
}