github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/snapshotterutil/sociutil.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package snapshotterutil
    18  
    19  import (
    20  	"bufio"
    21  	"os"
    22  	"os/exec"
    23  	"strconv"
    24  	"strings"
    25  
    26  	"github.com/containerd/log"
    27  	"github.com/containerd/nerdctl/v2/pkg/api/types"
    28  )
    29  
    30  // CreateSoci creates a SOCI index(`rawRef`)
    31  func CreateSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform bool, platforms []string, sOpts types.SociOptions) error {
    32  	sociExecutable, err := exec.LookPath("soci")
    33  	if err != nil {
    34  		log.L.WithError(err).Error("soci executable not found in path $PATH")
    35  		log.L.Info("you might consider installing soci from: https://github.com/awslabs/soci-snapshotter/blob/main/docs/install.md")
    36  		return err
    37  	}
    38  
    39  	sociCmd := exec.Command(sociExecutable)
    40  	sociCmd.Env = os.Environ()
    41  
    42  	// #region for global flags.
    43  	if gOpts.Address != "" {
    44  		sociCmd.Args = append(sociCmd.Args, "--address", gOpts.Address)
    45  	}
    46  	if gOpts.Namespace != "" {
    47  		sociCmd.Args = append(sociCmd.Args, "--namespace", gOpts.Namespace)
    48  	}
    49  	// #endregion
    50  
    51  	// Global flags have to be put before subcommand before soci upgrades to urfave v3.
    52  	// https://github.com/urfave/cli/issues/1113
    53  	sociCmd.Args = append(sociCmd.Args, "create")
    54  
    55  	if allPlatform {
    56  		sociCmd.Args = append(sociCmd.Args, "--all-platforms")
    57  	}
    58  	if len(platforms) > 0 {
    59  		// multiple values need to be passed as separate, repeating flags in soci as it uses urfave
    60  		// https://github.com/urfave/cli/blob/main/docs/v2/examples/flags.md#multiple-values-per-single-flag
    61  		for _, p := range platforms {
    62  			sociCmd.Args = append(sociCmd.Args, "--platform", p)
    63  		}
    64  	}
    65  
    66  	if sOpts.SpanSize != -1 {
    67  		sociCmd.Args = append(sociCmd.Args, "--span-size", strconv.FormatInt(sOpts.SpanSize, 10))
    68  	}
    69  	if sOpts.MinLayerSize != -1 {
    70  		sociCmd.Args = append(sociCmd.Args, "--min-layer-size", strconv.FormatInt(sOpts.MinLayerSize, 10))
    71  	}
    72  	// --timeout, --debug, --content-store
    73  	sociCmd.Args = append(sociCmd.Args, rawRef)
    74  
    75  	log.L.Debugf("running %s %v", sociExecutable, sociCmd.Args)
    76  
    77  	err = processSociIO(sociCmd)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	return sociCmd.Wait()
    83  }
    84  
    85  // PushSoci pushes a SOCI index(`rawRef`)
    86  // `hostsDirs` are used to resolve image `rawRef`
    87  func PushSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform bool, platforms []string) error {
    88  	log.L.Debugf("pushing SOCI index: %s", rawRef)
    89  
    90  	sociExecutable, err := exec.LookPath("soci")
    91  	if err != nil {
    92  		log.L.WithError(err).Error("soci executable not found in path $PATH")
    93  		log.L.Info("you might consider installing soci from: https://github.com/awslabs/soci-snapshotter/blob/main/docs/install.md")
    94  		return err
    95  	}
    96  
    97  	sociCmd := exec.Command(sociExecutable)
    98  	sociCmd.Env = os.Environ()
    99  
   100  	// #region for global flags.
   101  	if gOpts.Address != "" {
   102  		sociCmd.Args = append(sociCmd.Args, "--address", gOpts.Address)
   103  	}
   104  	if gOpts.Namespace != "" {
   105  		sociCmd.Args = append(sociCmd.Args, "--namespace", gOpts.Namespace)
   106  	}
   107  	// #endregion
   108  
   109  	// Global flags have to be put before subcommand before soci upgrades to urfave v3.
   110  	// https://github.com/urfave/cli/issues/1113
   111  	sociCmd.Args = append(sociCmd.Args, "push")
   112  
   113  	if allPlatform {
   114  		sociCmd.Args = append(sociCmd.Args, "--all-platforms")
   115  	}
   116  	if len(platforms) > 0 {
   117  		// multiple values need to be passed as separate, repeating flags in soci as it uses urfave
   118  		// https://github.com/urfave/cli/blob/main/docs/v2/examples/flags.md#multiple-values-per-single-flag
   119  		for _, p := range platforms {
   120  			sociCmd.Args = append(sociCmd.Args, "--platform", p)
   121  		}
   122  	}
   123  	if gOpts.InsecureRegistry {
   124  		sociCmd.Args = append(sociCmd.Args, "--skip-verify")
   125  		sociCmd.Args = append(sociCmd.Args, "--plain-http")
   126  	}
   127  	if len(gOpts.HostsDir) > 0 {
   128  		sociCmd.Args = append(sociCmd.Args, "--hosts-dir")
   129  		sociCmd.Args = append(sociCmd.Args, strings.Join(gOpts.HostsDir, ","))
   130  	}
   131  	sociCmd.Args = append(sociCmd.Args, rawRef)
   132  
   133  	log.L.Debugf("running %s %v", sociExecutable, sociCmd.Args)
   134  
   135  	err = processSociIO(sociCmd)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	return sociCmd.Wait()
   140  }
   141  
   142  func processSociIO(sociCmd *exec.Cmd) error {
   143  	stdout, err := sociCmd.StdoutPipe()
   144  	if err != nil {
   145  		log.L.Warn("soci: " + err.Error())
   146  	}
   147  	stderr, err := sociCmd.StderrPipe()
   148  	if err != nil {
   149  		log.L.Warn("soci: " + err.Error())
   150  	}
   151  	if err := sociCmd.Start(); err != nil {
   152  		// only return err if it's critical (soci command failed to start.)
   153  		return err
   154  	}
   155  
   156  	scanner := bufio.NewScanner(stdout)
   157  	for scanner.Scan() {
   158  		log.L.Info("soci: " + scanner.Text())
   159  	}
   160  	if err := scanner.Err(); err != nil {
   161  		log.L.Warn("soci: " + err.Error())
   162  	}
   163  
   164  	errScanner := bufio.NewScanner(stderr)
   165  	for errScanner.Scan() {
   166  		log.L.Info("soci: " + errScanner.Text())
   167  	}
   168  	if err := errScanner.Err(); err != nil {
   169  		log.L.Warn("soci: " + err.Error())
   170  	}
   171  
   172  	return nil
   173  }