127 lines
3.7 KiB
Go
127 lines
3.7 KiB
Go
package lifecycle
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
agentctl "github.com/kandev/kandev/internal/agent/runtime/agentctl"
|
|
agentctltypes "github.com/kandev/kandev/internal/agentctl/types"
|
|
"github.com/kandev/kandev/internal/githubauth"
|
|
)
|
|
|
|
var ErrGitCredentialBrokerUnreachable = errors.New("git credential broker is unreachable from executor")
|
|
|
|
// ErrGitHubCredentialBrokerUnreachable remains an alias for callers which
|
|
// predate provider-neutral leases.
|
|
var ErrGitHubCredentialBrokerUnreachable = ErrGitCredentialBrokerUnreachable
|
|
|
|
const brokerReachabilityScript = `if ! command -v curl >/dev/null 2>&1; then
|
|
echo "curl is required to reach the Kandev Git credential broker" >&2
|
|
exit 69
|
|
fi
|
|
broker_status=$(curl -sS --connect-timeout 5 --max-time 10 -o /dev/null -w '%{http_code}' "$KANDEV_GITHUB_CREDENTIAL_BROKER_URL") || exit 69
|
|
if [ "$broker_status" != "204" ]; then
|
|
echo "Kandev Git credential broker readiness returned HTTP $broker_status" >&2
|
|
exit 69
|
|
fi`
|
|
|
|
type brokerCommandRunner func(context.Context, string, map[string]string) ([]byte, error)
|
|
|
|
func runBrokerReachabilityPreflight(
|
|
ctx context.Context,
|
|
env map[string]string,
|
|
run brokerCommandRunner,
|
|
) error {
|
|
if !hasManagedGitCredentialBrokerEnv(env) {
|
|
return nil
|
|
}
|
|
probeEnv := map[string]string{
|
|
githubauth.CredentialBrokerURLEnv: env[githubauth.CredentialBrokerURLEnv],
|
|
}
|
|
output, err := run(ctx, brokerReachabilityScript, probeEnv)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %s: %v", ErrGitCredentialBrokerUnreachable,
|
|
strings.TrimSpace(string(output)), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type brokerAgentctlProcessClient interface {
|
|
StartProcess(context.Context, agentctl.StartProcessRequest) (*agentctl.ProcessInfo, error)
|
|
GetProcess(context.Context, string, bool) (*agentctl.ProcessInfo, error)
|
|
}
|
|
|
|
func runBrokerReachabilityViaAgentctl(
|
|
ctx context.Context,
|
|
client brokerAgentctlProcessClient,
|
|
sessionID string,
|
|
env map[string]string,
|
|
) error {
|
|
return runBrokerReachabilityPreflight(ctx, env, func(
|
|
ctx context.Context,
|
|
command string,
|
|
probeEnv map[string]string,
|
|
) ([]byte, error) {
|
|
process, err := client.StartProcess(ctx, agentctl.StartProcessRequest{
|
|
SessionID: sessionID,
|
|
Kind: agentctltypes.ProcessKindCustom,
|
|
Command: command,
|
|
Env: probeEnv,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return waitBrokerReachabilityProcess(ctx, client, process)
|
|
})
|
|
}
|
|
|
|
func waitBrokerReachabilityProcess(
|
|
ctx context.Context,
|
|
client brokerAgentctlProcessClient,
|
|
process *agentctl.ProcessInfo,
|
|
) ([]byte, error) {
|
|
if process == nil {
|
|
return nil, errors.New("agentctl returned no process")
|
|
}
|
|
deadline := time.NewTimer(15 * time.Second)
|
|
defer deadline.Stop()
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
for {
|
|
if output, done, err := brokerProcessResult(process); done {
|
|
return output, err
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, context.Cause(ctx)
|
|
case <-deadline.C:
|
|
return nil, errors.New("broker reachability process timed out")
|
|
case <-ticker.C:
|
|
var err error
|
|
process, err = client.GetProcess(ctx, process.ID, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func brokerProcessResult(process *agentctl.ProcessInfo) ([]byte, bool, error) {
|
|
if process.Status != agentctltypes.ProcessStatusExited &&
|
|
process.Status != agentctltypes.ProcessStatusFailed &&
|
|
process.Status != agentctltypes.ProcessStatusStopped {
|
|
return nil, false, nil
|
|
}
|
|
var output strings.Builder
|
|
for _, chunk := range process.Output {
|
|
output.WriteString(chunk.Data)
|
|
}
|
|
if process.ExitCode == nil || *process.ExitCode != 0 || process.Status != agentctltypes.ProcessStatusExited {
|
|
return []byte(output.String()), true, fmt.Errorf("probe process status %s", process.Status)
|
|
}
|
|
return []byte(output.String()), true, nil
|
|
}
|