// Package daemonclient submits local CLI requests to the running daemon service. package daemonclient import ( "context" "encoding/json" "errors" "fmt" "net" "path/filepath" "yms-daemon/internal/daemonapi" ) func Update(ctx context.Context, socketPath string, service string, inputType string, file string, progress func(daemonapi.Response)) (daemonapi.Response, error) { if !filepath.IsAbs(socketPath) || !filepath.IsAbs(file) { return daemonapi.Response{}, errors.New("daemon socket and update file paths must be absolute") } request := daemonapi.Request{Operation: daemonapi.OperationUpdate, Service: service, InputType: inputType, File: file} return submit(ctx, socketPath, request, progress) } func UpdateContainerImage(ctx context.Context, socketPath string, service string, imageReference string, startLog bool, progress func(daemonapi.Response)) (daemonapi.Response, error) { if !filepath.IsAbs(socketPath) { return daemonapi.Response{}, errors.New("daemon socket path must be absolute") } if imageReference == "" { return daemonapi.Response{}, errors.New("container image reference is required") } request := daemonapi.Request{Operation: daemonapi.OperationUpdate, Service: service, InputType: daemonapi.InputTypeContainerImage, ImageReference: imageReference, StartLog: startLog} return submit(ctx, socketPath, request, progress) } func Restart(ctx context.Context, socketPath string, service string, progress func(daemonapi.Response)) (daemonapi.Response, error) { if !filepath.IsAbs(socketPath) { return daemonapi.Response{}, errors.New("daemon socket path must be absolute") } request := daemonapi.Request{Operation: daemonapi.OperationRestart, Service: service} return submit(ctx, socketPath, request, progress) } func submit(ctx context.Context, socketPath string, request daemonapi.Request, progress func(daemonapi.Response)) (daemonapi.Response, error) { dialer := net.Dialer{} connection, err := dialer.DialContext(ctx, "unix", socketPath) if err != nil { return daemonapi.Response{}, fmt.Errorf("connect to daemon service at %s: %w", socketPath, err) } defer connection.Close() if err := json.NewEncoder(connection).Encode(request); err != nil { return daemonapi.Response{}, fmt.Errorf("submit daemon request: %w", err) } if writer, ok := connection.(interface{ CloseWrite() error }); ok { if err := writer.CloseWrite(); err != nil { return daemonapi.Response{}, fmt.Errorf("finish daemon request: %w", err) } } decoder := json.NewDecoder(connection) decoder.DisallowUnknownFields() for { var response daemonapi.Response if err := decoder.Decode(&response); err != nil { return daemonapi.Response{}, fmt.Errorf("read daemon response: %w", err) } switch response.Kind { case daemonapi.ResponseProgress: if progress != nil { progress(response) } case daemonapi.ResponseResult: if response.Error != "" { return response, errors.New(response.Error) } return response, nil default: return daemonapi.Response{}, fmt.Errorf("daemon returned unsupported response kind %q", response.Kind) } } }