oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/registry/remote/credentials/internal/executer/executer.go (about)

     1  /*
     2  Copyright The ORAS Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  // Package executer is an abstraction for the docker credential helper protocol
    17  // binaries. It is used by nativeStore to interact with installed binaries.
    18  package executer
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"errors"
    24  	"io"
    25  	"os"
    26  	"os/exec"
    27  
    28  	"oras.land/oras-go/v2/registry/remote/credentials/trace"
    29  )
    30  
    31  // dockerDesktopHelperName is the name of the docker credentials helper
    32  // execuatable.
    33  const dockerDesktopHelperName = "docker-credential-desktop.exe"
    34  
    35  // Executer is an interface that simulates an executable binary.
    36  type Executer interface {
    37  	Execute(ctx context.Context, input io.Reader, action string) ([]byte, error)
    38  }
    39  
    40  // executable implements the Executer interface.
    41  type executable struct {
    42  	name string
    43  }
    44  
    45  // New returns a new Executer instance.
    46  func New(name string) Executer {
    47  	return &executable{
    48  		name: name,
    49  	}
    50  }
    51  
    52  // Execute operates on an executable binary and supports context.
    53  func (c *executable) Execute(ctx context.Context, input io.Reader, action string) ([]byte, error) {
    54  	cmd := exec.CommandContext(ctx, c.name, action)
    55  	cmd.Stdin = input
    56  	cmd.Stderr = os.Stderr
    57  	trace := trace.ContextExecutableTrace(ctx)
    58  	if trace != nil && trace.ExecuteStart != nil {
    59  		trace.ExecuteStart(c.name, action)
    60  	}
    61  	output, err := cmd.Output()
    62  	if trace != nil && trace.ExecuteDone != nil {
    63  		trace.ExecuteDone(c.name, action, err)
    64  	}
    65  	if err != nil {
    66  		switch execErr := err.(type) {
    67  		case *exec.ExitError:
    68  			if errMessage := string(bytes.TrimSpace(output)); errMessage != "" {
    69  				return nil, errors.New(errMessage)
    70  			}
    71  		case *exec.Error:
    72  			// check if the error is caused by Docker Desktop not running
    73  			if execErr.Err == exec.ErrNotFound && c.name == dockerDesktopHelperName {
    74  				return nil, errors.New("credentials store is configured to `desktop.exe` but Docker Desktop seems not running")
    75  			}
    76  		}
    77  		return nil, err
    78  	}
    79  	return output, nil
    80  }