diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go index a3277a6c3f0ac1..3e1170cc948869 100644 --- a/src/cmd/go/internal/cfg/cfg.go +++ b/src/cmd/go/internal/cfg/cfg.go @@ -246,6 +246,7 @@ var ( GOPRIVATE = Getenv("GOPRIVATE") GONOPROXY = envOr("GONOPROXY", GOPRIVATE) GONOSUMDB = envOr("GONOSUMDB", GOPRIVATE) + GOPUSH = Getenv("GOPUSH") ) // GetArchEnv returns the name and setting of the diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go index 17852deed1e703..b7a889c567ef09 100644 --- a/src/cmd/go/internal/envcmd/env.go +++ b/src/cmd/go/internal/envcmd/env.go @@ -80,6 +80,7 @@ func MkEnv() []cfg.EnvVar { {Name: "GOOS", Value: cfg.Goos}, {Name: "GOPATH", Value: cfg.BuildContext.GOPATH}, {Name: "GOPRIVATE", Value: cfg.GOPRIVATE}, + {Name: "GOPUSH", Value: cfg.GOPUSH}, {Name: "GOPROXY", Value: cfg.GOPROXY}, {Name: "GOROOT", Value: cfg.GOROOT}, {Name: "GOSUMDB", Value: cfg.GOSUMDB}, diff --git a/src/cmd/go/internal/modcmd/mod.go b/src/cmd/go/internal/modcmd/mod.go index f150cc9728a4e5..8e2bb6918fd496 100644 --- a/src/cmd/go/internal/modcmd/mod.go +++ b/src/cmd/go/internal/modcmd/mod.go @@ -23,6 +23,8 @@ See 'go help modules' for an overview of module functionality. cmdEdit, cmdGraph, cmdInit, + cmdPack, + cmdPush, cmdTidy, cmdVendor, cmdVerify, diff --git a/src/cmd/go/internal/modcmd/pack.go b/src/cmd/go/internal/modcmd/pack.go new file mode 100644 index 00000000000000..5c0f49eec3522e --- /dev/null +++ b/src/cmd/go/internal/modcmd/pack.go @@ -0,0 +1,41 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// go mod pack + +package modcmd + +import ( + "cmd/go/internal/base" + "cmd/go/internal/modpack" + "os" +) + +var cmdPack = &base.Command{ + UsageLine: "go mod pack [ ]", + Short: "packages the project in the current location", + Long: ` +Packages a project in the current directory, +The package created can then be uploaded to a host compatible +server. +`, + Run: runPack, +} + +func runPack(cmd *base.Command, args []string) { + + if len(args) != 2 { + base.Fatalf("go mod pack: no. of arguments incorrect") + } + + if err := modpack.ValidateArgument(args); err != nil { + base.Fatalf("go mod pack: argument %v invalid: %v", args[0], err) + } + + if _, err := os.Stat("go.mod"); err != nil { + base.Fatalf("go mod pack: go.mod does not exists") + } + + modpack.Pack(args) // does all the hard work +} diff --git a/src/cmd/go/internal/modcmd/push.go b/src/cmd/go/internal/modcmd/push.go new file mode 100644 index 00000000000000..5788745107105c --- /dev/null +++ b/src/cmd/go/internal/modcmd/push.go @@ -0,0 +1,61 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// go mod push + +package modcmd + +import ( + "cmd/go/internal/base" + "cmd/go/internal/cfg" + "cmd/go/internal/modpush" + "fmt" +) + +var cmdPush = &base.Command{ + UsageLine: "go mod push [-project // -file project.zip]", + Short: "pushes the project to a hosted repository", + Long: ` +Pushes a project zip to a configured host, the environment variable GOPUSH is used +to determine the repository endpoint or it can be overridden using -host parameter + +Required fields are: + -project which is in the the form of /owner>/, for example github.com/golang/protobuf + -file filename of the package to build uploaded, this is in the form of SEMVER.zip for example 'v1.0.0.zip', this file can be generated using 'go mod pack' + +To upload using basic authentication use -u and -p for username and password respectively +`, + Run: runPush, +} + +var ( + host string + project string + filename string + username string + password string +) + +func init() { + cmdPush.Flag.StringVar(&host, "host", cfg.GOPUSH, "Overrides the GOPUSH environment setting") + cmdPush.Flag.StringVar(&project, "project", "", "in the form //") + cmdPush.Flag.StringVar(&filename, "file", "", "filename of the package to be uploaded") + cmdPush.Flag.StringVar(&username, "u", "", "username for basic authentication") + cmdPush.Flag.StringVar(&password, "p", "", "password for basic authentication") +} + +func runPush(cmd *base.Command, args []string) { + if project == "" { + base.Fatalf("project must be specified in the form // (use -project)") + } + if filename == "" { + base.Fatalf("file must be specified (use -file") + } + if host == "" { + base.Fatalf("Host must be set, either use the environment variable 'GOPUSH' or use the -host flag") + } + + fmt.Printf("Pushing file %s to project %s at %s\n", filename, project, host) + modpush.Push(host, project, filename, username, password) +} diff --git a/src/cmd/go/internal/modpack/pack.go b/src/cmd/go/internal/modpack/pack.go new file mode 100644 index 00000000000000..9ee88a125b23e7 --- /dev/null +++ b/src/cmd/go/internal/modpack/pack.go @@ -0,0 +1,135 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package modpack + +import ( + "archive/zip" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +func ValidateArgument(arg []string) error { + if len(arg) != 2 { + return errors.New("wrong number of argument") + } + + if err := Validate(arg[0], arg[1]); err != nil { + return err + } + + return nil +} + +func Pack(args []string) { + // TODO Check for errors + packageInfo, _ := GeneratePackageInfo(args[0], args[1]) + + tempDir := os.TempDir() + absoluteTempFolder := tempDir + packageInfo.GetTempFolderName() + + createFolder(absoluteTempFolder) + defer deleteTemporaryFolder(tempDir, packageInfo.GetTempFolderName()) + copyProject(".", absoluteTempFolder) + compressFolder(tempDir+packageInfo.vcs, packageInfo.GetZipFileName()) + + fmt.Println("Generated", packageInfo.GetZipFileName()) +} + +func createFolder(path string) { + absoluteDestination := path + err := os.MkdirAll(absoluteDestination, os.ModeDir|os.ModePerm) + if err != nil { + panic(err) + } +} + +func deleteTemporaryFolder(tempFolder string, destinationPath string) { + fmt.Println("Cleaning up temporary folders") + + split := strings.Split(destinationPath, string(os.PathSeparator)) + + err := os.RemoveAll(tempFolder + split[0]) + if err != nil { + panic(err) + } +} + +func copyProject(sourcePath string, destinationPath string) { + fmt.Println(destinationPath) + filepath.Walk(sourcePath, func(path string, info os.FileInfo, err error) error { + dst := destinationPath + string(os.PathSeparator) + path + src := sourcePath + string(os.PathSeparator) + path + if info.IsDir() { + os.Mkdir(dst, os.ModeDir|os.ModePerm) + return nil + } + copyFile(src, dst) + return nil + }) +} + +func copyFile(source string, destination string) { + in, err := os.Open(source) + if err != nil { + return + } + defer in.Close() + + out, err := os.Create(destination) + if err != nil { + return + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return + } + return +} + +func compressFolder(folderToZip string, name string) { + newZipFile, err := os.Create(name) + if err != nil { + panic(err) + } + + writer := zip.NewWriter(newZipFile) + defer writer.Close() + + _ = filepath.Walk(folderToZip, func(path string, info os.FileInfo, err error) error { + if !info.IsDir() && !isSelf(info, name) { + addFileToZip(writer, path) + } + return nil + }) +} + +func isSelf(info os.FileInfo, name string) bool { + return info.Name() == name +} + +func addFileToZip(writer *zip.Writer, file string) error { + openedFile, err := os.Open(file) + if err != nil { + return fmt.Errorf("unable to open file %v: %v", file, err) + } + defer openedFile.Close() + + // Strip the temp folder names + fileToCreate := strings.Replace(file, os.TempDir(), "", 1) + wr, err := writer.Create(fileToCreate) + if err != nil { + return fmt.Errorf("error adding file; '%s' to zip : %s", file, err) + } + + if _, err := io.Copy(wr, openedFile); err != nil { + return fmt.Errorf("error writing %s to zip: %s", file, err) + } + return nil +} diff --git a/src/cmd/go/internal/modpack/packInfo.go b/src/cmd/go/internal/modpack/packInfo.go new file mode 100644 index 00000000000000..a8f494fa904308 --- /dev/null +++ b/src/cmd/go/internal/modpack/packInfo.go @@ -0,0 +1,60 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package modpack + +import ( + "cmd/go/internal/semver" + "errors" + "fmt" + "os" + "strings" +) + +type PackInfo struct { + vcs string + owner string + name string + version string +} + +func Validate(project string, version string) error { + split := strings.Split(project, string(os.PathSeparator)) + if len(split) != 3 { + return errors.New("project path incorrect") + } + + if !semver.IsValid(version) { + return errors.New("version does not satisfy semver") + } + return nil +} + +func GeneratePackageInfo(project string, version string) (PackInfo, error) { + split := strings.Split(project, string(os.PathSeparator)) + if len(split) != 3 { + return PackInfo{}, errors.New("project path incorrect") + } + + pack := PackInfo{ + vcs: split[0], + owner: split[1], + name: split[2], + version: version, + } + return pack, nil +} + +func (pkg PackInfo) GetTempFolderName() string { + return pkg.vcs + string(os.PathSeparator) + + pkg.owner + string(os.PathSeparator) + + pkg.name + "@" + pkg.version +} + +func (pkg PackInfo) GetModCacheFolderName() string { + return fmt.Sprintf("%s/%s/%s@%s", pkg.vcs, pkg.owner, pkg.name, pkg.version) +} + +func (pkg PackInfo) GetZipFileName() string { + return pkg.version + ".zip" +} diff --git a/src/cmd/go/internal/modpush/push.go b/src/cmd/go/internal/modpush/push.go new file mode 100644 index 00000000000000..94289c1a2f02f1 --- /dev/null +++ b/src/cmd/go/internal/modpush/push.go @@ -0,0 +1,40 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package modpush + +import ( + "fmt" + "net/http" + "os" +) + +func Push(endpoint string, project string, file string, username string, password string) { + content, err := os.Open(file) + if err != nil { + fmt.Printf("Unable to find %s to push to endpoint %s\n", file, endpoint) + } + + defer content.Close() + + client := http.Client{} + request, _ := http.NewRequest("PUT", endpoint+project+"/@v/"+file, content) + maybeAddCredentials(username, password, request, file, project) + + response, err := client.Do(request) + if err != nil || response.StatusCode != http.StatusCreated { + fmt.Printf("Unable to post %s to %s\n", file, endpoint) + return + } + + fmt.Println("Successfully posted " + file + " to " + endpoint) +} + +func maybeAddCredentials(username string, password string, request *http.Request, file string, project string) { + if username != "" && password != "" { + fmt.Println("Using basic authentication arguments provided") + request.SetBasicAuth(username, password) + } else { + fmt.Printf("Using no credentials to push package %s to %s\n", file, project) + } +} diff --git a/src/internal/cfg/cfg.go b/src/internal/cfg/cfg.go index 4c2cf8ee8b6ee3..6e6a1dae80af9d 100644 --- a/src/internal/cfg/cfg.go +++ b/src/internal/cfg/cfg.go @@ -51,6 +51,7 @@ const KnownEnv = ` GOPATH GOPPC64 GOPRIVATE + GOPUSH GOPROXY GOROOT GOSUMDB