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 }