kcl-lang.io/kpm@v0.8.7-0.20240520061008-9fc4c5efc8c7/pkg/downloader/downloader.go (about)

     1  package downloader
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"path/filepath"
     8  
     9  	v1 "github.com/opencontainers/image-spec/specs-go/v1"
    10  	"kcl-lang.io/kpm/pkg/constants"
    11  	"kcl-lang.io/kpm/pkg/git"
    12  	"kcl-lang.io/kpm/pkg/oci"
    13  	pkg "kcl-lang.io/kpm/pkg/package"
    14  	"kcl-lang.io/kpm/pkg/reporter"
    15  	"kcl-lang.io/kpm/pkg/settings"
    16  )
    17  
    18  // DownloadOptions is the options for downloading a package.
    19  type DownloadOptions struct {
    20  	// LocalPath is the local path to download the package.
    21  	LocalPath string
    22  	// Source is the source of the package. including git, oci, local.
    23  	Source pkg.Source
    24  	// Settings is the default settings and authrization information.
    25  	Settings settings.Settings
    26  	// LogWriter is the writer to write the log.
    27  	LogWriter io.Writer
    28  }
    29  
    30  type Option func(*DownloadOptions)
    31  
    32  func WithLogWriter(logWriter io.Writer) Option {
    33  	return func(do *DownloadOptions) {
    34  		do.LogWriter = logWriter
    35  	}
    36  }
    37  
    38  func WithSettings(settings settings.Settings) Option {
    39  	return func(do *DownloadOptions) {
    40  		do.Settings = settings
    41  	}
    42  }
    43  
    44  func WithLocalPath(localPath string) Option {
    45  	return func(do *DownloadOptions) {
    46  		do.LocalPath = localPath
    47  	}
    48  }
    49  
    50  func WithSource(source pkg.Source) Option {
    51  	return func(do *DownloadOptions) {
    52  		do.Source = source
    53  	}
    54  }
    55  
    56  func NewDownloadOptions(opts ...Option) *DownloadOptions {
    57  	do := &DownloadOptions{}
    58  	for _, opt := range opts {
    59  		opt(do)
    60  	}
    61  	return do
    62  }
    63  
    64  // Downloader is the interface for downloading a package.
    65  type Downloader interface {
    66  	Download(opts DownloadOptions) error
    67  }
    68  
    69  // DepDownloader is the downloader for the package.
    70  // Only support the OCI and git source.
    71  type DepDownloader struct {
    72  	*OciDownloader
    73  	*GitDownloader
    74  }
    75  
    76  // GitDownloader is the downloader for the git source.
    77  type GitDownloader struct{}
    78  
    79  // OciDownloader is the downloader for the OCI source.
    80  type OciDownloader struct {
    81  	Platform string
    82  }
    83  
    84  func NewOciDownloader(platform string) *DepDownloader {
    85  	return &DepDownloader{
    86  		OciDownloader: &OciDownloader{
    87  			Platform: platform,
    88  		},
    89  	}
    90  }
    91  
    92  func (d *DepDownloader) Download(opts DownloadOptions) error {
    93  	// Dispatch the download to the specific downloader by package source.
    94  	if opts.Source.Oci != nil {
    95  		if d.OciDownloader == nil {
    96  			d.OciDownloader = &OciDownloader{}
    97  		}
    98  		return d.OciDownloader.Download(opts)
    99  	}
   100  
   101  	if opts.Source.Git != nil {
   102  		if d.GitDownloader == nil {
   103  			d.GitDownloader = &GitDownloader{}
   104  		}
   105  		return d.GitDownloader.Download(opts)
   106  	}
   107  	return nil
   108  }
   109  
   110  // Platform option struct.
   111  type Platform struct {
   112  	PlatformSpec string
   113  	Platform     *v1.Platform
   114  }
   115  
   116  func (d *OciDownloader) Download(opts DownloadOptions) error {
   117  	// download the package from the OCI registry
   118  	ociSource := opts.Source.Oci
   119  	if ociSource == nil {
   120  		return errors.New("oci source is nil")
   121  	}
   122  
   123  	localPath := opts.LocalPath
   124  
   125  	ociCli, err := oci.NewOciClient(ociSource.Reg, ociSource.Repo, &opts.Settings)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	ociCli.PullOciOptions.Platform = d.Platform
   131  
   132  	// Select the latest tag, if the tag, the user inputed, is empty.
   133  	tagSelected := ociSource.Tag
   134  	if len(tagSelected) == 0 {
   135  		tagSelected, err = ociCli.TheLatestTag()
   136  		if err != nil {
   137  			return err
   138  		}
   139  
   140  		reporter.ReportMsgTo(
   141  			fmt.Sprintf("the lastest version '%s' will be added", tagSelected),
   142  			opts.LogWriter,
   143  		)
   144  
   145  		ociSource.Tag = tagSelected
   146  	}
   147  
   148  	reporter.ReportMsgTo(
   149  		fmt.Sprintf(
   150  			"downloading '%s:%s' from '%s/%s:%s'",
   151  			ociSource.Repo, tagSelected, ociSource.Reg, ociSource.Repo, tagSelected,
   152  		),
   153  		opts.LogWriter,
   154  	)
   155  
   156  	err = ociCli.Pull(localPath, tagSelected)
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	return nil
   162  }
   163  
   164  func (d *GitDownloader) Download(opts DownloadOptions) error {
   165  	var msg string
   166  	if len(opts.Source.Git.Tag) != 0 {
   167  		msg = fmt.Sprintf("with tag '%s'", opts.Source.Git.Tag)
   168  	}
   169  
   170  	if len(opts.Source.Git.Commit) != 0 {
   171  		msg = fmt.Sprintf("with commit '%s'", opts.Source.Git.Commit)
   172  	}
   173  
   174  	reporter.ReportMsgTo(
   175  		fmt.Sprintf("cloning '%s' %s", opts.Source.Git.Url, msg),
   176  		opts.LogWriter,
   177  	)
   178  	// download the package from the git repo
   179  	gitSource := opts.Source.Git
   180  	if gitSource == nil {
   181  		return errors.New("git source is nil")
   182  	}
   183  
   184  	_, err := git.CloneWithOpts(
   185  		git.WithCommit(gitSource.Commit),
   186  		git.WithBranch(gitSource.Branch),
   187  		git.WithTag(gitSource.Tag),
   188  		git.WithRepoURL(gitSource.Url),
   189  		git.WithLocalPath(filepath.Join(opts.LocalPath, constants.GitEntry)),
   190  	)
   191  
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	return nil
   197  }